ページ 11

cv::Opencvのimshow(...)の直後にcv::waitKey(0)

Posted: 2017年4月07日(金) 13:04
by へえ?
cv::Opencvのimshow(...)の直後にcv::waitKey(0)を置かなければ、
画像が表示してくれない!

どうしてでしょうか。
Opencvのお決まり?

Re: cv::Opencvのimshow(...)の直後にcv::waitKey(0)

Posted: 2017年4月07日(金) 13:30
by usao
あなたがイベント処理を行っていないからです.
リファレンスのwaitKeyのところに書いてあります.

This function is the only method in HighGUI that can fetch and handle events, so it needs to be called
periodically for normal event processing unless HighGUI is used within an environment that takes care of event
processing.

Re: cv::Opencvのimshow(...)の直後にcv::waitKey(0)

Posted: 2017年4月07日(金) 14:55
by へえ?
usaoさん

さっそくお返答ありがとうございます。
申し訳ございませんが、その英語の意味が不明です。
英語そのものというより、技術的な表現に対する不理解です!

私は開発環境Visual studioの環境でデバッグ方式で逐行実行していくのですが、
それの影響かな。

Re: cv::Opencvのimshow(...)の直後にcv::waitKey(0)

Posted: 2017年4月07日(金) 17:16
by usao
私には正確な説明ができないので,イベントドリブンとかウィンドウプロシージャとか,なんかそんな単語で検索してください.
(あるいは,正確に説明してくれる人の登場に期待する)


以下,ざっくりとしたイメージですが…

(1)imshow()は,「画像表示したいんだけど?」と言う.
(2)どこか別の箇所が,そう言われたことを受けて,表示のために必要な何らかの処理を行う.

という手順で表示が成されるのだけど,
あなたのプログラムでimshow()だけ書いた場合には,(2)に相当する部分が無いから,表示がされないということです.
で,waitKey()は,(2)に相当する仕事をしてくれるので,
imshow()→waitKey()の順で呼ぶことで,表示が成されるわけです.

英文にあるように,(2)に相当する処理がプログラムの中に既に存在する場合には
waitKey()を使わなくても良いです.

Re: cv::Opencvのimshow(...)の直後にcv::waitKey(0)

Posted: 2017年4月15日(土) 18:40
by へえ?
お返答ありがとうございました。

いろいろ模索によって、規律性的な事がわかりました。
MS visual studio 2015のdebug状態で逐文実行する場合、
wait()がなければ、だめで、wait()を実行してから、
OpenCVによる画面に出されているimage表示windowをモウスでアクティブさせて、

何かキーを押せば、imageがきちんとOpenCVが出してくれたwindowに表示できて、
そして、次のC言語のセンテンスに行きます。

このような動きを論理的・理論的に解釈できる方、いらっしゃらないでしょうか。

Re: cv::Opencvのimshow(...)の直後にcv::waitKey(0)

Posted: 2017年4月16日(日) 10:28
by みえ
usao さんの説明をスルーしてしまうより、どの部分が分からなかったのかを書いていただいた方が建設的な議論ができると思いますが・・。

さて、質問者さんがどういうコードを書いているのか分からないので、以下のコードで説明します。

コード:

#include <opencv2/highgui.hpp>

int main(int argc, char *argv[]) {
  if (argc >= 2) {
    auto image = cv::imread(argv[1], cv::IMREAD_COLOR);
    cv::namedWindow("Display window", cv::WINDOW_AUTOSIZE);
    cv::imshow("Display window", image);
    cv::waitKey(0);
  }
  return 0;
}
cv::namedWindow を呼ぶと、空のウィンドウを作って、それを表示します。cv::imshow は、ウィンドウ上のカスタム コントロールに画像のビットマップデータを紐づけますが、実際の描画は行ないません。

したがって、上記プログラムで waitKey を呼び出す処理がない場合、空のウィンドウが一瞬だけ表示されて、そのままプログラムが終了してしまいます。

cv::waitKey は、ユーザーからのキー入力を取得する関数ですが、実際にはメッセージループの役割を持っています。

ウィンドウ ベースのプログラムの場合、ウィンドウを表示してから、何らかの方法でマウス入力やキー入力などを待ちながら、プログラムを実行し続けなければなりません。そのための仕組みがメッセージループです。
(入力を受け付けないループが、いわゆるフリーズだとかハングアップと言われる現象です。)

ループの部分は、OpenCV のコードを実際に見た方が理解が早いかもしれません。Windows で cv::waitKey を呼ぶと、以下の cvWaitKey が実行されます。

https://github.com/opencv/opencv/blob/m ... .cpp#L1920

見て分かるように二重の for ループがあります。内側は、複数のウィンドウを順に処理するためのループで、外側がメッセージ ループです。

最初の方で、GetMessage という API を使って、ウィンドウに送られてきたメッセージを読み出します。

キー入力を受け取ったときにキーの種類を返すため、GetMessage で受け取ったメッセージが条件 (メッセージの種類が WM_KEYDOWN、WM_SYSKEYDOWN などなど) を満たしている場合、ループから抜けるようになっているのが分かると思います。条件を満たさないメッセージの場合は、DispatchMessage を使って、メッセージをウィンドウプロシージャに送り、ウィンドウの既定の動作をするようにしています。

cv::imshow は、データを紐づけるだけで描画は行ないませんでした。描画が行われるタイミングは、カスタム コントロールに WM_PAINT メッセージが送られてきたときです。描画の処理は、同じファイルの HighGUIProc という関数にあります。

https://github.com/opencv/opencv/blob/m ... .cpp#L1629

したがって画像を表示するためには、少なくとも一回は WM_PAINT を処理する必要があり、これが、cv::waitKey を呼び出さないと画像が表示されない理由でもあります。
へえ? さんが書きました: MS visual studio 2015のdebug状態で逐文実行する場合、
wait()がなければ、だめで、wait()を実行してから、
OpenCVによる画面に出されているimage表示windowをモウスでアクティブさせて、

何かキーを押せば、imageがきちんとOpenCVが出してくれたwindowに表示できて、
そして、次のC言語のセンテンスに行きます。

このような動きを論理的・理論的に解釈できる方、いらっしゃらないでしょうか。
端的に回答するならば、cv::waitKey がそのように実装されているから、になるでしょうか。「そのように」とは、メッセージループを実行してウィンドウに送られてくるメッセージを順次処理しながら、もしキー入力があれば、ループを抜けて関数が戻り値を返すように、という意味です。

画像表示についても繰り返しになりますが、ループが処理するメッセージには WM_PAINT も含まれており、OpenCV は、このメッセージを処理するときに初めて画像を描画するので、cv::waitKey を実行しないと画像が見えません。

余談ですが、例えば以下のようにループを自分で書いて画像を表示することはできます。

コード:

#include <opencv2/highgui.hpp>
#include <windows.h>

int main(int argc, char *argv[]) {
  if (argc >= 2) {
    auto image = cv::imread(argv[1], cv::IMREAD_COLOR);
    cv::namedWindow("Display window", cv::WINDOW_AUTOSIZE);
    cv::imshow("Display window", image);

    MSG msg = {};
    while (GetMessage(&msg, nullptr, 0, 0) > 0) {
      if (msg.message == WM_KEYDOWN && msg.wParam == VK_ESCAPE) {
        break;
      }
      TranslateMessage(&msg);
      DispatchMessage(&msg);
    }
  }
  return 0;
}
ただし上記例では、ウィンドウの右上の×ボタンがクリックされたことをメッセージから判断できず、キー入力以外ではプログラムが終了しません。

OpenCV 内部では、作成されたウィンドウをリンクトリストとしてグローバル変数の hg_windows で管理しているみたいですが、外部からアクセスする真っ当な方法がないので、真面目にやるなら何か別の方法を見つけないといけません。

Re: cv::Opencvのimshow(...)の直後にcv::waitKey(0)

Posted: 2017年4月18日(火) 10:37
by へえ?
みえ様
大変丁寧なご解説本当に感激です。
自分が理解できなかった本質的な原因はまさに下記のような事ですね。

> cv::imshow は、ウィンドウ上のカスタム コントロールに画像のビットマップデータを紐づけますが、実際の描画は行ないません。

>cv::imshow は、データを紐づけるだけで描画は行ないませんでした。描画が行われるタイミングは、カスタム コントロールに WM_PAINT メッセージが送られてきたときです。

>ループが処理するメッセージには WM_PAINT も含まれており、OpenCV は、このメッセージを処理するときに初めて画像を描画するので、cv::waitKey を実行しないと画像が見えません。

OpenCVはなんでこんなやりかたにされているのでしょうか。
非常に非直感で、通常の他のGUI LIBと違います。
OpenCVのimshow(...)はMATLABのimshow(...)を真似て作られたと思いますが、
MATLABのimshow(...)は次にwait文がなければならないという話はありませんし。

それから、OpenCVの場合、
imshow(...)はwait()と関連付けて、wait()はWM_PAINT に関連付けて、
imshow(...)はwait()を経由して初めて、WM_PAINTメッセージをもらって、描画します。
というような理解で宜しいでしょうか。

でも、現にOpencvが出しているwindowをアクティブにして、どんなキーを押しても描画してくれるから、なんでwait()がWM_PAINT に関連付けられるのでしょうか。

またどうぞ宜しくお願いします

Re: cv::Opencvのimshow(...)の直後にcv::waitKey(0)

Posted: 2017年4月18日(火) 10:37
by へえ?
みえ様
大変丁寧なご解説本当に感激です。
自分が理解できなかった本質的な原因はまさに下記のような事ですね。

> cv::imshow は、ウィンドウ上のカスタム コントロールに画像のビットマップデータを紐づけますが、実際の描画は行ないません。

>cv::imshow は、データを紐づけるだけで描画は行ないませんでした。描画が行われるタイミングは、カスタム コントロールに WM_PAINT メッセージが送られてきたときです。

>ループが処理するメッセージには WM_PAINT も含まれており、OpenCV は、このメッセージを処理するときに初めて画像を描画するので、cv::waitKey を実行しないと画像が見えません。

OpenCVはなんでこんなやりかたにされているのでしょうか。
非常に非直感で、通常の他のGUI LIBと違います。
OpenCVのimshow(...)はMATLABのimshow(...)を真似て作られたと思いますが、
MATLABのimshow(...)は次にwait文がなければならないという話はありませんし。

それから、OpenCVの場合、
imshow(...)はwait()と関連付けて、wait()はWM_PAINT に関連付けて、
imshow(...)はwait()を経由して初めて、WM_PAINTメッセージをもらって、描画します。
というような理解で宜しいでしょうか。

でも、現にOpencvが出しているwindowをアクティブにして、どんなキーを押しても描画してくれるから、なんでwait()がWM_PAINT に関連付けられるのでしょうか。

またどうぞ宜しくお願いします

Re: cv::Opencvのimshow(...)の直後にcv::waitKey(0)

Posted: 2017年4月18日(火) 12:56
by へえ?
二重投稿してしまって、ごめんなさい!

追伸として、自分がみえ様にご紹介いただいたwait()関数に関するソースコードを見ましたが、やはり、wait()とWM_PAINT の関連性が見いだせなかったのです。

Re: cv::Opencvのimshow(...)の直後にcv::waitKey(0)

Posted: 2017年4月18日(火) 14:03
by usao
>OpenCVはなんでこんなやりかたにされているのでしょうか。

正確な理由が知りたいなら,OpenCVを作った人に訊くしかないのでは… とか思いますが.

「再描画が必要だよ」というOSからのメッセージに対応して画像を再描画する必要がある
→ そのためには,メッセージを処理する部分(メッセージループ)が必要
→ ユーザのプログラムにメッセージ処理部が無い場合に備えて,どこかにメッセージ処理部を仕込まないとなぁ… じゃあどこに?
→ OpenCVには,メッセージ処理が必要な事柄を扱う関数は cv::waitKey() くらいしかないから,仕込み場所としてはコレになる

という感じでは?

Re: cv::Opencvのimshow(...)の直後にcv::waitKey(0)

Posted: 2017年4月19日(水) 10:14
by みえ
へえ? さんが書きました:OpenCVはなんでこんなやりかたにされているのでしょうか。
非常に非直感で、通常の他のGUI LIBと違います。
OpenCVのimshow(...)はMATLABのimshow(...)を真似て作られたと思いますが、
MATLABのimshow(...)は次にwait文がなければならないという話はありませんし。
MATLAB は使ったことがないので分かりませんが、OpenCV を使う側のプログラムがメッセージ ループやウィンドウ プロシージャを意識せずに済むようにウィンドウの表示を実装するなら、今のような形にならざるを得ないですし、ウィンドウ ベースのアプリケーションとしては自然な実装だと思います。

別実装の一例として、OpenCV をマルチスレッド化し、メイン スレッドとウィンドウを表示するスレッドを分ければ、imshow がウィンドウを表示してすぐに制御を返しつつも、ウィンドウはそのまま表示され続けるようにできるかもしれません。

usao さんも書かれていますが、どのような実装にしろ、OS における GUI の仕組み上、OpenCV か OpenCV の呼び出し側のいずれかがメッセージ ループ(及びウィンドウ プロシージャ)を実装しないといけないことに変わりはありません。
へえ? さんが書きました:それから、OpenCVの場合、
imshow(...)はwait()と関連付けて、wait()はWM_PAINT に関連付けて、
imshow(...)はwait()を経由して初めて、WM_PAINTメッセージをもらって、描画します。
というような理解で宜しいでしょうか。
imshow と waitKey との間に関連はありません。imshow はウィンドウにデータを転送する一回きりの処理で、そのウィンドウが後でどうなるか、例えば waitKey が呼ばれるかどうか、などは全く気にしません。

一方 waitKey と WM_PAINT には関連性があり、概ねへえ? さんの理解で正しいと思います。

waitKey は、作成済みのウィンドウに対する全てのメッセージをループで随時受け取って、各コントロールにディスパッチしています。WM_PAINT の処理が書かれている HighGUIProc 関数は、namedWindow が作成したウィンドウ上のカスタム コントロールのウィンドウ プロシージャ (= メッセージハンドラ) であり、waitKey がディスパッチしたメッセージの受け取り先の一つです。
へえ? さんが書きました:でも、現にOpencvが出しているwindowをアクティブにして、どんなキーを押しても描画してくれるから、
なんでwait()がWM_PAINT に関連付けられるのでしょうか。
キー入力と描画との間に関連性はありません。waitKey は例外なく全てのイベントを処理しています(処理と言っても、キー入力以外は、受け取ったものをディスパッチするだけの簡単な作業ですが)。これは、ウィンドウが既定通りに動くためには必要な作業です。

もしかすると、「なぜ WM_PAINT なのか」そして「WM_PAINT とは何なのか」という疑問が根底にあるのかもしれません。

仮に WM_PAINT を使わないとして、ウィンドウを最初に表示するときに画像を直接描画することはできます。しかし、その一回だけの描画では期待通りの動作になりません。それは再描画がされないからです。

ウィンドウを最初に表示した後、そのウィンドウが他アプリケーションのウィンドウによって隠れてしまった場合などで、描画した部分が消えてしまい、再描画が必要になることがあります。しかし一回きりの描画だとそれが行われません。再描画のタイミングと、再描画すべき領域は OS が管理しており、それを教えてくれるのが WM_PAINT です。

WM_PAINT message (Windows)
https://msdn.microsoft.com/en-us/librar ... s.85).aspx
The WM_PAINT message is sent when the system or another application makes a request to paint a portion of an application's window. The message is sent when the UpdateWindow or RedrawWindow function is called, or by the DispatchMessage function when the application obtains a WM_PAINT message by using the GetMessage or PeekMessage function.
OpenCV が WM_PAINT で画像を表示することで、ウィンドウが隠れてしまっても、再び同じ画像で再描画することが可能になります。もし再描画のタイミングの前に imshow を呼び出してビットマップ データを更新すれば、次の再描画の時には更新された画像を表示することができます。

長文になりすみませんが、以上の説明でいかがでしょうか。

Re: cv::Opencvのimshow(...)の直後にcv::waitKey(0)

Posted: 2017年4月24日(月) 16:40
by へえ?
みえ様をはじめ皆さんに非常に感謝します。

論理上または理論上の話が分かったような気がします。

ただ、私の質問や疑問に皆さんが誤解されているかどうかを確かめるために、
もう一度、質問をさせてください。

OpenCV以外のLIBや言語では、
imshowのような関数は画像ファイル名を渡せば、実行してから、すぐ画面に
指定した画像ファイルの内容を表示してくれます。

① OpenCVの場合は、そうでなくて、imshowを実行しても、単にwindowを出してくれて、
指定した画像ファイルの表示はしない。。。。これでOpenCV流的に正常なのでしょうか。

② imshowの後ろにwait文があって、上記windowにクリックして、
(そのwait関数に対して、)なんらかのキーを押して、初めて画像を表示してくれます。

③ 要はimshowの後ろにwait文がなければ、画像を表示してくれないのです。
だから、imshowがwaitと関連付けられているのではと感じます。

上記ロジックはOpenCVにおいて正しいでしょうか、どうか、

大変お手数ですが、
再度ご教授お願い致します。

Re: cv::Opencvのimshow(...)の直後にcv::waitKey(0)

Posted: 2017年4月24日(月) 17:54
by usao
>私の質問や疑問に皆さんが誤解されているかどうかを確かめるために

何をおっしゃりたいのかわかりません.
これだと 「誤解」しているのは私達答えている側 という意味の文面だと見えてしまうのですが,
そういうことで合っていますか?


>再度ご教授お願い致します。

全く同じ説明の繰り返しにしかならないと思いますが…


>OpenCV以外のLIBや言語では、
>imshowのような関数は画像ファイル名を渡せば、実行してから、すぐ画面に
>指定した画像ファイルの内容を表示してくれます。

何を持って 「すぐ」 なのかわかりませんけども,そういうのが欲しいのであれば,例えば

コード:

void my_imshow(const string& winname, InputArray image)
{
  imshow( winname, image );
  waitKey(1);
}
とでもしておけば,とりあえず似たようなことになりませんか.


>①
正常だと思います.

>②
キーの押下は画像表示とは無関係だと既に説明されています.
(というか, クリックだのキー押下だのをしないと画像が表示されないのかどうか くらいのことは
 実際にご自身で試してみれば良いのでは?)

>③
・waitKeyが必要な場合とそうでない場合とがある,ということが既に説明されています.
・inshowとwaitとの関係性を「関連付られている」と表現するか否かについては知りません.

Re: cv::Opencvのimshow(...)の直後にcv::waitKey(0)

Posted: 2018年3月12日(月) 09:36
by 通りすがりT
> OpenCV以外のLIBや言語では、
> imshowのような関数は画像ファイル名を渡せば、実行してから、すぐ画面に
> 指定した画像ファイルの内容を表示してくれます。
> ③ 要はimshowの後ろにwait文がなければ、画像を表示してくれないのです。

計算がメインで表示がメインではないようなライブラリでは、描画処理に詳しくないプログラマでも分かりやすいように、描画指示を行うと即座に表示されているようにしてくれています。
しかし、実際は描画指示のあと、メッセージループの処理(OSとのメッセージをやりとりするためのwait処理)を行ってくれています。
例えば、WM_PAINTメッセージが来る度に再描画を行う、など。これを行わないと、特定の操作をしたあと、画面が真っ白になってしまうとか、変な動作がどこかが起こります。
つまり、ライブラリが描画関連の処理を隠蔽して、分かりやすくしてくれています。

描画処理を表面的にしか触らないならそれでいいのですが、OpenCVの場合、ある程度厳密に行う必要があるシーンがあります。
様々なケースがあるので一概には言えませんが、処理途中で描画されてしまうのは困るとか、再描画しなくていいタイミングで描画してしまうとか、余計な描画処理を行って処理が重くなるとか、いろいろあると思います。
そのため、wait処理を呼び出したタイミングだけ、メッセージループ処理を行うようになっているのではと推測です。