前回はlibcameraを使ってC++からCSI接続のカメラを読み出せるようにしました。
今回は2つのカメラを同時に使用できるようにプログラムを修正します。
おさらい
前回はedward-ardu様のサンプルを利用させていただきました。
https://github.com/edward-ardu/libcamera-cpp-demo/このサンプルはラズパイ5発売前に作成されたものなので、カメラIDは0に固定
されています。そこで
IDを指定する仕様に変更しました。これでラズパイ5に
接続した2つのカメラのうちどちらか片方を指定して利用できるようになりました。
このまま2つのカメラを同時に利用してみます。
int main() { LibCamera cam0; LibCamera cam1; uint32_t width = 640; uint32_t height = 480; uint32_t stride0; uint32_t stride1; cv::Mat frame0; cv::Mat frame1; //カメラを初期化 int ret0 = cam0.initCamera(0); int ret1 = cam1.initCamera(1); //以下画像取得処理等残念ながら
Multiple ProsessManager objects are not allowと出て実行できません。
色々と条件を変えて調べてみると
cam1.initCamera(1)を実行実行した時点でエラーが出ることが分かります。
さらにLibCamera.cpp内にある
initCameraの中身を調べてみると冒頭の
cm = std::make_unique<CameraManager>(); ret = cm->start();のあたりが原因だと分かります。
この
CameraManagerですが、1つのプログラムから同時に2個以上できないようで、
今回作成したメイン関数では
LibCamera cam0 LibCamera cam1でそれぞれ使うことになるのでエラーになってしまいます。
そこで、メイン関数で
CameraManagerを管理できるようにプログラムを修正します。
修正内容
かなり行き当たりばったりな感じで、正しい方法ではない気がしますがとりあえず修正します。
LibCamera.hを確認すると
CameraManagerはLibCamera.hで
std::unique_ptr<CameraManager> cm;と定義されています。libcameraとしては正しい利用方法だと思います。
ただ、このままではメイン関数で設定した
CameraManagerを登録できない様なので、
std::shared_ptr<CameraManager> cm;へ変更します。
cmはclass外から読み出せないprivate変数なのでprivateへ変更すればそれはそれで対応
できそうですが、間違ってアクセスする可能性があるので今回は関数を使うことにします。
class外からも呼び出せるようにpublicメソッド(関数)として
initCamera関数付近に
void setCameraManager(std::shared_ptr<CameraManager> cm_ref);を作成します。
関数の中身はこんな感じ
void LibCamera::setCameraManager(std::shared_ptr<CameraManager> cm_ref){ cm=cm_ref;}続いて
initCameraも編集します。
2カメラ同時使用以外は
CameraManagerを指定するのが煩わしいので、
cmが未設定の場合は既存の処理を実行することにします。
int LibCamera::initCamera(int index){
if(cm==NULL){ int ret;
cm = std::make_unique<CameraManager>();
ret = cm->start();
if (ret){
std::cout << "Failed to start camera manager: "
<< ret << std::endl;
return ret;
}
} cameraId = cm->cameras()[index]->id();
camera_ = cm->get(cameraId);
//以下そのままの処理メイン関数ではinitCameraを実行する前に
CameraManagerを作成したのち、
setCameraManagerにて登録すればいいので
int main() {
LibCamera cam0;
LibCamera cam1;
uint32_t width = 640;
uint32_t height = 480;
uint32_t stride0;
uint32_t stride1;
cv::Mat frame0;
cv::Mat frame1;
//メイン関数でCameraManagerを作成する std::shared_ptr<CameraManager> cm; cm=std::make_unique<CameraManager>(); cm->start(); //camを初期化する前にCameraManagerを登録する cam0.setCameraManager(cm); cam1.setCameraManager(cm); int ret0 = cam0.initCamera(0);
int ret1 = cam1.initCamera(1);
//以下画像取得処理等といった感じに変更します。
これで2カメラ同時にアクセスできるようになったと思います。
遅延の改善
とりあえず2カメラ同時に使えるようになりましたが、遅延が大きく同期していません。
このままステレオカメラ等に利用するのは厳しいでしょう。
原因は例によって
バッファです。
v4l2ではバッファ数を指定するコマンドがありましたが、libcameraではバッファの設定方法が
よくわかりません。そこで今回はバッファがなくなるまで読み続けるという力技で対応することにします。
バッファ対応の前にCPU100%問題に対応しておきます。
前回の記事では
10us程度のスリープを追加すると改善すると書きましたが、これを関数化しておきます。readFrame付近へ追加します。
int LibCamera::readFrameWait(LibcameraOutData *frameData){
int count=0;
int wait_us=100;
while(!LibCamera::readFrame(frameData)){
if(count>=1000000){
//1s以上経過したらあきらめて終了 return -count;
}
usleep(wait_us);
//要 #include <signal.h> count+=wait_us;
continue;
}
return count;
}
この関数を使うことで画像を取得できるまでスリープ付きで繰り返します。
スリープは100us(0.1ms)とし、スリープの合計時間を戻り値としています。
1秒以上応答がなかった場合は取得を諦めてマイナス値を戻り値としています。
実際に使ってみると分かると思いますが、遅延発生中はバッファを読み出すだけなので
スリープ時間が0となります。これを利用してバッファの判定を行います。
こちらもreadFrame付近に追加します。
int LibCamera::readFrameNew(LibcameraOutData *frameData){
int wait_time=0;
int wait_time_sum=0;
int count_max=10;
for(int i=0;i<count_max;i++){
wait_time=LibCamera::readFrameWait(frameData);
wait_time_sum+=wait_time;
if(wait_time<0){
//取得時間が負なら強制終了 return wait_time;
}
if(wait_time>0){
//取得時間が1以上ならループ終了 break;
}
if(i!=count_max-1){
//最終回以外はバッファをクリア LibCamera::returnFrameBuffer(*frameData);
}
}
return wait_time_sum;
}
これでバッファがなくなるまで取得を繰り返すことができます。
readFrameの代わりにreadFrameNewを使うことで遅延が改善したと思います。