はじめに
- この記事はOpenCV Advent Calendar 2017の2日目の記事です。他の記事は目次にまとめられています。
- またこの記事はBuild OpenCV from the sourceをもとにしたものであります
後日談というか、今回のオチ
- OpenCVのエラーは不親切なので、ステップイン実行で原因を確かめよう
ソースからビルド
- 世の中に、OpenCVをソースからビルドしたくない人がたくさんいることを、私は知っています。
- ちょろっとダウンロードしてビルドできればそれで十分で、できることならそうしたいと思っている人がたくさんいることも痛感しています。
- 私もその一人でした。
- しかしながら、OpenCVはソースからビルドすることに大きなメリットがあると信じています。
ちょっとした実行時エラー
- ちょっとした例で示しましょう。
- キャリブレーションのコードを書きたくなって、下記のようなコードを書いたとしましょう。
#include <opencv2/opencv.hpp>
#include <iostream>
#include "main.h"
int main()
{
using namespace cv;
Mat imagePoints, image;
Size patternSize;
bool isFound;
loadImage(image);
detectPionts(image, imagePoints, patternSize, isFound);
drawChessboardCorners(image, patternSize, imagePoints, isFound);
imshow("window", image);
waitKey(0);
destroyAllWindows();
return 0;
}
OpenCV Error: Assertion failed (nelems >= 0) in cv::drawChessboardCorners, file C:\Users\foo\work\opencv\modules\calib3d\src\calibinit.cpp, line 2092
- ともすると「なんか動かなかった」と匙を投げたくなるレベルのエラーメッセージですね。
ステップイン実行
- さて、Windowsユーザの皆様はVisual Studioのステップ実行機能をよく使われているかと思います。
- ブレークポイントを設定し、1行ずつ、あるいは関数内に入って、1行ずつプログラムを実行できます。
- 前述の実行時エラーの原因を特定するために、ステップ実行して見ましょう。
- おや?何故かOpenCVの関数内に入れませんね。
- これはソースコードからビルドせず、ビルド済みのライブラリを利用している場合におきます。
- 公式サイトからダウンロードできるビルド済みバイナリ(Windowsパック)には、リリースモード/デバッグビルド両方のバイナリが収録されています。
- しかし、ステップイン実行するためには、デバッグビルドのライブラリの他に、バイナリ中のアセンブラと、ソースコードの対応表たるpdbファイルが必要になります
- 皆さんが手元でデバッグビルドする際には、自動的にプロジェクト名.pdbというファイルが作成され、その中に対応表が格納されます1
- そしてpdbファイルの中では、ソースコードへのパスは絶対パスで埋め込まれているため、違うマシンでデバッグビルドしたくても、対応表は意味をなしません。
- 故に公式からダウンロードするビルド済みパッケージにはpdbファイルが含まれず、ステップイン実行ができません。
- とはいえ、このデバッグビルドを手元で再現するのは全く難しいことではありません。
- 単にOpenCVをソースからビルドしてやれば良いのです。
OpenCVのエラーメッセージ
- さて、OpenCVのエラーメッセージに戻りましょう。
- このエラーメッセージが、優しいものかといえば、残念ながら暗号レベルに難しいメッセージだと言う外ないでしょう。
- 英語を読み解けば、
nelem >= 0 の必要が有るよ!今回は違ったよ!
というエラーメッセージだとわかるのですが、「いや、そもそもnelem
ってなんだよ」という事になります。 - では、実際にビルドしてステップ実行してみましょう。
- デバッグ実行するためにはデバッグ版とちゃんとリンクする必要がありますが、ご心配なく、昨日書いた「Universal OpenCV プロジェクト」を参照頂くと、そのあたりをCMakeがハンドリングしてくれます。
OpenCVの関数にステップイン実行
- で、実際にステップイン実行すると、こんな感じにOpenCVのソースコードの中に入れます。
- 実際に目的の
cv::drawChessboardCorners
関数に到達するまえに、InputArray
クラス、InputOutputArray
クラス、そしてcv::Size
クラスのコンストラクタを通過します。 -
InputArray
/InputOutputArray
は、proxy classと呼ばれる、データの型の違いを吸収してくれるクラスです。OpenCV 2.3 から導入されました - ですので、目的の関数に潜り込むためには実際はそれぞれのクラスのコンストラクタにステップイン→ステップアウトを何度か繰り返す必要があります
- ショートカットキーは F11(ステップイン) と Shift+F11(ステップアウト) です。
- 初めて見ると、目まぐるしく画面が変わるので、ちょっと驚きますが、分かってしまえばなんてことはありません。
エラーメッセージに到達
calibinit.cpp
void cv::drawChessboardCorners( InputOutputArray _image, Size patternSize,
InputArray _corners,
bool patternWasFound )
{
CV_INSTRUMENT_REGION()
Mat corners = _corners.getMat();
if( corners.empty() )
return;
Mat image = _image.getMat(); CvMat c_image = image;
int nelems = corners.checkVector(2, CV_32F, true); // ここでnelems の値が決まる!!!!!
CV_Assert(nelems >= 0); // ここでnelemsに関するassertがある!!!!
cvDrawChessboardCorners( &c_image, patternSize, corners.ptr<CvPoint2D32f>(),
nelems, patternWasFound );
}
- すると、今回のエラー
nelem
が現れ、CV_Assert
で値のチェックが行われています - これを読み解くに、直前の
corners.checkVector
の返り値が負の値の場合、Assertionが発生するようです。 - これは画像の行/列/チャンネル数を読み解いて、何ビット幅の要素がいくつ含まれているかチェックする関数です。
- 今回の例で言えば、 画像座標を渡す必要があったので行列
corners
は、2xN
かNX2
の1チャネルもしくはNx1
か1xN
の2チャネルのいずれかの形式で、かつ各要素がfloatの型でデータを渡す必要がありました - で、checkVectorでは渡されたデータの数、幅、高さ、チャンネル数をもとに、指定した形式であるかどうかをチェックしているわけです。
- つまりは
OpenCV Error: Assertion failed (nelems >= 0)
というエラーメッセージは「入力データのサイズが間違っています」という日本語に翻訳できるわけですねじゃあそう書けよと、全CV研究者/エンジニアからの総ツッコミが入る - で、今回の大本のエラーはというと、渡した画像の座標郡のデータに読み込み失敗してるデータがあることに気づいていなかったというトホホな原因でした。
- というわけで、無事デバッグ終了
で、ソースコードからビルドした価値あったの?
- 私が是非知らせたいのは、OpenCVをステップイン実行することで難解なメッセージを読み解く必要がない、ということです。
- 今回のように
- OpenCVを使ったコーディング中は実行時エラーに遭遇することがある
- 残念ながらOpenCVのエラーメッセージは
不親切なので情報不足お世辞にもわかり易いとは言えない - エラーを特定するためにソースを追いかけるのはめっちゃ有効
- ということが言えるため、ぜひともOpenCVをソースコードからビルドして使いましょう、という提案です。
まとめ
- OpenCVをソースコードからビルドすることでステップイン実行する方法を紹介しました
- 筆者は以下の環境で執筆しました
項目 | バージョン |
---|---|
OS | Windows 7 Professional 64bit |
OpenCV | 3.3.0 |
コンパイラ | Visual Studio 2013 Update 5 |
CMake | 3.9.1 |
CUDA | 8.0 |
- 明日は本Advent Calendarの立ち上げ主でもあるhon_no_mushiさんの投稿で、執筆時点でのタイトルは「Tinker BoardのMali T764をOpenCV 3.3.1で酷使してみる」です!全ARMおじさん歓喜のTinkerBoardです!
補足
目grep でも良いんじゃね?
- もちろん、ソースコードからビルドしないでも目grepでソースコードを追跡することも可能です。
- しかし、OpenCVの中を覗いてみると、多くの関数は内部で
OpenCL
版/GPU(CUDA)
版/CPU
版と実装が多岐にわたるうえ、bit深度/チャネル数に応じて 実行時に呼び出す関数が決定される(=目grepほぼ不可能) な部分もあったりします。 - ですので、デバッグが徒労に終わらないためにも、是非ステップ実行を使ってエラー発生場所を突き止めて下さい。
Visual Studio限定じゃないの?
- いいえ、ステップ実行はVisual Studio限定の機能ではありません。
- Linuxで広く利用可能なgdbでも、CodeBlocks、CodeLite、KDevelop3などでも、数多くのIDEがちゃんとステップ実行をサポートしています。
-
ファイル名はVisual Studioのバージョンにより差異がある ↩