Spresense ズームレンズ対応HDRカメラを動作させてみた

[前回の記事](https://qiita.com/TaroYoshino)でズームレンズのモーター制御に成功しました。転がっていたデータシートがあっていてよかった。今回はいよいよHDRカメラとあわせてカメラとして動作するか買う人をしていきます。

## SPRESENSE HDRカメラズームレンズの接続
Spresense HDRカメラとしての動作を確認するために、LCDとSDカード、そして画像を保存するためのシャッターボタンを追加しました。SDカードとLCDとの接続には拡張ボードを使います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/598183/bbc2af05-8367-e795-b578-e1b4ce09475c.png)

実際の接続の様子はこちら。かなりビジーになってしまいました。

![DSC02605.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/598183/5d5c9f4f-f0d1-917b-56f3-d8fd8d3707c2.jpeg)

![DSC02606.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/598183/ec2b0dcb-591a-6aed-443e-dd8b04df4ba4.jpeg)

## SPRESENSE HDRカメラズームレンズのプログラム
ズームとフォーカスは可変抵抗で調整します。シャッターを押すとBMP画像でSDカードに保存します。

```c++
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <arch/cxd56xx/scu.h>
#include <arch/cxd56xx/adc.h>
#include <Camera.h>
#include <Stepper.h>
#include <Adafruit_ILI9341.h>
#include <BmpImage.h>
#include <SDHCI.h>

// D27 - Focus A+ (BLACK)
// D28 - Focus A- (BLUE) 
// D01 - Focus B+ (RED)
// D00 - Focus B- (WHITE)

// D23 - Zoom A+ (ORANGE)
// D24 - Zoom A- (GREEN)
// D25 - Zoom B+ (YELLOW)
// D26 - Zoom B- (PURPLE)

#define FOCUS_AP (27)
#define FOCUS_AN (28)
#define FOCUS_BP (1)
#define FOCUS_BN (0)

#define ZOOM_AP (23)
#define ZOOM_AN (24)
#define ZOOM_BP (25)
#define ZOOM_BN (26)

#define TFT_DC  9
#define TFT_CS  10

Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC);
BmpImage bmp;
SDClass theSD;

const int turnSteps = 1607;  // 0.224 deg/step
const int zoomMaxStep = 2342;
const int focusMaxStep = 2342;
const int rpm = 20;

Stepper FocusStepper(turnSteps, FOCUS_AP, FOCUS_AN, FOCUS_BP, FOCUS_BN);
Stepper ZoomStepper(turnSteps, ZOOM_AP, ZOOM_AN, ZOOM_BP, ZOOM_BN);

int fd_a2;
int fd_a3;

const int intPin = 21;
bool bButtonPressed = false;
void changeState() {
  bButtonPressed = true;
}

void setup() {
  Serial.begin(115200);

  tft.begin();
  tft.setRotation(3);

  theCamera.begin();
  theCamera.setStillPictureImageFormat(
    CAM_IMGSIZE_QVGA_H, CAM_IMGSIZE_QVGA_V, 
    CAM_IMAGE_PIX_FMT_RGB565);

  while (!theSD.begin()) { Serial.println("Insert SD Card"); } 
  pinMode(intPin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(intPin) ,changeState ,FALLING);

  // initialize LLPADC 2 and 3
  fd_a2 = open("/dev/lpadc2", O_RDONLY);
  fd_a3 = open("/dev/lpadc3", O_RDONLY);
  ioctl(fd_a2, SCUIOC_SETFIFOMODE, 1);
  ioctl(fd_a3, SCUIOC_SETFIFOMODE, 1);
  ioctl(fd_a2, ANIOC_CXD56_FIFOSIZE, 2);
  ioctl(fd_a3, ANIOC_CXD56_FIFOSIZE, 2);
  ioctl(fd_a2, ANIOC_CXD56_START, 0);
  ioctl(fd_a3, ANIOC_CXD56_START, 0);

  FocusStepper.setSpeed(rpm);
  ZoomStepper.setSpeed(rpm);

  Serial.println("move home position");
  FocusStepper.step(focusMaxStep);
  ZoomStepper.step(zoomMaxStep);
  Serial.println("home position");
  delay(1000);
}

void loop() {
  static int zoom_pos = zoomMaxStep;
  static int focus_pos = focusMaxStep;

  int16_t val_z;
  int16_t val_f;
  while(read(fd_a2, &val_z, sizeof(int16_t)) == 0);
  while(read(fd_a3, &val_f, sizeof(int16_t)) == 0);

  val_z = map(val_z, -0x7fff, 0x7fff, 0, zoomMaxStep);
  val_f = map(val_f, -0x7fff, 0x7fff, 0, focusMaxStep);

  Serial.println("Zoom: " + String(val_z));
  Serial.println("Focus: " + String(val_f));

  int zoom_step = val_z - zoom_pos;
  int focus_step = val_f - focus_pos;
  if (abs(zoom_step) > 100 || abs(focus_step) > 100) {
    zoom_pos = val_z;
    focus_pos = val_f;
    ZoomStepper.step(zoom_step);
    FocusStepper.step(focus_step);
  }

  CamImage img = theCamera.takePicture();
  if (img.isAvailable()) {
    tft.drawRGBBitmap(0, 0, (uint16_t*)img.getImgBuff(), CAM_IMGSIZE_QVGA_H, CAM_IMGSIZE_QVGA_V);
    if (bButtonPressed) {
      static int g_counter = 200;
      bmp.begin(BmpImage::BMP_IMAGE_RGB565, CAM_IMGSIZE_QVGA_H, CAM_IMGSIZE_QVGA_V, img.getImgBuff());
      bmp.alignImageLine(false); 
      char filename[16] = {};
      sprintf(filename, "rgb16%03d.bmp", g_counter++);
      if (theSD.exists(filename)) theSD.remove(filename);
      File myFile = theSD.open(filename, FILE_WRITE);
      myFile.write(bmp.getBmpBuff(), bmp.getBmpSize());
      myFile.close();
      bmp.end();
      Serial.println("Saved as " + String(filename));    
      bButtonPressed = false;
    }
  }

}
```

## 撮影結果
フォーカスを手動で合わすのはちょっと難儀しましたが、慣れてくるとなんとかなりますね。今後、オートフォーカスの実装を試してみたいと思います。倍率をかえた画像をいくつか撮影をしましたので、その結果をこちらに披露します。被写体は私の大好きなトラスト・イカちゃんです。


![rgb16200.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/598183/2b9fb79c-908b-aa90-6ac2-21239881a38c.png)
最大ズーム

![rgb16004.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/598183/1a8128c0-7854-e679-d318-5197bcc09b77.png)
フォーカスが甘かった (´・ω・｀)

![rgb16010.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/598183/d6a79441-32cd-d5f3-0076-b44d590d4847.png)

![rgb16013.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/598183/255260b9-4711-5f3c-2caf-e21bf3541c2a.png)

![rgb16016.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/598183/191e7e97-0a7d-4cbe-7228-a6a4f269177f.png)

![rgb16100.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/598183/542a9bb6-1d60-b9d2-0cb4-c5c4956910ab.png)
最大ワイドでフォーカス合わず

最大ワイドのケースでは、被写体との距離が近すぎてフォーカスがあいませんでした。レンズの特性をよく考えて制御をする必要がありますね。

こうして画像を並べてみると、かなりズームできるのがわかります。これは、かなり使えそうです。HDRカメラなら監視カメラとしてもばっちりです。オートフォーカスやパン・チルト機能をつければ立派なカメラになりそうですね。

そのあたりは今後検討していきたいと思います。


関連記事
[Spresense HDRカメラのズームフォーカスレンズ用のフォルダを作る](https://qiita.com/TaroYoshino/items/d0abd58370d2c42f6fbb)
[Spresense HDRカメラ用のズームフォーカスレンズのモーターの仕様を確認する](https://qiita.com/TaroYoshino/items/7f2b4e3c9ce0c7de4176)
[Spresense HDRカメラ用のズームフォーカスレンズのモーターを制御する](https://qiita.com/TaroYoshino/items/342d6180e4ca6aa3b439)