聞きかじりめも

主にC++やメディア処理技術などに関して気付いたことを書いていきます.ここが俺のメモ帳だ!

PointGray社のカメラを使い倒す

本記事は研究や高貴な趣味などでPointGray社の超小型高機能産業用カメラ(Flea3など)を使う人のためのTipsです.ソースコードだけ欲しい方は最後の方にすべてまとめたラッパークラスが書いてありますので,参考にしてください.

※注意!古いPointGrey FlyCapture2 APIにはバグがあるので,VS2015では以下のライブラリはそのままでは動きません(最新版なら問題ないです).この記事に従って修正してください.

自分がPointGrayカメラを使うに至った理由,あるいはKinectV2をやめた理由

今まではKinectV2のRGBカメラでいいじゃんと思っていたのですが,どうやらこいつはKinectV1とは異なり,自動露出や自動ホワイトバランスがOFFにできないらしいという事に気が付きました. このことは海外では散々議論されてきたようで(日本語で調べてもなかなか出てこなくて困った…)我が圧倒的英語力をもって調べた結果,結局本質的な回避方法はないとのことです. (参照: Set Exposure of Kinect 2 Color Camera

注:これはKinectV2の設計思想に由来するもので,複数のサービスが同時に1つのKinectにアクセスできるようにした結果そうなってしまったようです.(1台のPCで複数のKinectV2接続を公式にサポートしていないのもそのため?)ちなみに今のところ,開発チームは今後KinectV2に自動露出をオフにする機能を取り入れる予定はないらしいです.ふざけんな.

そこで私は,そこそこの解像度を持ちながらコンパクトかつ自動露出等が設定可能な高機能カメラが必要になり,たまたま研究室に余っていたPointGray社のカメラを使うことになりました.従ってKinectV2は暫くお払い箱になります.わざわざ入手困難な時期に何個も買ってもらったのに先生ごめんなさい.

Xbox One Kinect センサー

Xbox One Kinect センサー

Knct Adapter for Win En/Zh/Fr/Ja/Es Amer/Asia Hdwr

Knct Adapter for Win En/Zh/Fr/Ja/Es Amer/Asia Hdwr

自分の環境

FlyCapture SDKは私の大好きなOpenCVとの連携が容易で,ビュープラス様が日本語チュートリアルをお出ししていたのもPointGrayカメラを使う理由の一つです.但しこのOpenCV連携はAPIが両方とも最新版ではないのでちょっと情報が古いです.なので英語のリファレンスやサンプルコードを読む羽目になりました.リファレンスはオンラインではなく,FlyCapture2 SDKの中に入ってるdocumentation.chmが参考になります.

デフォルト機能で使う場合

ここはサンプルそのままですが,最低限必要なところだけピックアップしていきます.

 FlyCapture2::Camera flycam;
        FlyCapture2::Error flycamError;

        // Connect the camera
    flycamError = flycam.Connect(0);
    if (flycamError != PGRERROR_OK)
    {
        std::cout << "Failed to connect to camera" << std::endl;
        return;
    }

カメラとの接続部分です.まあ難しいことはないですね.

 flycamError = flycam.StartCapture();
    if (flycamError == PGRERROR_ISOCH_BANDWIDTH_EXCEEDED)
    {
        std::cout << "Bandwidth exceeded" << std::endl;
        return;
    }
    else if (flycamError != PGRERROR_OK)
    {
        std::cout << "Failed to start image capture" << std::endl;
        return;
    }

カメラのキャプチャを開始します.別々のエラーコードを吐いてくれる新設設計.

 FlyCapture2::Image flyImg, bgrImg;
    cv::Mat cvImg;

        // Get the image
    flycamError = flycam.RetrieveBuffer(&flyImg);
    if (flycamError != PGRERROR_OK)
    {
        std::cout << "capture error" << std::endl;
        return cvImg;
    }
    // convert to bgr
    flyImg.Convert(FlyCapture2::PIXEL_FORMAT_BGR, &bgrImg);
    // convert to OpenCV Mat
    unsigned int rowBytes = (unsigned int)((double)bgrImg.GetReceivedDataSize() / (double)bgrImg.GetRows());
    cvImg = cv::Mat(bgrImg.GetRows(), bgrImg.GetCols(), CV_8UC3, bgrImg.GetData(), rowBytes);

ここはwhileループの中.カメラからBGRのフォーマットで流れてくるとは限らないので,flyImg.Convert()で変換してからcv::Matにぶち込みます.

 flycamError = flycam.StopCapture();
    if (flycamError != PGRERROR_OK)
    {
        // This may fail when the camera was removed, so don't show 
        // an error message
    }
    flycam.Disconnect();

終了処理です.変数を解放する必要はありませんが,ちゃんとflycamを止めてやりましょう.

とまあこれだけだったら何の苦労もなかったんですが,私が欲しいのは自動露出等まで制御可能なやつです.ということでPointGrayカメラをOpenCVで動作させるラッパークラスを作りました.画像取得部分がめちゃめちゃ短くなります.

使い方

最後に全ソースコードを載せますが,このFlyCap2CVWrapperクラスで最も重要な部分はここです.使うときはここを変えてください.

    // Set Video Property
    // Video Mode: Custom(Format 7)
    // Frame Rate: 120fps
    flycamError = flycam.SetVideoModeAndFrameRate(VIDEOMODE_FORMAT7, FRAMERATE_FORMAT7);
    Format7ImageSettings imgSettings;
    imgSettings.offsetX = 268;
    imgSettings.offsetY = 248;
    imgSettings.width = 640;
    imgSettings.height = 480;
    imgSettings.pixelFormat = PIXEL_FORMAT_422YUV8;
    flycamError = flycam.SetFormat7Configuration(&imgSettings, 100.0f);
    if (flycamError != PGRERROR_OK)
    {
        std::cout << "Failed to set video mode and frame rate" << std::endl;
        return;
    }
    // Disable Auto changes
    autoFrameRate(false, 85.0f);
    autoWhiteBalance(false, 640, 640);
    autoExposure(false, 1.585f);
    autoSaturation(false, 100.0f);
    autoShutter(false, 7.5f);
    autoGain(false, 0.0f);

最初の行でFORMAT7を指定することでカスタム設定にすることを知らせています.documentationを見るときはFormat7に注意しましょう.

imgSettingsは,画像取得ROI(つまり取得画像の切り出し位置)とピクセルフォーマットを指定します.ピクセルフォーマットはいろいろありますが,白黒画像,YUV,RGB,RAW(Bayer配列?)を指定できます.ビット深度は8bitだったり16bitだったり.

最後のautoXxxx()が自動設定をOn/Offする大事な関数です.ここではかなり暗めな自分の環境で最適な数値を入れているので,実際に使うときはSDK付属のサンプルGUIを弄りながら自分で決めましょう.他の設定が欲しくなったらこの関数の中身を参考にしてみてください.

ソースコード

main.cpp

#include "FlyCap2CVWrapper.h"

using namespace FlyCapture2;

int main(void)
{
    FlyCap2CVWrapper cam;

    // capture loop
    char key = 0;
    while (key != 'q')
    {
        // Get the image
        cv::Mat image = cam.readImage();
        cv::imshow("image", image);
        key = cv::waitKey(1);
    }

    return 0;
}

FlyCap2CVWrapper.h

#pragma once

#ifdef _DEBUG
#define FC2_EXT ".lib"
#define CV_EXT "d.lib"
#else
#define FC2_EXT ".lib"
#define CV_EXT ".lib"
#endif
#pragma comment(lib, "FlyCapture2" FC2_EXT)
#pragma comment(lib, "FlyCapture2GUI" FC2_EXT)
#pragma comment(lib, "opencv_world300" CV_EXT)

#include <opencv2\opencv.hpp>
#include <FlyCapture2.h>
#include <FlyCapture2GUI.h>

class FlyCap2CVWrapper
{
protected:
    FlyCapture2::Camera flycam;
    FlyCapture2::CameraInfo flycamInfo;
    FlyCapture2::Error flycamError;
    FlyCapture2::Image flyImg, bgrImg;
    cv::Mat cvImg;

public:
    FlyCap2CVWrapper();
    ~FlyCap2CVWrapper();
    cv::Mat readImage();
    // Settings
    void autoExposure(bool flag, float absValue);
    void autoWhiteBalance(bool flag, int red, int blue);
    void autoSaturation(bool flag, float absValue);
    void autoShutter(bool flag, float ms);
    void autoGain(bool flag, float dB);
    void autoFrameRate(bool flag, float fps);
    bool checkError();
};

FlyCap2CVWrapper.cpp

#include "FlyCap2CVWrapper.h"

using namespace FlyCapture2;

FlyCap2CVWrapper::FlyCap2CVWrapper()
{
    // Connect the camera
    flycamError = flycam.Connect(0);
    if (flycamError != PGRERROR_OK)
    {
        std::cout << "Failed to connect to camera" << std::endl;
        return;
    }

    // Get the camera info and print it out
    flycamError = flycam.GetCameraInfo(&flycamInfo);
    if (flycamError != PGRERROR_OK)
    {
        std::cout << "Failed to get camera info from camera" << std::endl;
        return;
    }
    std::cout << flycamInfo.vendorName << " "
        << flycamInfo.modelName << " "
        << flycamInfo.serialNumber << std::endl;

    // Set Video Property
    // Video Mode: Custom(Format 7)
    // Frame Rate: 120fps
    flycamError = flycam.SetVideoModeAndFrameRate(VIDEOMODE_FORMAT7, FRAMERATE_FORMAT7);
    Format7ImageSettings imgSettings;
    imgSettings.offsetX = 268;
    imgSettings.offsetY = 248;
    imgSettings.width = 640;
    imgSettings.height = 480;
    imgSettings.pixelFormat = PIXEL_FORMAT_422YUV8;
    flycamError = flycam.SetFormat7Configuration(&imgSettings, 100.0f);
    if (flycamError != PGRERROR_OK)
    {
        std::cout << "Failed to set video mode and frame rate" << std::endl;
        return;
    }
    // Disable Auto changes
    autoFrameRate(false, 85.0f);
    autoWhiteBalance(false, 640, 640);
    autoExposure(false, 1.585f);
    autoSaturation(false, 100.0f);
    autoShutter(false, 7.5f);
    autoGain(false, 0.0f);

    flycamError = flycam.StartCapture();
    if (flycamError == PGRERROR_ISOCH_BANDWIDTH_EXCEEDED)
    {
        std::cout << "Bandwidth exceeded" << std::endl;
        return;
    }
    else if (flycamError != PGRERROR_OK)
    {
        std::cout << "Failed to start image capture" << std::endl;
        return;
    }
}

FlyCap2CVWrapper::~FlyCap2CVWrapper()
{
    flycamError = flycam.StopCapture();
    if (flycamError != PGRERROR_OK)
    {
        // This may fail when the camera was removed, so don't show 
        // an error message
    }
    flycam.Disconnect();
}

// 自動露出設定
// true -> auto, false -> manual
void FlyCap2CVWrapper::autoExposure(bool flag, float absValue = 1.585f)
{
    Property prop;
    prop.type = AUTO_EXPOSURE;
    prop.onOff = true;
    prop.autoManualMode = flag;
    prop.absControl = true;
    prop.absValue = absValue;
    flycamError = flycam.SetProperty(&prop);
    if (flycamError != PGRERROR_OK)
    {
        std::cout << "Failed to change Auto Exposure Settings" << std::endl;
    }
    return;
}

// 自動ホワイトバランス設定
void FlyCap2CVWrapper::autoWhiteBalance(bool flag, int red = 640, int blue = 640)
{
    Property prop;
    prop.type = WHITE_BALANCE;
    prop.onOff = true;
    prop.autoManualMode = flag;
    prop.valueA = red;
    prop.valueB = blue;
    flycamError = flycam.SetProperty(&prop);
    if (flycamError != PGRERROR_OK)
    {
        std::cout << "Failed to change Auto White Balance Settings" << std::endl;
    }
    return;
}

// 自動Satulation設定
void FlyCap2CVWrapper::autoSaturation(bool flag, float percent = 50.0f)
{
    Property prop;
    prop.type = SATURATION;
    prop.onOff = true;
    prop.autoManualMode = flag;
    prop.absControl = true;
    prop.absValue = percent;
    flycamError = flycam.SetProperty(&prop);
    if (flycamError != PGRERROR_OK)
    {
        std::cout << "Failed to change Auto Satulation Settings" << std::endl;
    }
    return;
}

// 自動シャッター速度設定
void FlyCap2CVWrapper::autoShutter(bool flag, float ms = 7.5f)
{
    Property prop;
    prop.type = SHUTTER;
    prop.autoManualMode = flag;
    prop.absControl = true;
    prop.absValue = ms;
    flycamError = flycam.SetProperty(&prop);
    if (flycamError != PGRERROR_OK)
    {
        std::cout << "Failed to change Auto Shutter Settings" << std::endl;
    }
    return;
}

// 自動ゲイン設定
void FlyCap2CVWrapper::autoGain(bool flag, float gain = 0.0f)
{
    Property prop;
    prop.type = GAIN;
    prop.autoManualMode = flag;
    prop.absControl = true;
    prop.absValue = gain;
    flycamError = flycam.SetProperty(&prop);
    if (flycamError != PGRERROR_OK)
    {
        std::cout << "Failed to change Auto Gain Settings" << std::endl;
    }
    return;
}

// フレームレート設定
void FlyCap2CVWrapper::autoFrameRate(bool flag, float fps = 85.0f)
{
    Property prop;
    prop.type = FRAME_RATE;
    prop.autoManualMode = flag;
    prop.absControl = true;
    prop.absValue = fps;
    flycamError = flycam.SetProperty(&prop);
    if (flycamError != PGRERROR_OK)
    {
        std::cout << "Failed to change Frame Rate Settings" << std::endl;
    }
    return;
}

// cv::Matへの転送
cv::Mat FlyCap2CVWrapper::readImage()
{
    // Get the image
    flycamError = flycam.RetrieveBuffer(&flyImg);
    if (flycamError != PGRERROR_OK)
    {
        std::cout << "capture error" << std::endl;
        return cvImg;
    }
    // convert to bgr
    flyImg.Convert(FlyCapture2::PIXEL_FORMAT_BGR, &bgrImg);
    // convert to OpenCV Mat
    unsigned int rowBytes = (unsigned int)((double)bgrImg.GetReceivedDataSize() / (double)bgrImg.GetRows());
    cvImg = cv::Mat(bgrImg.GetRows(), bgrImg.GetCols(), CV_8UC3, bgrImg.GetData(), rowBytes);

    return cvImg;
}

bool FlyCap2CVWrapper::checkError()
{
    return flycamError != PGRERROR_OK;
}