Help us understand the problem. What is going on with this article?

リアルタイム画像処理のための動体視力を求めないprintfデバッグ

More than 1 year has passed since last update.

はじめに

リアルタイム画像処理をチューニングすると,計算速度や精度などをリアルタイムに表示しながら速くなったか,精度が維持できているかなどを確認しながら開発することが多くなります.

そのための簡単な確認方法はprintfデバッグとなります.
しかし,そのprintfデバッグも,実行速度が30msなどリアルタイム処理になるとあっという間に下に流れていき,必要な情報を追うためには高い動体視力が要求されます.

以下のような処理とかだと,各funcが速いとつらいですよね.

while(1)
{
    timer.start()
    funcA();
    cout<<timer.stop()<<endl;
    timer.start()
    funcB();
    cout<<timer.stop()<<endl;
    timer.start()
    funcC();
    cout<<timer.stop()<<endl;
}

イメージはこんな感じの出力です.
(昔の自分の目はこれが見えていて,問題なかったってのが信じられません.年齢に伴って動体視力も結構落ちます...)
console2.gif

これを何とかするには,以下の動画(コマンドプロンプトで動くゲーム2(python))みたいな感じでコンソール画面を同じ場所にprintf結果を出力するような懐かしのテクニックを駆使すれば問題解決可能です.
ですが,そのためには関係のないコーディングを結構頑張る必要があります.

そこでこの記事では,コンソール出力用のライブラリなどは使わずに,コンソール出力を黒い画像に白い文字を書くことで再現し,目に優しいprintfデバッグを実現します.

putText

OpenCVでは画像にテキストを書き出す関数としてcv::putTextがあります.それを活用して今回はコンソール画面を作るクラスを作ってみましょう.

void cv::putText(InputOutputArray   img,
                 const String &     text,
                 Point  org,
                 int    fontFace,
                 double     fontScale,
                 Scalar     color,
                 int    thickness = 1,
                 int    lineType = LINE_8,
                 bool   bottomLeftOrigin = false 
)   

これを座標をうまく指定して黒い画面に出力してあげればコンソール出力っぽくなります.出力結果は以下のようにできます.

コマンドプロンプトに出力されている文字を追うよりも断然目に優しくなっています.ハズキルーペなんか比ではありません.

console3.gif

さらにフォントにはいろいろあります(リンク)が全体的にダサいです.
特に,すべてのフォントがプロポーショナルフォントであるためレイアウトが崩れてしまいます.
次は,OSに搭載されているフォントを使って表示してみましょう.

参考:いろいろなフォント
text1.png
生成コード

void fonttest()
{
    namedWindow("font");
    Mat show(Size(640, 320), CV_8UC3);
    String str = "This is OpenCV";
    int n = 0;
    int step1 = 20;
    int step2 = 10;
    putText(show, "OpenCV FONT_HERSHEY_COMPLEX", Point(10, 30), FONT_HERSHEY_COMPLEX, 1, Scalar::all(255));
    n++;
    putText(show, "OpenCV FONT_HERSHEY_COMPLEX_SMALL", Point(10, 30 + step1 * n + step2 * n), FONT_HERSHEY_COMPLEX_SMALL, 1, Scalar::all(255));
    n++;
    putText(show, "OpenCV FONT_HERSHEY_DUPLEX", Point(10, 30 + step1 * n + step2 * n), FONT_HERSHEY_DUPLEX, 1, Scalar::all(255));
    n++;
    putText(show, "OpenCV FONT_HERSHEY_PLAIN", Point(10, 30 + step1 * n + step2 * n), FONT_HERSHEY_PLAIN, 1, Scalar::all(255));
    n++;
    putText(show, "OpenCV FONT_HERSHEY_SCRIPT_COMPLEX", Point(10, 30 + step1 * n + step2 * n), FONT_HERSHEY_SCRIPT_COMPLEX, 1, Scalar::all(255));
    n++;
    putText(show, "OpenCV FONT_HERSHEY_SCRIPT_SIMPLEX", Point(10, 30 + step1 * n + step2 * n), FONT_HERSHEY_SCRIPT_SIMPLEX, 1, Scalar::all(255));
    n++;
    putText(show, "OpenCV FONT_HERSHEY_SIMPLEX", Point(10, 30 + step1 * n + step2 * n), FONT_HERSHEY_SIMPLEX, 1, Scalar::all(255));
    n++;
    putText(show, "OpenCV FONT_HERSHEY_TRIPLEX", Point(10, 30 + step1 * n + step2 * n), FONT_HERSHEY_TRIPLEX, 1, Scalar::all(255));

    imshow("font", show);
    waitKey();
}

addText

もし,GUIにQtを有効化している場合,テキスト出力にaddText関数が使えます.関数は以下で定義されています.

void cv::addText    (   const Mat &     img,
const String &  text,
Point   org,
const String &  nameFont,
int     pointSize = -1,
Scalar  color = Scalar::all(0),
int     weight = QT_FONT_NORMAL,
int     style = QT_STYLE_NORMAL,
int     spacing = 0 
)       

概ね,第4引数のint fontFaceconst String& nameFont,に変わっただけで使い方はputTextと同じです.大事なことは,引数にstringでフォントの名前が入れられることです.
例えば,引数にConsolas(モノスペースフォント)を指定してみましょう.

cv::addText(src, "text", Point(100, 100), "Consolas", fontSize, color);

console4.gif

そうすると,フォントもきれいにかつ出力結果が整形されてきれいに整形されて出力されます.
これでだいぶ目に優しいですね.

コード

実際使ったコードを以下に張り付けておきます.
なお,この関数はこのプロジェクトからの抜粋です.

基本的な使い方は以下の通りです.

ConsoleImage ci(Size(300,300));//consoleの画像サイズを指定
//ci.setIsLineNumber();//この命令で行番号を表示することもできます.
ci("text output");//operator()でテキストが出力可能です.
string str="abc";
ci(str);//stringも出力可能です.
ci("%d",10);//printfの入力も受け付けます.
ci(Scalar(0,255,0),"text");//引数の先頭に色を指定してテキストカラーも変えられます.
ci.show();//画面を出力します.デフォルトが出力をclsします.
waitKey(1);//imshowと同じでwaitKeyを入れないと画像が出ません.

行番号を入れて色を少し変えてみた結果

console_screenshot_05.12.2018.png

class ConsoleImage
{
private:
    int count;
    std::string windowName;
    std::vector<std::string> strings;
    bool isLineNumber;
    int fontSize;
    int lineSpaceSize;
public:
    void setFontSize(int size);
    void setLineSpaceSize(int size);

    void setIsLineNumber(bool isLine = true);
    bool getIsLineNumber();
    cv::Mat image;

    void init(cv::Size size, std::string wname);
    ConsoleImage();
    ConsoleImage(cv::Size size, std::string wname = "console");
    ~ConsoleImage();

    void printData();
    void clear();

    void operator()(std::string str);
    void operator()(const char *format, ...);
    void operator()(cv::Scalar color, std::string str);
    void operator()(cv::Scalar color, const char *format, ...);

    void show(bool isClear = true);
};

void ConsoleImage::setFontSize(int size)
{
    fontSize = size;
}

void ConsoleImage::setLineSpaceSize(int size)
{
    lineSpaceSize = size;
}

void ConsoleImage::init(Size size, string wname)
{
    isLineNumber = false;
    windowName = wname;
    image = Mat::zeros(size, CV_8UC3);
    fontSize = 20;
    lineSpaceSize = 5;
    clear();
    namedWindow(windowName);
}

ConsoleImage::ConsoleImage()
{
    init(Size(640, 480), "console");
}

ConsoleImage::ConsoleImage(Size size, string wname)
{
    init(size, wname);
}

ConsoleImage::~ConsoleImage()
{
    destroyWindow(windowName);
    printData();
}

void ConsoleImage::setIsLineNumber(bool isLine)
{
    isLineNumber = isLine;
}

bool ConsoleImage::getIsLineNumber()
{
    return isLineNumber;
}

void ConsoleImage::printData()
{
    for (int i = 0; i < (int)strings.size(); i++)
    {
        cout << strings[i] << endl;
    }
}

void ConsoleImage::clear()
{
    count = 0;
    image.setTo(0);
    strings.clear();
}

void ConsoleImage::show(bool isClear)
{
    imshow(windowName, image);
    if (isClear)clear();
}

void ConsoleImage::operator()(string src)
{
    this->operator()(Scalar(255, 255, 255), src);
}

void ConsoleImage::operator()(const char *format, ...)
{
    char buff[255];

    va_list ap;
    va_start(ap, format);
    vsprintf(buff, format, ap);
    va_end(ap);

    string a = buff;

    this->operator()(Scalar(255, 255, 255), a);
}

void ConsoleImage::operator()(cv::Scalar color, const char *format, ...)
{
    char buff[255];

    va_list ap;
    va_start(ap, format);
    vsprintf(buff, format, ap);
    va_end(ap);

    string a = buff;

    this->operator()(color, a);
}

void ConsoleImage::operator()(cv::Scalar color, string src)
{
if (isLineNumber)strings.push_back(format("%2d ", count) + src);
        else strings.push_back(src);

        int skip = fontSize + lineSpaceSize;
        cv::addText(image, strings[count], Point(skip, skip + count * skip), "Consolas", fontSize, color);
        //cv::putText(image, strings[count], Point(skip, skip + count * skip), CV_FONT_HERSHEY_COMPLEX_SMALL, 1.0, color, 1);

        count++;
}

void consoleTest()
{
    ConsoleImage ci(Size(640, 480));
    CalcTime t;//自作タイマー関数
    int count = 0;
    while (1)
    {
        Mat src;
        Mat dest;
        cout << count << endl;
        cout << "===========================" << endl;
        ci("%d", count);
        ci("===========================");
        {
            CalcTime t("image input", TIME_MSEC);
            src = imread("img/lenna.png");
            ci("image input     : %f ms", t.getTime());
        }
        {
            CalcTime t("box filter", TIME_MSEC);
            boxFilter(src, dest, src.depth(), Size(7, 7));
            ci("box filter      : %f ms", t.getTime());
        }
        {
            CalcTime t("gauss filter", TIME_MSEC);
            GaussianBlur(dest, dest, Size(7, 7), 2);
            ci("gauss filter    : %f ms", t.getTime());
        }
        {
            CalcTime t("median filter", TIME_MSEC);
            medianBlur(dest, dest, 5);
            ci("median filter   : %f ms", t.getTime());
        }
        {
            CalcTime t("bilateralfilter", TIME_MSEC);
            Mat temp = dest.clone();
            bilateralFilter(temp, dest, 7, 2, 2);
            ci("bilateral filter: %f ms", t.getTime());
        }
        cout << endl;
        ci.show();
        imshow("out", dest);
        waitKey(1);
        count++;
    }
}

おわりに

だれかこのコードを"<<"のストリームに拡張してくれるとcoutっぽくなって嬉しいです.
次は,oyngtmhrさんの「オセロの勝敗判定を作ってみる」です!

あとこのGIF動画を作るためにScreenToGifっていうツールを使いました.大変素晴らしいです.

fukushima1981
名古屋工業大学 准教授.博士(工学).専門は画像処理,映像符号化,並列プログラミング,3次元画像処理,コンピュータビジョン.
http://fukushima.web.nitech.ac.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした