[Spresense] HDR カメラで写真を撮り、YYYYMMDD-HHMMSS を含めたファイル名で microSD に保存するスケッチ

前書き

Spresense でトレイルカメラを作る!ためのスケッチです。

今回は、HDR カメラで写真を 1 枚撮り、その写真のファイル名に YYYYMMDD-HHMMSS を含めて、microSD の images ディレクトリに保存する、という内容だ。

ファイル名に YYYYMMDD-HHMMSS を含めるためには、リアルタイムクロック = RTC (Real-Time Clock) が設定されている必要があるようで、RTC に時刻を設定するために衛星から情報取得するところもコードに含めた。

RTC とは、システムの時刻を正確に記録し、電源がオフの状態でもバッテリーによって動作を継続するハードウェアで、Spresense メインボードに実装されています。

Spresense のハードウェア構成としては、Spresense メインボード + LTE 拡張ボード (LM1) + HDR カメラボードである。microSD は LTE 拡張ボードに挿入している。microSD に保存される画像のサイズは 1280 x 960 である。

LTE 拡張ボードが LM2 であっても、HDR カメラボードが普通のカメラボードでも動作するのではないかと思う。

コード

写真のファイル名が関連するところは赤字で記載した。

#include <Camera.h>
#include <GNSS.h>
#include <RTC.h>
#include <SDHCI.h>

#define BAUDRATE (115200)
#define CAMERA_ID "cam01"

SDClass theSD;
SpGnss gnss;

bool rtcInitialized = false;

bool syncRtcWithGnss() {
  if (gnss.waitUpdate(1000)) {
    SpNavData navData;
    gnss.getNavData(&navData);
    if (navData.time.year >= 2000) {
      RtcTime rtc_time(navData.time.year, navData.time.month, navData.time.day,
                       navData.time.hour, navData.time.minute, navData.time.sec);
      RTC.setTime(rtc_time);
      return true;
    }
  }
  return false;
}

void printError(enum CamErr err) {
  Serial.print("Error: ");
  switch(err) {
    case CAM_ERR_NO_DEVICE:
      Serial.println("No Device");
      break;
    case CAM_ERR_ILLEGAL_DEVERR:
      Serial.println("Illegal device error");
      break;
    case CAM_ERR_ALREADY_INITIALIZED:
      Serial.println("Already initialized");
      break;
    case CAM_ERR_NOT_INITIALIZED:
      Serial.println("Not initialized");
      break;
    case CAM_ERR_NOT_STILL_INITIALIZED:
      Serial.println("Still picture not initialized");
      break;
    case CAM_ERR_CANT_CREATE_THREAD:
      Serial.println("Failed to create thread");
      break;
    case CAM_ERR_INVALID_PARAM:
      Serial.println("Invalid parameter");
      break;
    case CAM_ERR_NO_MEMORY:
      Serial.println("No memory");
      break;
    case CAM_ERR_USR_INUSED:
      Serial.println("Buffer already in use");
      break;
    case CAM_ERR_NOT_PERMITTED:
      Serial.println("Operation not permitted");
      break;
    default:
      Serial.println("Unknown error");
      break;
  }
}

void takePictureAndSave() {
  Serial.println("写真を撮影します。");
  RtcTime now = RTC.getTime();
  CamImage img = theCamera.takePicture();

  if(img.isAvailable()) {
    char filename[64] = {0};
    snprintf(filename, sizeof(filename), "/images/%s-%04d%02d%02d-%02d%02d%02d.jpg", 
             CAMERA_ID, now.year(), now.month(), now.day(),
             now.hour(), now.minute(), now.second());
    Serial.print("撮影した写真を保存しています。");

    theSD.remove(filename);
    File myFile = theSD.open(filename, FILE_WRITE);
    myFile.write(img.getImgBuff(), img.getImgSize());
    myFile.close();
    Serial.println(String("写真が保存されました。") + String(filename));
  } else {
    Serial.println("写真の撮影に失敗しました。");
  }
}

void setup() {
  CamErr err;

  Serial.begin(BAUDRATE);
  while (!Serial);
  Serial.println("シリアル接続が確立されました。");

  RTC.begin();
  if (RTC.getTime().year() < 2000) {
    Serial.println("GNSS 衛星からの信号受信を開始します。 (数分かかる場合があります)");
    if (gnss.begin() == 0 && gnss.start(COLD_START) == 0) {
      while (!rtcInitialized) {
        if (syncRtcWithGnss()) {
          rtcInitialized = true;
          // GNSS モジュールの動作停止・リソース開放
          gnss.stop();
          gnss.end();
          Serial.println("\nGNSS から時刻情報を取得し RTC を更新しました。");
        } else {
          delay(1000);
          Serial.print(".");
        }
      }
    } else {
      Serial.println("GNSS 衛星からの情報取得に失敗しました。");
    }
  }

  while(!theSD.begin()) {
    Serial.println("microSD カードを挿入してください。");
    delay(1000);
  }
  Serial.println("microSD カードの認識と準備が完了しました。");

  if (!theSD.exists("/images")) {
    if (theSD.mkdir("/images")) {
      Serial.println("images ディレクトリを作成しました。");
    } else {
      Serial.println("images ディレクトリの作成に失敗しました。");
    }
  }

  Serial.println("カメラの初期化を開始します。");
  err = theCamera.begin(0);
  if(err != CAM_ERR_SUCCESS) {
    printError(err);
    return;
  }
  Serial.println("カメラが初期化されました。");

  Serial.println("画像フォーマットを設定します。");
  err = theCamera.setStillPictureImageFormat(
    CAM_IMGSIZE_QUADVGA_H,
    CAM_IMGSIZE_QUADVGA_V,
    CAM_IMAGE_PIX_FMT_JPG);
  if(err != CAM_ERR_SUCCESS) {
    printError(err);
    return;
  }
  Serial.println("画像フォーマットを設定しました。");

  // 写真を 1 枚撮影して保存
  takePictureAndSave();

  // カメラを終了
  theCamera.end();

  Serial.println("プログラムを終了します。");
}

void loop() {
  // ループ処理は行わない
}

トレイルカメラ用プログラムが完成したら複数の Spresense にインストールして使うことを想定している。だから、CAMERA_ID というグローバル変数にカメラ番号的な情報を設定することを意図している。上記では cam01 というのがそれだ。

この cam01 のあとにハイフンが 1 つ入り、YYYYMMDD-HHMMSS が続き、拡張子 .jpg が付く。それが microSD カードの images ディレクトリに保存される。

YYYYMMDD-HHMMSS の時刻は、実際に撮影をするコード theCamera.takePicture() の直前で、RTC.getTime() により RTC から取得するようにしている。

コードを動かした時のコンソール表示

Arduino IDE でプログラムを動かしているところ。

microSD カードに保存されているところ

E ドライブとして認識されているのが microSD カードで、images ディレクトリに保存されていることがわかるだろう。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください