Raspberry Piと専用のカメラモジュールを使用し、ONVIF対応の監視カメラを作成するシリーズ記事です。
本記事はリンク情報システム株式会社の有志が作成しています。
前回の記事では監視カメラ映像配信アプリのビルド方法について説明したので、今回からはプログラムの中からポイントとなる場所を解説していきます。
#静止画撮影(カメラ制御)
###カメラのセットアップ
事前にraspi-configでカメラモジュールを有効化し、再起動しておきます。
第4回で設定済みです。
##MMAL (Multi-Media Abstraction Layer) を使って静止画撮影
Raspbery Piには、 MMAL というライブラリがあり、これを使用して、カメラモジュールから静止画や動画のデータを取得する事ができます。
また、第4回で静止画取得に使った raspistill も MMAL を使用して静止画を取得しています。
そこで、raspistillのソースファイル RaspiStill.c を元にポイントとなる処理を抜粋して解説します。
参考元:userland/RaspiStill.c at master · raspberrypi/userland · GitHub
静止画取得処理の流れは以下のようになります。
1.カメラ設定
2.エンコーダー設定
3.カメラとエンコーダーの接続
4.キャプチャ開始
5.データ受信
####1.カメラ設定 (create_camera_component関数)
カメラから取得する画像のサイズ等の設定を行います。
/* コンポーネント作成 */
mmal_component_create(MMAL_COMPONENT_DEFAULT_CAMERA, &camera);
/* ポート取得 */
camera_still_port = state.camera_component->output[MMAL_CAMERA_CAPTURE_PORT];
/* カメラの撮影条件を設定 */
MMAL_PARAMETER_CAMERA_CONFIG_T cam_config =
{
{ MMAL_PARAMETER_CAMERA_CONFIG, sizeof(cam_config) },
.max_stills_w = state->width, /* 画像の幅 */
.max_stills_h = state->height, /* 画像の高さ */
.stills_yuv422 = 0,
.one_shot_stills = 1,
.max_preview_video_w = state->width,
.max_preview_video_h = state->height,
.num_preview_video_frames = 3,
.stills_capture_circular_buffer_height = 0,
.fast_preview_resume = 0,
.use_stc_timestamp = MMAL_PARAM_TIMESTAMP_MODE_RAW_STC
};
mmal_port_parameter_set(camera->control, &cam_config.hdr);
/* ポートを有効化 */
mmal_port_enable(camera->control, camera_control_callback);
####2.エンコーダー設定 (create_encoder_component関数)
カメラから取得した画像をエンコードする為の設定を行います。
JPEGで圧縮する場合は以下のように設定します。
/* コンポーネント作成 */
mmal_component_create(MMAL_COMPONENT_DEFAULT_IMAGE_ENCODER, &encoder);
/* ポート取得 */
encoder_input = encoder->input[0];
encoder_output = encoder->output[0];
/* 条件設定 */
encoder_output->format->encoding = MMAL_ENCODING_JPEG;
mmal_port_format_commit(encoder_output);
/* ポートを有効化 */
status = mmal_port_enable(encoder_output_port, encoder_buffer_callback);
####3.カメラとエンコーダーの接続 (main関数)
カメラからの画像出力をエンコーダーの入力に接続します。
connect_ports(camera_still_port, encoder_input_port, &state.encoder_connection);
####4.キャプチャ開始 (main関数)
画像のキャプチャを開始します。作業用バッファを通知することで、そのバッファに生成された画像データが格納されます。
作業バッファをキューで管理し使い回す事で、メモリの断片化を防ぐ事に繋がります。
/* ファイルオープン */
output_file = fopen(use_filename, "wb");
callback_data.file_handle = output_file;
/* 受信用バッファを出力ポートに渡す */
num = mmal_queue_length(state.encoder_pool->queue);
for (q=0;q<num;q++)
{
MMAL_BUFFER_HEADER_T *buffer = mmal_queue_get(state.encoder_pool->queue);
if (!buffer)
vcos_log_error("Unable to get a required buffer %d from pool queue", q);
if (mmal_port_send_buffer(encoder_output_port, buffer)!= MMAL_SUCCESS)
vcos_log_error("Unable to send a buffer to encoder output port (%d)", q);
}
/* 画像受信終了を待つ */
vcos_semaphore_wait(&callback_data.complete_semaphore);
callback_data.file_handle = NULL;
/* ファイルクローズ */
fclose(output_file);
####5.データ受信 (main関数)
バッファに画像が格納されると、mmal_port_enableで設定しておいた、encoder_buffer_callback関数が呼び出されます。
1枚の画像データに対して複数回分割して関数が呼び出される場合があります。
その為、buffer->flagsで1枚分の画像データの受信が終了したか判断します。
static void encoder_buffer_callback(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer)
{
int complete = 0;
// We pass our file handle and other stuff in via the userdata field.
PORT_USERDATA *pData = (PORT_USERDATA *)port->userdata;
if (pData)
{
int bytes_written = buffer->length;
if (buffer->length && pData->file_handle)
{
mmal_buffer_header_mem_lock(buffer);
/* 受信データ(JPEG形式の画像データ)を出力 */
bytes_written = fwrite(buffer->data, 1, buffer->length, pData->file_handle);
mmal_buffer_header_mem_unlock(buffer);
}
// We need to check we wrote what we wanted - it's possible we have run out of storage.
if (bytes_written != buffer->length)
{
vcos_log_error("Unable to write buffer to file - aborting");
complete = 1;
}
// Now flag if we have completed
if (buffer->flags & (MMAL_BUFFER_HEADER_FLAG_FRAME_END | MMAL_BUFFER_HEADER_FLAG_TRANSMISSION_FAILED))
complete = 1; /* 出力終了 */
}
else
{
vcos_log_error("Received a encoder buffer callback with no state");
}
// release buffer back to the pool
mmal_buffer_header_release(buffer);
// and send one back to the port (if still open)
if (port->is_enabled)
{
MMAL_STATUS_T status = MMAL_SUCCESS;
MMAL_BUFFER_HEADER_T *new_buffer;
new_buffer = mmal_queue_get(pData->pstate->encoder_pool->queue);
if (new_buffer)
{
status = mmal_port_send_buffer(port, new_buffer);
}
if (!new_buffer || status != MMAL_SUCCESS)
vcos_log_error("Unable to return a buffer to the encoder port");
}
if (complete)
vcos_semaphore_post(&(pData->complete_semaphore));
}
###まとめ
出力されたファイルはペイントブラシなどで開いて確認してみましょう。
画像が表示されればOKです。
また、今回紹介したRaspiStill.c は RaspiCamControl.c と RaspiCLI.c にあるカメラやエンコーダーを制御する関数を経由してMMALドライバにアクセスしています。
監視カメラ映像配信アプリでも、RaspiCamControl.c や RaspiCLI.c を使用して静止画取得処理を実現してます。
リンク情報システム株式会社では一緒に働く仲間を随時募集しています!
また、お仕事のご依頼、ビジネスパートナー様も募集しております。お気軽にご連絡ください。