まメモ

みむ

リンカの気持ちになってOpenCVのメジャーバージョン変更と戦った話

今年の抱負が "アウトプットを増やす" に決定したので早速(1月も終盤)書きます。

背景

現在、諸事情でUbuntu14.04LTS上で作業をしているのですが、当然のように最新のパッケージは降ってきません。 OpenCVも2.4.8がリポジトリ上の最新となっています。 そんな中で3系で新しく導入された、魚眼レンズのカメラキャリブレーションに関する関数が使いたかったので手元でビルドしました。 この時点でかなり疲弊していて、「prefixが/usr/localになってるけど、今日は取りあえず動くか確認して明日整理すればええやろ、インストールや!」などと思ったのが問題のそもそもの発端でした。 無事、魚眼レンズのキャリブレーションを終え、翌日に面倒事を押しつけることに成功し満足げな表情でこの日は作業を終えました。

翌日、また別の作業でビルドを走らせると何やらエラーがでています。 見たところOpenCV周りのエラーっぽかったので心当たりありまくりで、取りあえず3系のOpenCVを消してビルドし直しました。 しかし、エラーは消えません。undefined reference だそうです。 こういった類のエラーが出る時は、大抵ライブラリの指定忘れなのですが、cmakeを使ってこれまで同じようにビルドしてきたのに、今さらこんなエラーが出るのも変な話です。 そんな阿呆なと思いながら、コンパイルコマンドをダンプさせてじっくり眺めてみても、やっぱりきちんと2系のライブラリが漏れなく指定されています。 更に気になる点として、OpenCVに関係する関数が全体的にundefined referenceならよくある話なのですが、今回はごく一部だけがシンボル解決に失敗しているようでした。

ライブラリに含まれるシンボルの調査

というわけで、本当に指定したライブラリに今探しているシンボルがないのか、逆にどんなシンボルならあるのか(型が合ってない可能性)を調べることにしました。 readelf コマンドを使うことで、実行ファイルやライブラリなどのELFバイナリのメタデータを人間が読みやすい(?)形で得ることができます。 シンボルを出力するためにオプション-sを、途中で出力が打ち切られるのを防ぐために-Wを指定して、

readelf -s -W <ELFファイル>

とすると、何やら面妖な記号を含みながらそれっぽいのがズラズラっと出力されると思います。*1

  (前略) _ZN2cv12findContoursERKNS_17_OutputArrayERKNS_12_OutputArrayEiiNS_6Point_IiEE

この記号はC++名前空間や多重定義を実現しながらユニークなシンボル名を作り出すために付加されていて、c++filtなどのコマンドを用いることで以下のようにデコードすることができます。

  (前略) cv::findContours(cv::_OutputArray const&, cv::_OutputArray const&, int, int, cv::Point_<int>)

問題解決

さて、実はさっきから出てきているこのcv::findContoursというのが、今回名前解決がされないと言われている関数の1つでした。 見たところ普通に存在しているようですが……エラー文と比較してみましょう。

undefined reference to `cv::findContours(cv::_InputOutputArray const&, cv::_OutputArray const&, cv::_OutputArray const&, int, int, cv::Point_<int>)'

第一引数がなんか微妙に違う……。 調べてみると、OpenCV2系までは _InputOutputArray_OutputArray の別名だったそうなのですが、3系でそれぞれ別の構造体となったようです。 つまり、ライブラリは2系なので _InputOutputArray は別名定義の解決が行われた _OutputArray としてシンボルが存在しており、リンクしようとしているバイナリの方は3系の残滓が残っている時にコンパイルしたために、独立した _InputOutputArrayとしてシンボルを登録していたようです。 というわけで、一旦バイナリを全てコンパイルし直したらすんなり問題解決しました。

結論

市原仁奈「リンカの気持ちになるですよー!」

*1:readelfも実は3系の結果で、第一引数の部分だけ話の都合で書き換えてあります。手元に2系のバイナリがなかったので……。