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

Python bindingのエラーを修正しながら仕組みを追う気の遠くなるような長い長い旅

More than 1 year has passed since last update.

はじめにのはじめに

本記事は超絶マニアックな誰得な作業ログです。読者を置いていくことが目標と言っても過言ではありません。記事のクオリティの低さたるや、格別です。

さて、pythonからOpenCVを使うというのは大分一般的になってきたと思います。ここで、多くの人は配布されているバイナリを使っていますが、contribや動画処理など、デフォルトでは入っていない機能を使おうとすると、ビルドが必要になります。

すると、大抵python bindingでコケて沼にはまります。コケた際に、OpenCVのcmakeファイルを読んで自力で解決している人は、非常に少ないのでしょうか。でも、人生、cmakeファイルを読まなくてはいけない時があるんです。本記事は、そんな時に、俺はこうやって解決した!という作業ログです。人それぞれエラーは違うので、そのまま適用できるケースは無いと思いますが、気合だけでも伝わればと思います。

ちなみにマニュアルを読めば早(ry

はじめに

よくこんな質問が寄せられます。
Q: OpenCVをビルドしたいのに、python bindingで失敗する

答えは、
A: cmake fileを読む
です。

失敗ケースとしては、condaを使ったら動かない、Macだと動かない・・・など色々ありますが、今回は各ケースに対する優しいケーススタディではなく、cmakeをガチムチで読むという話です。

最初のビルド

まずは成功画面、cmakeするとこんな感じでpython-bindingに成功します。

$ mkdir build
$ cd build
$ cmake ..

image.png

という事で、めでたし!・・という感じで記事を締めたいところですが、多くの場合はこんな感じで失敗します。

image.png

よくある原因は、conda内のpythonを使っていると、OpenCVがリンクを見つけられない事に起因します。こんな時の調査手順を載せます。なお、今回は、ワザと失敗させているため、色々未インストールのものとかも準備しておき、ログに出ても気づかなかった事にします

失敗表示されている場所

エラーメッセージでひとまずgitリポジトリ内を検索するとroot dierectory直下のCMakeLists.txtの最後の方にこんな記述が見られます。

CMakeList.txt
if(BUILD_opencv_python3)
status("")
status("  Python 3:")
status("    Interpreter:"     PYTHON3INTERP_FOUND  THEN "${PYTHON3_EXECUTABLE} (ver ${PYTHON3_VERSION_STRING})"       ELSE NO)
if(PYTHON3LIBS_VERSION_STRING)
  status("    Libraries:"   HAVE_opencv_python3  THEN  "${PYTHON3_LIBRARIES} (ver ${PYTHON3LIBS_VERSION_STRING})"   ELSE NO)
else()
  status("    Libraries:"   HAVE_opencv_python3  THEN  "${PYTHON3_LIBRARIES}"                                      ELSE NO)
endif()
status("    numpy:"         PYTHON3_NUMPY_INCLUDE_DIRS THEN "${PYTHON3_NUMPY_INCLUDE_DIRS} (ver ${PYTHON3_NUMPY_VERSION})" ELSE "NO (Python3 wrappers can not be generated)")
status("    packages path:" PYTHON3_EXECUTABLE         THEN "${PYTHON3_PACKAGES_PATH}"                                    ELSE "-")
endif()

ふむふむ、Librariesとnumpyが見つからないと言われているということは、HAVE_opencv_python3PYTHON3_NUMPY_INCLUDE_DIRSが空か、OFFになっているっぽいな、となります。

念のため、この付近で、上記2変数を表示させて見ましょう。

CMakeList.txt
if(BUILD_opencv_python3)
  status("HAVE_open_CV3????? ${HAVE_opencv_python3}")
  status("NUMPY dir????? ${PYTHON3_NUMPY_INCLUDE_DIRS}")
  status("")

とすると、

image.png

と表示されました。というわけで、HAVE_opencv_python3をONにするのと、PYTHON3_NUMPY_INCLUDE_DIRSにディレクトリ名を設定する旅に出ましょう。

注意点として、今回はcmakeファイルをいじりながらログを確認していきますが、前回時のcmakeのログが残ってしまうため、チェックの際はbuildディレクトリ以下は毎回全部消した方が確実です。

HAVE_opencv_python3をONにする旅

さて、また同じように、gitリポジトリ内を検索してみましょう。

image.png

 無い・・・・だと・・・・?

image.png

冗談です。頑張ります。手がかりを探すため、まずはCMakeLists.txtの中からpythonを検出している入り口を探しに行きます。すると、こんな記述が・・・!

CMakeList.txt
# --- Python Support ---
if(NOT IOS)
  include(cmake/OpenCVDetectPython.cmake)
endif()

さて、ここでPythonを探しているかを確認するため、この前後で、変数HAVE_opencv_python3PYTHON3_EXECUTABLEがどのように変化しているかを見てみましょう。

CMakeList.txt
if(NOT IOS)
status("HAVE_open_CV3????? ${HAVE_opencv_python3}") <- HAVE_opencv_python3を表示
status("exec????? ${PYTHON3_EXECUTABLE}") <- PYTHON3_EXECUTABLEを表示
include(cmake/OpenCVDetectPython.cmake)
status("HAVE_open_CV3????? ${HAVE_opencv_python3}")
status("exec????? ${PYTHON3_EXECUTABLE}")
endif()

image.png

上の????とあるところでは、exec側が何も設定されていませんが、下では/opt/conda/bin/python3/が設定されています。というわけで、ここで何かが起きていることは確認できます。なお、HAVE_opencv_python3はこの段階ではまだ何も入っておらず、この行より下で、何かのフラグに従ってON/OFFが決まる模様です。どこでHAVE_opencv_python3が生成されたかを引き続き探りましょう。と言ってもpythonの文字も特になく手がかりがありません。

各行に、messageを入れてログを確認しまくります。

message("1: HAVE_open_CV3????? ${HAVE_opencv_python3}")

こんな感じのコードを入れまくって、OFFに切り替わる瞬間を探します。・・・すると、以下の所でOFFフラグが生成されることがわかります。

CMakeList.txt
# OpenCV modules
message("321: HAVE_open_CV3????? ${HAVE_opencv_python3}")
add_subdirectory(modules)
message("322: HAVE_open_CV3????? ${HAVE_opencv_python3}")

一番上では、HAVE_open_CV3にはフラグが入っていませんが、一番下ではOFFが入力されています。

image.png

ということで、

add_subdirectory(modules)

がキーであることが分かります。まずはここを深掘って、HAVE_opencv_python3が切り替わる瞬間を探しましょう。modulesディレクトリ内のCMakeファイルを確認すると、内容はとてもシンプルで、

opencv/modules/CMakeLists.txt
add_definitions(-D__OPENCV_BUILD=1)

if(NOT OPENCV_MODULES_PATH)
  set(OPENCV_MODULES_PATH "${CMAKE_CURRENT_SOURCE_DIR}")
endif()

ocv_glob_modules(${OPENCV_MODULES_PATH} EXTRA ${OPENCV_EXTRA_MODULES_PATH})

となっています。ocv_glob_modulesが具体的に何かを処理しているようですね・・・ということで検索。・・・すると、cmake/OpenCVModule.cmakeの中で定義されているようです。中を軽く読んでみましょう。

長いけど、今回はHAVE_を探す旅なので、細かいところは飛ばしつつ、ファイル全体を見て行きましょう。とりあえず、ざっくりとHAVEの文字を探すと、たくさんヒットする。ここのどこかでHAVE_opencv_python3がON/OFFされる予感がします。さて、ocv_glob_modules関数を見てみると、大まかに、collect modules, __ocv_resolve_dependencies, create modulesから成っています。最初と最後は、add_subdirectoryで飛ばしているだけ、__ocv_resolve_dependenciesが怪しそうなので中を見てみましょう。

すると、すぐ見つかりました。

cmake/OpenCVModule.cmake
  foreach(m ${OPENCV_MODULES_DISABLED_USER})
    set(HAVE_${m} OFF CACHE INTERNAL "Module ${m} will not be built in current configuration")
  endforeach()
  foreach(m ${OPENCV_MODULES_BUILD})
    set(HAVE_${m} ON CACHE INTERNAL "Module ${m} will be built in current configuration")
  endforeach()

ここが、怪しい。ということで、お決まりのmessageチェックをしてみましょう。前後をさっきのmessageコマンドで挟む。

image.png

...人生は厳しい

ここに来た時点で、既にOFFが入っていた。もう少し前に戻ってみよう。最初のブロックのどこかがターゲットなので、ひたすらmessageを入れる。結果として、ここがターゲットであることが分かりました。

cmake/OpenCVModule.cmake
  message("4 HAVE_open_CV3 in ocv_resolve_dependencies ????? ${HAVE_opencv_python3}")
message("${__modpath} ${CMAKE_CURRENT_BINARY_DIR}/${mod}/.${mod}")
            add_subdirectory("${__modpath}" "${CMAKE_CURRENT_BINARY_DIR}/${mod}/.${mod}")
  message("5 HAVE_open_CV3 in ocv_resolve_dependencies ????? ${HAVE_opencv_python3}")

image.png

何をやっているか見てみましょう。

message("${__modpath} ${CMAKE_CURRENT_BINARY_DIR}/${mod}/.${mod}")

とすると・・・

image.png

みたいな感じで、/opencv/modules/pythonというディレクトリをaddすると、目的のフラグがON/OFFになるらしい。なお、後者の.pythonはadd_subdirectoryの定義上ここでは不要。というわけで、/opencv/modules/pythonを覗きましょう。

まず、/opencv/modules/pythonを見ると、CMakeLists.txtがいますので、ここの中に入って見ましょう。念のため、入り口と出口で目的のフラグが変化するかを確認するため、messageデバッグしましょう。ファイルの先頭と、最後でメッセージを表示させる。

image.png

というわけで、このどこかで目的のフラグがON/OFFされているらしいことが分かる。いつものようにmessageを出しまくって確認すると、

add_subdirectory(python3)

でフラグが変わることが分かる。さて、/opencv/modules/python/python3ディレクトリを漁って行きましょう。

/modules/python/python3/CMakeLists.txt
message("in /modules/python/python3/CMakeLists.txt 1 ????? ${HAVE_opencv_python3}")
if(NOT PYTHON3_INCLUDE_PATH OR NOT PYTHON3_NUMPY_INCLUDE_DIRS)
  ocv_module_disable(python3)
endif()
message("in /modules/python/python3/CMakeLists.txt 2 ????? ${HAVE_opencv_python3}")

set(the_description "The python3 bindings")
set(MODULE_NAME python3)
set(MODULE_INSTALL_SUBDIR python3)

set(PYTHON PYTHON3)

message("in /modules/python/python3/CMakeLists.txt 3 ????? ${HAVE_opencv_python3}")
include(../common.cmake)

message("in /modules/python/python3/CMakeLists.txt 4 ????? ${HAVE_opencv_python3}")
unset(MODULE_NAME)
unset(MODULE_INSTALL_SUBDIR)

message("in /modules/python/python3/CMakeLists.txt head ????? ${HAVE_opencv_python3}")
if(MSVC)
  ocv_warnings_disable(CMAKE_CXX_FLAGS /wd4996)
else()
  ocv_warnings_disable(CMAKE_CXX_FLAGS -Wdeprecated-declarations)
endif()
message("in /modules/python/python3/CMakeLists.txt head ????? ${HAVE_opencv_python3}")

こんな感じで、ところどころmessageを入れて、cmakeすると最初のmessageの時点で

image.png

と、一行表示されただけで他の行まで行きませんでした。なので、PYTHON3_INCLUDE_PATHPYTHON3_NUMPY_INCLUDE_DIRSが定義されていないと、ここで終了となるようです。

/modules/python/python3/CMakeLists.txt
if(NOT PYTHON3_INCLUDE_PATH OR NOT PYTHON3_NUMPY_INCLUDE_DIRS)
  ocv_module_disable(python3)
endif()

さて、フラグがセットされて、終了する様子を確認しましょう。ocv_module_disableを探して見てみると・・・

opencv/cmake/OpenCVModule.cmake
set(HAVE_${__modname} OFF CACHE INTERNAL "Module ${__modname} can not be built in current configuration")

という感じでHAVE_opencv_python3がOFFにされるのを確認できました。

さて、ここで目的は、PYTHON3_INCLUDE_PATHの設定と、PYTHON3_NUMPY_INCLUDE_DIRSの設定に変わります。これらの変数に何が入っているか、/modules/python/python3/CMakeLists.txtの先頭に以下を追加して確認すると・・・

/modules/python/python3/CMakeLists.txt
message("PYTHON3_INCLUDE_PATH ${PYTHON3_INCLUDE_PATH}")
message("PYTHON3_NUMPY_INCLUDE_DIRS ${PYTHON3_NUMPY_INCLUDE_DIR}")

image.png

てな感じで、NUMPYが未設定となっています。つまりNumpyを先に入れないと、HAVE_opencv_python3のチェックまで進めないことが分かります。

ちなみに、PYTHON3_INCLUDE_PATHが入っていない場合、apt-getなどでpythonを入れている場合は、python-devを入れれば、ここに値が入るはず。入らなければ、頑張って探しましょう!

進路変更、PYTHON3_NUMPY_INCLUDE_DIRを入れる旅に出る

さて、これについては、最初から最後まで何も設定されないので手掛かりがありません。とりあえず検索してみましょう。

image.png

なお、rootなのはDockerで作業しているからです。この中で、今まで通ってきた経路を選びましょう。怪しいのは、

cmake/OpenCVDetectPython.cmake
modules/python/common.cmake

でしょう。他のファイルは、これまでに確認済みで、NUMPYに設定するような記述はなかったはず。また、後者は、実はopencv/modules/python/python3/CMakeLists.txtの真ん中でincludeされており、まだ通っていない。という訳で、cmake/OpenCVDetectPython.cmakeを読みましょう。

さて、ザーッと読んでみると、こんな構成です。

opencv/cmake/OpenCVDetectPython.cmake
function(find_python preferred_version min_version library_env include_dir_env
         found executable version_string version_major version_minor
         libs_found libs_version_string libraries library debug_libraries
         debug_library include_path include_dir include_dir2 packages_path
         numpy_include_dirs numpy_version)
.
.
.
endfunction(find_python)

find_python(2.7 "${MIN_VER_PYTHON2}" PYTHON2_LIBRARY PYTHON2_INCLUDE_DIR
    PYTHON2INTERP_FOUND PYTHON2_EXECUTABLE PYTHON2_VERSION_STRING
    PYTHON2_VERSION_MAJOR PYTHON2_VERSION_MINOR PYTHON2LIBS_FOUND
    PYTHON2LIBS_VERSION_STRING PYTHON2_LIBRARIES PYTHON2_LIBRARY
    PYTHON2_DEBUG_LIBRARIES PYTHON2_LIBRARY_DEBUG PYTHON2_INCLUDE_PATH
    PYTHON2_INCLUDE_DIR PYTHON2_INCLUDE_DIR2 PYTHON2_PACKAGES_PATH
    PYTHON2_NUMPY_INCLUDE_DIRS PYTHON2_NUMPY_VERSION)

find_python(3.4 "${MIN_VER_PYTHON3}" PYTHON3_LIBRARY PYTHON3_INCLUDE_DIR
    PYTHON3INTERP_FOUND PYTHON3_EXECUTABLE PYTHON3_VERSION_STRING
    PYTHON3_VERSION_MAJOR PYTHON3_VERSION_MINOR PYTHON3LIBS_FOUND
    PYTHON3LIBS_VERSION_STRING PYTHON3_LIBRARIES PYTHON3_LIBRARY
    PYTHON3_DEBUG_LIBRARIES PYTHON3_LIBRARY_DEBUG PYTHON3_INCLUDE_PATH
    PYTHON3_INCLUDE_DIR PYTHON3_INCLUDE_DIR2 PYTHON3_PACKAGES_PATH
    PYTHON3_NUMPY_INCLUDE_DIRS PYTHON3_NUMPY_VERSION)

ここで、pythonを探してるらしいことはわかります。find_pythonの定義をよんでみると、

opencv/cmake/OpenCVDetectPython.cmake
#   numpy_include_dirs (variable): Output of found Python Numpy include dirs
#   numpy_version (variable): Output of found Python Numpy version

という記述が。という訳で、numpy_include_dirsに値がセットされているタイミング、条件を確認してみます。

まず、明らかに中間にある

opencv/cmake/OpenCVDetectPython.cmake
set(_numpy_include_dirs ${${numpy_include_dirs}})

が怪しいですね。_numpy_include_dirsを追っかけると、すぐ下に、

opencv/cmake/OpenCVDetectPython.cmake
execute_process(COMMAND "${_executable}" -c "import os; os.environ['DISTUTILS_USE_SDK']='1'; import numpy.distutils; print(os.pathsep.join(numpy.distutils.misc_util.get_numpy_include_dirs()))"
                          RESULT_VARIABLE _numpy_process
                          OUTPUT_VARIABLE _numpy_include_dirs
                          OUTPUT_STRIP_TRAILING_WHITESPACE)

という表記が!という訳で、ここにデバッグコードを入れて何をやっているか確認してみましょう。

message("${_executable} -c import os; os.environ['DISTUTILS_USE_SDK']='1'; import numpy.distutils; print(os.pathsep.join(numpy.distutils.misc_util.get_numpy_include_dirs()))")

というのを直前に入れると、

image.png

と出ました。ということで、numpyを呼び出そうとしたら、import errorが出ていることが確認できました。そこで、ようやくnumpyをインストールしてみます!(そんくらい先にやっとけというツッコミは、今回はそういう記事なので・・・ということで)

image.png

無事、エラーが消えました!さて、PYTHON3_NUMPY_INCLUDE_DIRがセットされたかを確認しましょう。一番最初のCMakeの画面に戻ると・・・・

image.png

numpyに値がセットされていました!!めでたし!さて、困ったことに、実はこれだけが原因だったらしく、pythonが認識されてしまいました。これだけだとnumpyインストールするだけかよ!となってしまいます。

ちなみに、結局HAVE_opencv_python3がどこでON/OFFになるか見つける前に動いてしまった。

が、ここまでは初心者コース、macに鞍替えしてもう一回やってみましょう

Macでやってみる。

めちゃめちゃ汚い環境と化している手元のMacでもう一度確認してみますと、この有様。何も見つからない、素晴らしいですね。

image.png

pythonはどこに行ったレベル。ちなみにnumpyもpythonも入っています。さて、もう一度同じ手順を踏む訳ですが、正直全部書くのが面倒になってきたので、message命令を入れまくって見つかった事にします

さて、先ほどのOpenCVDetectPython.cmakeで色々とチェックしているのがわかりましたので、ここを見ていきましょう。全部認識されていないので、頭から確認します。

cmake/OpenCVDetectPython.cmake
if(NOT ${found})
  message(WARNING, "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ${executable}")
  message(WARNING, "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ${${executable}}")
  ocv_check_environment_variables(${executable})

とすると

image.png

と表示されます。これは、executableには、PYTHON3_EXECUTABLEが入っており、ここに、何か値をセットすると、${${executable}}とすると、その値が実施されます。さて、PYTHON3_EXECUTABLEはどこでセットされるか。検索するとマニュアルがヒットし、

-# [optional] Building python. Set the following python parameters: 
- PYTHON2(3)_EXECUTABLE = <path to python> 
- PYTHON_INCLUDE_DIR = /usr/include/python<version> 
- PYTHON_INCLUDE_DIR2 = /usr/include/x86_64-linux-gnu/python<version> - PYTHON_LIBRARY = /usr/lib/x86_64-linux-gnu/libpython<version>.so 
- PYTHON2(3)_NUMPY_INCLUDE_DIRS = /usr/lib/python<version>/dist-packages/numpy/core/include/

と、cmakeコマンドを使う際に自分で入れろ、と書いてあります。

$ which python
/Users/peisuke/*************/python

として、自分のpythonのパスを調べて、cmake実施時に、コマンドに追加しましょう。

-D PYTHON3_EXECUTABLE=/Users/peisuke/*************/python

みたいに。すると・・・

image.png

として、実行スクリプトが認識されました!Summaryの所も、

image.png

ちょっと情報が増えました。次にいきましょう。SummaryからはInterpreterが発見されているけど、それ以外がないと言われているので下の方を見ていきましょう。すると、

cmake/OpenCVDetectPython.cmake
find_package(PythonLibs...)

みたいな記述があるので、その後ろに

      message(WARNING, "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ${PYTHON_LIBRARY}")
      message(WARNING, "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ${PYTHON_INCLUDE_DIR}")

を仕込んで見ると、

image.png

として、Includeディレクトリ、とLibが見つかっているのは確認できます。Libraryが見つかっているのに、Summaryに表示されないということは、先ほどと同様Numpyが原因と考えられます。先ほどはNumpyのインストールだけで認識されましたが、今回は既にNumpyは入っているのに見つからない。Mac+Conda+pyenvとかゴチャゴチャ入っていて、うまくincludeパスが見つかっていない模様です。なので、手動でnumpyのディレクトリを設定しましょう。

python -c "import numpy;print(numpy.get_include())"

とすると、ディレクトリが表示されるので、cmakeのパスに加えてみましょう。

-D PYTHON3_NUMPY_INCLUDE_DIRS=XXXXXXXXXX

結果は・・・

image.png

認識されました!

他にうまくいかないケースとしては、PYTHON_LIBRARYPYTHON_INCLUDE_DIRが見つからないケースです。無理やり設定してもいいですし、find_package(PythonLibs)が見つけられるように、cmakeがデフォルトで持っているFindPythonLib.cmakeに潜るのも一興ですね。少し大変ですが僕もたまにやります。問題を難しくしているのは、Numpyが見つからないだけなのに、PYTHON_LIBRARYが見つからないと怒られたりと、問題を分けられないことがしばしばある点です。その場合にも、この辺のコードを見て、どこが問題なのかをチェックすれば、いつでも解決できますね。

その他のケース

condaだと、PythonLibsが見つからない場合もよくある。python3.6を入れてるのであれば、例えば、

-D PYTHON_LIBRARY=/opt/conda/lib/libpython3.6m.so 
-D PYTHON_INCLUDE_DIR=/opt/conda/include/python3.6m/

をビルドオプションに入れると良かったりする。

おわりに

長くてダラダラした記事になってしまいましたが、個人的にpython bindingで失敗することは結構あるので、作業ログは取っておきたいなぁと思っており、ちょうど良い機会でした。多分ここまで読む人は世の中にいないと思いますが、これを機にpython bindingの達人を目指してみるのは如何でしょうか。

なお、この上級編として、OpenCV内のpython bindingのコードを切り貼りして、C++とpythonを繋げるマニアックな技を昨年のAdvent Calenderで公開しています(一年前はPython2.7を使っていたので、3系では動きませんが)。そちらも併せて読んでいただければと思います。では、よいお年を〜。

peisuke
abeja
「ディープラーニング」を活用し、多様な業界、シーンにおけるビジネスの効率化・自動化を促進するベンチャー企業です。
https://abejainc.com
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