カメラから画像を取得してSDカードに保存する方法を学ぶ

はじめに

 スマートフォンの普及にともなって、安価に入手できる小型のカメラが増えています。当コラムではカメラから取得したデータの表示・収集からAI分析までの開発チュートリアルを3部構成でお届けします。今回はVol.2として、RTOS「μC3/Compact」と「STM32L496G-Discovery」で、カメラからデータを取得しSDカードに保存する方法をご紹介します。

概要

 AI分野では、人や物の画像認識を使った応用が多く存在します。この一例として、学習済みモデルをそのまま使わず、独自のデータを使った物体認識の学習モデルの作成を考えた場合には、学習データとなる画像が必要になります。

 このチュートリアルでは、以下の画像のようにカメラから得られる画像データを、SDカードに保存する方法を紹介します。

 組み込み未経験者でも、このチュートリアルとコードを合わせて読むことで、画像データをSDカードへ保存する仕組みを理解することができます。また、文末に用語集を記述しましたので、こちらも参照ください。

必要なもの

ボードSTM32L496G-Discovery
カメラモジュールB-CAMS-OMV
SDカードNetac microSD 8GB SDHC
IDEEWARM 8.50.6
SDKμC3/Compact for STM32L4, STM32CubeMX
ミドルウェアFatFs R0.12c, LibJPEG 8d
開発PCWindows 10

μC3/Compact for STM32L4はhttps://www.eforce.co.jp/uc3-compact/から評価版をダウンロードすることができます。

処理の概要

1.カメラからDCMIを通じて画像データを取得
2.画像データをDMAでRAMに転送
3.LibJPEGでRGB565形式で得られる画像データをJPEG形式に変換
4.SDMMCでJPEG画像データをSDカードに保存する

 上記の流れでカメラから得られる画像データを、SDカードに保存することが可能になります。このチュートリアルで使用したソースコードは、STM32CubeMXでDCMIやSDMMCなどのパラメータ設定、μC3のConfiguratorで割り込み・タスクの設定・生成したコードを元にして作成します。

ハードウェアの接続

 カメラはB-CAMS-OMVというSTマイクロエレクトロニクス社(以下STマイクロ社)の製品で、OV5640というイメージセンサが載っているものを使います。

 L496にはカメラ用のコネクタが付属しており、B-CAMS-OMVとFFCケーブルで簡単に接続ができます。

JPEGエンコーディングとファイルシステム

 今回、カメラから得られる画像データはRGB565の2byte形式で表現され、240*240*2=115200 byteのデータ量となりますが、JPEGエンコーディングをすることでPCからビューアーで画像を確認したり、ファイルサイズを小さくすることができます。

 JPEGエンコーディングは大きく分けて、下記より構成されます。

1.Chrominance Subsampling
2.Discreet Cosine Transformation & Quantization
3.Run-Length, Delta & Huffman Encoding

 RGBで表現されたデータを順に変換して、最終的に、小さなファイルサイズにエンコーディングを行います。
このチュートリアルでは、JPEGエンコーディングにLibJPEGというライブラリを使用します。

 データをSDカードにファイルとして保存するためには「どんなファイル形式なのか」「どこからどこまでが1つのファイルなのか」「どんな名前なのか」といったメタ情報と呼ばれる情報が必要になります。このチュートリアルでは、これらの情報を管理するために、FatFsと呼ばれるファイルシステム(ファイルを管理する仕組み)を使用します。

 FatFsで提供されるf_openやf_writeといった関数を使うことで、これらの事項を意識することなく、シンプルにファイルの保存や読み込みができます。

詳細の設定

カメラ用DCMIの設定項目

 カメラは「画像データの左上の1ピクセル」から「右下の1ピクセル」まで1行ずつ順番にデータを転送していきます。1行分のデータ転送が終わるタイミングをHSYNC、1フレーム分のデータ転送が終わるタイミングをVSYNC、8bit分のピクセルのデータ転送が終わるタイミングをPIXSYNCで取りますので、これらのパラメータをSTM32CubeMxで設定する必要があります。

 加えてSyncroモードは、STM32CubeMXからExternal SynchroかEmbedded Synchroかを選択出来ますが、カメラからのHSYNCなどで同期タイミングを取りますので、External Synchroで設定します。

 LCDに画像を表示するチュートリアルでは、DCMIにDCMI_MODE_CONTINUOUSを設定して連続的に画像データを取得して明示的にストップする必要がありましたが、このチュートリアルではDCMI_MODE_SNAPSHOTを設定しているため、以下の画像データ取得フローとなります。

1. HAL_DCMI_Start_DMAで画像データの取得を開始
2. HAL_DCMI_FrameEventCallbackにて画像データ取得完了割り込み
3. HAL_DCMI_Stopで画像データの取得停止

 上記のように、1枚の画像データ取得を行うため、定期的に画像データをSDカードに保存する場合は、タイマの割り込みなども併用することで実現できると考えられます。

// Start DCMI
HAL_DCMI_Start_DMA(&hdcmi, DCMI_MODE_SNAPSHOT,  (uint32_t)pBuffer , (ST7789H2_LCD_PIXEL_WIDTH*ST7789H2_LCD_PIXEL_HEIGHT)/2 );
// Wait until frame transfer complete
while(start_the_camera_capture == 0) {;}
// Frame transfer complete
void BSP_CAMERA_FrameEventCallback(void)
{
  start_the_camera_capture = 1;  
}
// Stop DCMI
HAL_DCMI_Stop(&hdcmi);

RGB565からRGB888形式への変換

 RGB565で表現されるデータは16bitとなり、0b11111(赤) 111111(緑) 11111(青)という順番でデータが保存されます。RGB565の各ピクセルデータをpix565とすると、

 r = (pix565 >> 11) & 0x1f;で赤要素を得ることができ、255/32 * rとすることで、RGB888形式への変換を行っています。

r = (RGB565 >> 11) & 0x1f;
g = (RGB565 >> 5) & 0x3f;
b = RGB565 & 0x1f;
blue = 255/31 * b;
green = 255/63 * g;
red = 255/32 * r;

SDMMC設定項目

 SDMMCで使用するGPIOのピンは、データの送受信で使用するD0~D3ピン、CLOCKで使用するピン、CMD送受信用のピンから構成されます。

 SDカードとの通信フォーマットは、SD Associationが定めるSD Specificationにより規定されています。SDMMCを使用することで、規格に従ったSDカードならどのカードでも同じ手順でWrite/Readが可能になります。

 コマンドを使ってSDカードの設定を行いますが、以下のレジスタで通信を行います。

1.SDMMC->CMDレジスタ:送信コマンド用のレジスタ
2.SDMMC->RESPCMDレジスタ:SDカードからの返答コマンド用のレジスタ
3.SDMMC->RESP1~4:SDカードからの返答データ用のレジスタ

カメラデータからSDカードへのデータの流れ

 データはカメラから取得するRGB565データをRGB888に変換し、LibJPEGを使ってJPEGに変換した後に、SDカードにFatFsを保存をする流れになります。

 データの取得については、HAL_DCMI_Start_DMA関数を呼び出すことでDCMIで取得したデータがDMAでRAMに転送されます。そして、DCMI_DMAXferCpltというコールバック関数から画像データの転送完了を知ることができます。

 転送された画像データは、以下の手順でSDカードに保存できます。

1.マウント

FATFS_LinkDriver(&SD_Driver, SDPath);
f_mount(&SDFatFs, (TCHAR const*)SDPath, 0);

 SDPathにマウントするディレクトリ(今回はルートディレクトリ)を指定し、SD_Driverにはinitやread/writeで使う関数を定義しておきます。SDFatFsはFAT32などのファイルシステムのタイプなどを持つオブジェクトとして、f_mountにより設定されます。

2.ファイルオープン

f_open(&MyFile, filename, FA_CREATE_ALWAYS | FA_WRITE)

 MyFileにはfilenameで指定されたファイルのオブジェクトが登録されます。

3.JPEGエンコーディングと保存

          /* Step 1: allocate and initialize JPEG compression object */
          /* Set up the error handler */
          cinfo.err = jpeg_std_error(&jerr);
          /* Initialize the JPEG compression object */  
          jpeg_create_compress(&cinfo);
          /* Step 2: specify data destination */
          jpeg_stdio_dest(&cinfo, &MyFile);
          /* Step 3: set parameters for compression */
          cinfo.image_width = ST7789H2_LCD_PIXEL_WIDTH;
          cinfo.image_height = ST7789H2_LCD_PIXEL_HEIGHT;
          cinfo.input_components = 3;
          cinfo.in_color_space = JCS_RGB;
          /* Set default compression parameters */
          jpeg_set_defaults(&cinfo);
          cinfo.dct_method  = JDCT_FLOAT;
          jpeg_set_quality(&cinfo, IMAGE_QUALITY, TRUE);
          /* Step 4: start compressor */
          jpeg_start_compress(&cinfo, TRUE);
          row_pointer = (JSAMPROW)buff;
          jpeg_write_scanlines(&cinfo, &row_pointer, 1);            
          /* Step 5: finish compression */
          jpeg_finish_compress(&cinfo);
          /* Step 6: release JPEG compression object */
          jpeg_destroy_compress(&cinfo);

 struct jpeg_compress_struct cinfoとして定義されるcinfoに必要な情報を定義し、設定した上で、jpeg_write_scanlinesにてbuffに入っている1行分のデータを書き込みます。

4.ファイルクローズ

f_close(&MyFile);

まとめ

 カメラから取得した画像データをSDカードに保存するためには、カメラから取得した画像データをDMAでRAMに転送し、RAMに保存されたデータをRGB565→RGB888に変換します。

 カメラからの画像データ取得方法は、カメラモジュールによりインタフェースが異なりますが、STマイクロ社のボードの場合はDCMIを使うことで、毎回モジュール依存のRead/Writeの処理を書く必要がなくなります。カメラの初期化を書けばデータをDCMIで取得し、RAMに転送してくれます。

 得られたRGB888データはLibJPEGによりJPEG画像にエンコーディングされます。JPEG画像をSDカードに保存するためには、SDカードとの通信処理の初期化とデータ書き込みを行う必要があります。この時、SDカードとのインタフェースにST社のSDMMCを用いることで、どのSDカードでもコマンドやデータのWrite/Readが可能となります。FatFsを併用することで、JPEG画像データとして意味のあるファイルとして、SDカードにデータを保存することが可能になります。

用語

DCMI
 DCMI (Digital Camera Interface) はカメラとのピンを接続し、カメラに合わせた初期設定をI2Cなどを通じて行うことにより、カメラからのデータ受信、VSYNC・HSYNC・PIXSYNCのようなタイミングのパラメータやDMAを用いたRAMへのデータ転送を担ってくれるインタフェースになります。

I2C
 プロセッサとカメラなどがお互いに通信を行う方法の1つ。カメラもI2Cでレジスタに設定値を書き込むことでカメラデータの出力方法やリセットなどの制御ができます。

DMA
 CPUを介さないデータの転送方法。一般的にデータ転送などにはある程度の時間がかかるため、CPUをデータ転送に使ってしまうとその間他の処理が出来なくなってしまいます。DMAを使えば、データ転送中はCPUが他の処理を行うことが可能になります。

参考文献

Discovery kit with STM32L496AG MCU
https://www.st.com/en/evaluation-tools/32l496gdiscovery.html

Unraveling the JPEG
https://parametric.press/issue-01/unraveling-the-jpeg/