cv2を含んだcx_freezeの実行ファイル生成について

タグ:

cx_freezeの対応状況にもよるのですが、以下の環境で生成された実行ファイルが正常に起動しない状況に遭遇しました。

  • Python 3.8.12
  • cx-freeze 6.8.1
  • opencv-python-headless 4.5.4.58

cv2を利用したPythonコードが起動しない原因

直接の原因はcv2の初期化コードにあります。

cx_freezeはビルド過程において、必要なモジュールを単に複製するのではなくpycファイル化して格納するのですが、cv2は内部で config-3.8.py を読み込もうとしている箇所があり失敗します。

失敗している例

Traceback (most recent call last):
  File "/Users/********/.pyenv/versions/3.8.12/lib/python3.8/site-packages/cx_Freeze/initscripts/__startup__.py", line 113, in run
    module_init.run(name + "__main__")
  File "/Users/********/.pyenv/versions/3.8.12/lib/python3.8/site-packages/cx_Freeze/initscripts/Console.py", line 15, in run
    exec(code, module_main.__dict__)
  File "ilwork.py", line 8, in <module>
  File "<frozen zipimport>", line 259, in load_module
  File "/Users/********/Documents/workarea/GitHub/ilwork/src/image_view.py", line 8, in <module>
    import image_data
  File "<frozen zipimport>", line 259, in load_module
  File "/Users/********/Documents/workarea/GitHub/ilwork/src/image_data.py", line 7, in <module>
    import cv2
  File "/Users/********/.pyenv/versions/3.8.12/lib/python3.8/site-packages/cv2/__init__.py", line 180, in <module>
    bootstrap()
  File "/Users/********/.pyenv/versions/3.8.12/lib/python3.8/site-packages/cv2/__init__.py", line 110, in bootstrap
    load_first_config(['config.py'], True)
  File "/Users/********/.pyenv/versions/3.8.12/lib/python3.8/site-packages/cv2/__init__.py", line 108, in load_first_config
    raise ImportError('OpenCV loader: missing configuration file: {}. Check OpenCV installation.'.format(fnames))
ImportError: OpenCV loader: missing configuration file: ['config.py']. Check OpenCV installation.

これを回避するにはsetup.pyの完了後にcv2モジュールを手動でコピーしてやります。

自分は以下の様なコードを作成して複製を行いました。

import platform
import sys
import os
import shutil
import cv2
import nnabla
import nnabla_ext


def copy_module(src_path: str, dst_path: str, module_name: str):
    try:
        shutil.rmtree(os.path.join(dst_path, module_name))
    except FileNotFoundError:
        pass
    shutil.copytree(src_path, os.path.join(dst_path, module_name))


def main():

    if platform.system() == "Darwin":
        os_version, _, os_machine = platform.mac_ver()
        dst_path = "build/exe.macosx-{:s}-{:s}-{:d}.{:d}/lib".format(
            os_version, os_machine, sys.version_info.major, sys.version_info.minor
        )
    elif platform.system() == "Windows":
        dst_path = "build/exe.win-{:s}-{:d}.{:d}/lib".format(
            platform.machine().lower(), sys.version_info.major, sys.version_info.minor
        )
    else:
        return

    copy_module(cv2.__path__[0], dst_path, "cv2")
    copy_module(nnabla.__path__[0], dst_path, "nnabla")
    copy_module(nnabla_ext.__path__[0], dst_path, "nnabla_ext")


if __name__ == "__main__":
    main()