聞きかじりめも

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

cv::waitKey()の処理時間を計測してみた

ニコ動っぽいタイトルになっちゃったけど嫌いな人は許して(笑)

なんか遅くね?

PointGray社のFlea3は最大120fpsまで出る素晴らしい高速カメラですが,OpenGLを使わずOpenCVだけと連携させたところ,どうしても64fps以上出ないという問題にぶち当たりました.これではこのカメラを使う意味がないのでどうにか80fps以上は出せるようにしたい.そこで処理時間を計測してみたところ,どうやらcv::waitKey()で相当時間をかけているようなので,実際に計測してみました.

計測用コード

画像取得の部分に使ってるのは自作クラスですが,これについては以前の記事を参照のこと.

#include "FlyCap2CVWrapper.h"

using namespace FlyCapture2;

int main(void)
{
    FlyCap2CVWrapper cam;
    int count = 0;
    double time100 = 0;
    int waitparam = 1;
    // capture loop
    char key = 0;
    while (key != 'q')
    {

        // Get the image
        cv::Mat image = cam.readImage();
        flip(image, image, 1);
        cv::imshow("image", image);

        double f = 1000.0 / cv::getTickFrequency();
        int64 time = cv::getTickCount();

        key = cv::waitKey(waitparam);

        if (count == 1000)
        {// TickCountの変化を[ms]単位で表示 1000回平均
            std::cout << "param = " << waitparam << ", time = " << time100 / (count+1) << " [ms]" << std::endl;
            time100 = 0;
            count = 0;
            waitparam++;
        }
        else
        {
            count++;
            time100 += (cv::getTickCount() - time)*f;
        }
    }

    return 0;
}

1000回ループしたときの平均時間を表示したらcv::waitKey()の引数を1ずつ上昇させていくコードです. 風の噂によるとcv::getTickCount()は1msくらいの精度はあるみたいなので使っています.本当かどうか良く分かりませんが...

ちなみに計測環境は,Windows8.1 64bit,Intel Xeon X5675,12GB RAMです.

結果

f:id:Mzawa2:20151228175054j:plain

これはひどい

なんと,cv::waitKey()はせいぜい15ms程度の精度しかないことが分かりました.

ちなみにカメラ入力を描画するまでの処理は1.2msでした.

原因

これはcv::waitKey()の実装を見ればわかります.sources\modules\highgui\src\window_w32.cppの1900行目にあります.

CV_IMPL int
cvWaitKey( int delay )
{
    int time0 = GetTickCount();

    for(;;)
    {
        CvWindow* window;
        MSG message;
        int is_processed = 0;

        if( (delay > 0 && abs((int)(GetTickCount() - time0)) >= delay) || hg_windows == 0 )
            return -1;

        if( delay <= 0 )
            GetMessage(&message, 0, 0, 0);
        else if( PeekMessage(&message, 0, 0, 0, PM_REMOVE) == FALSE )
        {
            Sleep(1);
            continue;
        }

        for( window = hg_windows; window != 0 && is_processed == 0; window = window->next )
        {
            if( window->hwnd == message.hwnd || window->frame == message.hwnd )
            {
                is_processed = 1;
                switch(message.message)
                {
                case WM_DESTROY:
                case WM_CHAR:
                    DispatchMessage(&message);
                    return (int)message.wParam;

                case WM_SYSKEYDOWN:
                    if( message.wParam == VK_F10 )
                    {
                        is_processed = 1;
                        return (int)(message.wParam << 16);
                    }
                    break;

                case WM_KEYDOWN:
                    TranslateMessage(&message);
                    if( (message.wParam >= VK_F1 && message.wParam <= VK_F24)       ||
                        message.wParam == VK_HOME   || message.wParam == VK_END     ||
                        message.wParam == VK_UP     || message.wParam == VK_DOWN    ||
                        message.wParam == VK_LEFT   || message.wParam == VK_RIGHT   ||
                        message.wParam == VK_INSERT || message.wParam == VK_DELETE  ||
                        message.wParam == VK_PRIOR  || message.wParam == VK_NEXT )
                    {
                        DispatchMessage(&message);
                        is_processed = 1;
                        return (int)(message.wParam << 16);
                    }

                    // Intercept Ctrl+C for copy to clipboard
                    if ('C' == message.wParam && (::GetKeyState(VK_CONTROL)>>15))
                        ::SendMessage(message.hwnd, WM_COPY, 0, 0);

                    // Intercept Ctrl+S for "save as" dialog
                    if ('S' == message.wParam && (::GetKeyState(VK_CONTROL)>>15))
                        showSaveDialog(window);

                default:
                    DispatchMessage(&message);
                    is_processed = 1;
                    break;
                }
            }
        }

        if( !is_processed )
        {
            TranslateMessage(&message);
            DispatchMessage(&message);
        }
    }
}

あの精度が低いことで有名なGetTickCount()を内部で使っているんですね.メディア処理プログラムでは御法度レベルなんですがこれ...Intelさんなんで未だにリアルタイム画像処理ライブラリでこんなの使ってんの?

ただし,cv::waitKey()は内部的にOpenGLでいうglutSwapBuffers()に相当する処理も兼ねてるらしいので,HighGUIを使う限りこいつからは逃れられません.もっとFPSを上げたい場合は画像処理だけOpenCVにやらせてあとは別の描画ライブラリを使うようにしましょう.

とはいえこれはwindows環境での話なので,別のOSならまた結果は違うかもしれませんね.Linuxだともっと早いかも.