H.264対応のopencv-pythonのwheelをマルチステージビルドする
はじめに
先日、「opencv-pythonのwheelを並列ビルドしてビルド時間を5分の1にした」という記事を書きました。
H.264対応のopencv-python
パッケージを素早く手に入れたかったがゆえに書いた記事ですが、並列ビルドでビルド時間を短縮した後も約5分とそれなりの時間が掛かってしまいます。
Dockerイメージのビルドキャッシュが効かない状況(依存関係の変更など)が生じる度に5分待つのはしんどいので、マルチステージビルドを活用して、wheelのビルドを分離することにしました。
opencv-python-headless
パッケージ
ビルド済みH.264対応のopencv-python-headless
パッケージ(opencv-python
のヘッドレス版)をビルドする前に、比較対象としてH.264に対応していないビルド済みバイナリ(pip install opencv-python-headless
でインストールされるもの)を使って動作を確認しました。
FROM python:3.11-slim
WORKDIR /app
COPY ./requirements.txt ./
RUN \
python -m pip install --requirement requirements.txt
numpy==2.2.0
opencv-python-headless==4.10.0.84
import pathlib
import cv2
pathlib.Path("build_info.txt").write_text(cv2.getBuildInformation())
import sys
import cv2
import numpy as np
fourcc = sys.argv[1]
width, height = 640, 480
fps = 30
video_writer = cv2.VideoWriter(
f"{fourcc}.mp4", cv2.VideoWriter_fourcc(*fourcc), fps, (width, height)
)
for _ in range(30):
frame = np.random.randint(0, 255, (height, width, 3), dtype=np.uint8)
video_writer.write(frame)
video_writer.release()
上記のDocker環境、Pythonスクリプトを使って動作を確認してみます。
$ python build_info.py
$ python make_video.py mp4v
$ python make_video.py avc1
[ERROR:0@0.009] global cap_ffmpeg_impl.hpp:3133 open Could not find encoder for codec_id=27, error: Encoder not found
[ERROR:0@0.009] global cap_ffmpeg_impl.hpp:3211 open VIDEOIO/FFMPEG: Failed to initialize VideoWriter
コーデックにavc1
(H.264の別名)を指定したところ、エラーになってしまいました。まあ、想定通りですが。
出力されたbuild_info.txt
は以下の通りです。(長いので折りたたんでいます)
build_info.txtの内容
General configuration for OpenCV 4.10.0 =====================================
Version control: 4.10.0
Platform:
Timestamp: 2024-06-17T17:56:36Z
Host: Linux 5.15.0-1064-azure x86_64
CMake: 3.29.5
CMake generator: Unix Makefiles
CMake build tool: /bin/gmake
Configuration: Release
CPU/HW features:
Baseline: SSE SSE2 SSE3
requested: SSE3
Dispatched code generation: SSE4_1 SSE4_2 FP16 AVX AVX2 AVX512_SKX
requested: SSE4_1 SSE4_2 AVX FP16 AVX2 AVX512_SKX
SSE4_1 (16 files): + SSSE3 SSE4_1
SSE4_2 (1 files): + SSSE3 SSE4_1 POPCNT SSE4_2
FP16 (0 files): + SSSE3 SSE4_1 POPCNT SSE4_2 FP16 AVX
AVX (8 files): + SSSE3 SSE4_1 POPCNT SSE4_2 AVX
AVX2 (36 files): + SSSE3 SSE4_1 POPCNT SSE4_2 FP16 FMA3 AVX AVX2
AVX512_SKX (5 files): + SSSE3 SSE4_1 POPCNT SSE4_2 FP16 FMA3 AVX AVX2 AVX_512F AVX512_COMMON AVX512_SKX
C/C++:
Built as dynamic libs?: NO
C++ standard: 11
C++ Compiler: /opt/rh/devtoolset-10/root/usr/bin/c++ (ver 10.2.1)
C++ flags (Release): -Wl,-strip-all -fsigned-char -W -Wall -Wreturn-type -Wnon-virtual-dtor -Waddress -Wsequence-point -Wformat -Wformat-security -Wmissing-declarations -Wundef -Winit-self -Wpointer-arith -Wshadow -Wsign-promo -Wuninitialized -Wsuggest-override -Wno-delete-non-virtual-dtor -Wno-comment -Wimplicit-fallthrough=3 -Wno-strict-overflow -fdiagnostics-show-option -Wno-long-long -pthread -fomit-frame-pointer -ffunction-sections -fdata-sections -msse -msse2 -msse3 -fvisibility=hidden -fvisibility-inlines-hidden -O3 -DNDEBUG -DNDEBUG
C++ flags (Debug): -Wl,-strip-all -fsigned-char -W -Wall -Wreturn-type -Wnon-virtual-dtor -Waddress -Wsequence-point -Wformat -Wformat-security -Wmissing-declarations -Wundef -Winit-self -Wpointer-arith -Wshadow -Wsign-promo -Wuninitialized -Wsuggest-override -Wno-delete-non-virtual-dtor -Wno-comment -Wimplicit-fallthrough=3 -Wno-strict-overflow -fdiagnostics-show-option -Wno-long-long -pthread -fomit-frame-pointer -ffunction-sections -fdata-sections -msse -msse2 -msse3 -fvisibility=hidden -fvisibility-inlines-hidden -g -O0 -DDEBUG -D_DEBUG
C Compiler: /opt/rh/devtoolset-10/root/usr/bin/cc
C flags (Release): -Wl,-strip-all -fsigned-char -W -Wall -Wreturn-type -Waddress -Wsequence-point -Wformat -Wformat-security -Wmissing-declarations -Wmissing-prototypes -Wstrict-prototypes -Wundef -Winit-self -Wpointer-arith -Wshadow -Wuninitialized -Wno-comment -Wimplicit-fallthrough=3 -Wno-strict-overflow -fdiagnostics-show-option -Wno-long-long -pthread -fomit-frame-pointer -ffunction-sections -fdata-sections -msse -msse2 -msse3 -fvisibility=hidden -O3 -DNDEBUG -DNDEBUG
C flags (Debug): -Wl,-strip-all -fsigned-char -W -Wall -Wreturn-type -Waddress -Wsequence-point -Wformat -Wformat-security -Wmissing-declarations -Wmissing-prototypes -Wstrict-prototypes -Wundef -Winit-self -Wpointer-arith -Wshadow -Wuninitialized -Wno-comment -Wimplicit-fallthrough=3 -Wno-strict-overflow -fdiagnostics-show-option -Wno-long-long -pthread -fomit-frame-pointer -ffunction-sections -fdata-sections -msse -msse2 -msse3 -fvisibility=hidden -g -O0 -DDEBUG -D_DEBUG
Linker flags (Release): -Wl,--exclude-libs,libippicv.a -Wl,--exclude-libs,libippiw.a -L/ffmpeg_build/lib -Wl,--gc-sections -Wl,--as-needed -Wl,--no-undefined
Linker flags (Debug): -Wl,--exclude-libs,libippicv.a -Wl,--exclude-libs,libippiw.a -L/ffmpeg_build/lib -Wl,--gc-sections -Wl,--as-needed -Wl,--no-undefined
ccache: YES
Precompiled headers: NO
Extra dependencies: /lib64/libopenblas.so /usr/local/lib/libpng.so /lib64/libz.so dl m pthread rt
3rdparty dependencies: libprotobuf ade ittnotify libjpeg-turbo libwebp libtiff libopenjp2 IlmImf ippiw ippicv
OpenCV modules:
To be built: calib3d core dnn features2d flann gapi highgui imgcodecs imgproc ml objdetect photo python3 stitching video videoio
Disabled: world
Disabled by dependency: -
Unavailable: java python2 ts
Applications: -
Documentation: NO
Non-free algorithms: NO
GUI: NONE
VTK support: NO
Media I/O:
ZLib: /lib64/libz.so (ver 1.2.7)
JPEG: build-libjpeg-turbo (ver 3.0.3-70)
SIMD Support Request: YES
SIMD Support: YES
WEBP: build (ver encoder: 0x020f)
PNG: /usr/local/lib/libpng.so (ver 1.6.43)
TIFF: build (ver 42 - 4.6.0)
JPEG 2000: build (ver 2.5.0)
OpenEXR: build (ver 2.3.0)
HDR: YES
SUNRASTER: YES
PXM: YES
PFM: YES
Video I/O:
DC1394: NO
FFMPEG: YES
avcodec: YES (59.37.100)
avformat: YES (59.27.100)
avutil: YES (57.28.100)
swscale: YES (6.7.100)
avresample: NO
GStreamer: NO
v4l/v4l2: YES (linux/videodev2.h)
Parallel framework: pthreads
Trace: YES (with Intel ITT)
Other third-party libraries:
Intel IPP: 2021.11.0 [2021.11.0]
at: /io/_skbuild/linux-x86_64-3.9/cmake-build/3rdparty/ippicv/ippicv_lnx/icv
Intel IPP IW: sources (2021.11.0)
at: /io/_skbuild/linux-x86_64-3.9/cmake-build/3rdparty/ippicv/ippicv_lnx/iw
VA: NO
Lapack: YES (/lib64/libopenblas.so)
Eigen: NO
Custom HAL: NO
Protobuf: build (3.19.1)
Flatbuffers: builtin/3rdparty (23.5.9)
OpenCL: YES (no extra features)
Include path: /io/opencv/3rdparty/include/opencl/1.2
Link libraries: Dynamic load
Python 3:
Interpreter: /opt/python/cp39-cp39/bin/python3.9 (ver 3.9.19)
Libraries: libpython3.9m.a (ver 3.9.19)
Limited API: YES (ver 0x03060000)
numpy: /home/ci/.local/lib/python3.9/site-packages/numpy/_core/include (ver 2.0.0)
install path: python/cv2/python-3
Python (for build): /opt/python/cp39-cp39/bin/python3.9
Java:
ant: NO
Java: NO
JNI: NO
Java wrappers: NO
Java tests: NO
Install to: /io/_skbuild/linux-x86_64-3.9/cmake-install
-----------------------------------------------------------------
また、出力されたmp4v.mp4
のffprobe
の結果は以下の通りです。
$ ffprobe -hide_banner mp4v.mp4
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'mp4v.mp4':
Metadata:
major_brand : isom
minor_version : 512
compatible_brands: isomiso2mp41
encoder : Lavf59.27.100
Duration: 00:00:01.00, start: 0.000000, bitrate: 28959 kb/s
Stream #0:0(und): Video: mpeg4 (Simple Profile) (mp4v / 0x7634706D), yuv420p, 640x480 [SAR 1:1 DAR 4:3], 28951 kb/s, 30 fps, 30 tbr, 15360 tbn, 30 tbc (default)
Metadata:
handler_name : VideoHandler
vendor_id : [0][0][0][0]
opencv-python-headless
パッケージ
H.264対応本題のマルチステージビルドに入りましょう。
少々長くなってしまいましたが、できあがったDockerfile
は以下の通りです。依存Debianパッケージはもう少し整理できるかもしれません。
opencv-base
ステージが依存Debianパッケージのインストール用、opencv
ステージがwheelのビルド用、無名ステージが実際のアプリケーション用です。
なお、通常版(非ヘッドレス版)をビルドするためにはもう少し依存Debianパッケージが増えますが、私はヘッドレス版ばかり使っているので未検討です。
FROM python:3.11-slim AS opencv-base
RUN \
rm -f /etc/apt/apt.conf.d/docker-clean \
&& apt-get update \
&& DEBIAN_FRONTEND=noninteractive apt-get install --yes --no-install-recommends \
build-essential \
cmake \
libavcodec-dev \
libavformat-dev \
libopencv-dev \
libopenexr-dev \
libpng-dev \
libssl-dev \
libswscale-dev \
libtiff-dev \
libturbojpeg0-dev \
libwebp-dev \
libx264-dev
ENV OPENCV_PYTHON_VERSION 4.10.0.84
FROM opencv-base AS opencv
RUN \
python -m pip install \
build==1.2.2.post1 \
packaging==24.2 \
pyproject_hooks==1.2.0
WORKDIR /root
RUN \
python -m pip download --no-binary opencv-python-headless opencv-python-headless==${OPENCV_PYTHON_VERSION}
RUN tar zxfv opencv-python-headless-${OPENCV_PYTHON_VERSION}.tar.gz
RUN cd opencv-python-headless-${OPENCV_PYTHON_VERSION}/ \
&& MAKEFLAGS="-j$(nproc)" python -m build --wheel
RUN mkdir /dist \
&& cp opencv-python-headless-${OPENCV_PYTHON_VERSION}/dist/opencv_python_headless-*.whl /dist/
FROM opencv-base
COPY /dist/opencv_python_headless-*.whl /root/
WORKDIR /app
COPY ./requirements.txt ./
RUN \
python -m pip install --requirement requirements.txt
numpy==2.2.0
opencv-python-headless @ file:///root/opencv_python_headless-4.10.0.84-cp311-cp311-linux_x86_64.whl
ビルド済み版と同様、上記のDocker環境を使って動作を確認してみます。
$ python build_info.py
$ python make_video.py mp4v
$ python make_video.py avc1
今度はmp4v
、avc1
共に正常に出力されました。
出力されたbuild_info.txt
は以下の通りです。(長いので折りたたんでいます)
ビルド済み版とあまり差が出ないようにビルドしたつもりですが、JPEG関係など、細かな違いはあります。
build_info.txtの内容
General configuration for OpenCV 4.10.0 =====================================
Version control: unknown
Platform:
Timestamp: 2024-12-18T10:00:40Z
Host: Linux 5.15.0-126-generic x86_64
CMake: 3.31.2
CMake generator: Unix Makefiles
CMake build tool: /usr/bin/gmake
Configuration: Release
CPU/HW features:
Baseline: SSE SSE2 SSE3
requested: SSE3
Dispatched code generation: SSE4_1 SSE4_2 FP16 AVX AVX2 AVX512_SKX
requested: SSE4_1 SSE4_2 AVX FP16 AVX2 AVX512_SKX
SSE4_1 (16 files): + SSSE3 SSE4_1
SSE4_2 (1 files): + SSSE3 SSE4_1 POPCNT SSE4_2
FP16 (0 files): + SSSE3 SSE4_1 POPCNT SSE4_2 FP16 AVX
AVX (8 files): + SSSE3 SSE4_1 POPCNT SSE4_2 AVX
AVX2 (36 files): + SSSE3 SSE4_1 POPCNT SSE4_2 FP16 FMA3 AVX AVX2
AVX512_SKX (5 files): + SSSE3 SSE4_1 POPCNT SSE4_2 FP16 FMA3 AVX AVX2 AVX_512F AVX512_COMMON AVX512_SKX
C/C++:
Built as dynamic libs?: NO
C++ standard: 11
C++ Compiler: /usr/bin/c++ (ver 12.2.0)
C++ flags (Release): -fsigned-char -W -Wall -Wreturn-type -Wnon-virtual-dtor -Waddress -Wsequence-point -Wformat -Wformat-security -Wmissing-declarations -Wundef -Winit-self -Wpointer-arith -Wshadow -Wsign-promo -Wuninitialized -Wsuggest-override -Wno-delete-non-virtual-dtor -Wno-comment -Wimplicit-fallthrough=3 -Wno-strict-overflow -fdiagnostics-show-option -Wno-long-long -pthread -fomit-frame-pointer -ffunction-sections -fdata-sections -msse -msse2 -msse3 -fvisibility=hidden -fvisibility-inlines-hidden -O3 -DNDEBUG -DNDEBUG
C++ flags (Debug): -fsigned-char -W -Wall -Wreturn-type -Wnon-virtual-dtor -Waddress -Wsequence-point -Wformat -Wformat-security -Wmissing-declarations -Wundef -Winit-self -Wpointer-arith -Wshadow -Wsign-promo -Wuninitialized -Wsuggest-override -Wno-delete-non-virtual-dtor -Wno-comment -Wimplicit-fallthrough=3 -Wno-strict-overflow -fdiagnostics-show-option -Wno-long-long -pthread -fomit-frame-pointer -ffunction-sections -fdata-sections -msse -msse2 -msse3 -fvisibility=hidden -fvisibility-inlines-hidden -g -O0 -DDEBUG -D_DEBUG
C Compiler: /usr/bin/cc
C flags (Release): -fsigned-char -W -Wall -Wreturn-type -Waddress -Wsequence-point -Wformat -Wformat-security -Wmissing-declarations -Wmissing-prototypes -Wstrict-prototypes -Wundef -Winit-self -Wpointer-arith -Wshadow -Wuninitialized -Wno-comment -Wimplicit-fallthrough=3 -Wno-strict-overflow -fdiagnostics-show-option -Wno-long-long -pthread -fomit-frame-pointer -ffunction-sections -fdata-sections -msse -msse2 -msse3 -fvisibility=hidden -O3 -DNDEBUG -DNDEBUG
C flags (Debug): -fsigned-char -W -Wall -Wreturn-type -Waddress -Wsequence-point -Wformat -Wformat-security -Wmissing-declarations -Wmissing-prototypes -Wstrict-prototypes -Wundef -Winit-self -Wpointer-arith -Wshadow -Wuninitialized -Wno-comment -Wimplicit-fallthrough=3 -Wno-strict-overflow -fdiagnostics-show-option -Wno-long-long -pthread -fomit-frame-pointer -ffunction-sections -fdata-sections -msse -msse2 -msse3 -fvisibility=hidden -g -O0 -DDEBUG -D_DEBUG
Linker flags (Release): -Wl,--exclude-libs,libippicv.a -Wl,--exclude-libs,libippiw.a -Wl,--gc-sections -Wl,--as-needed -Wl,--no-undefined
Linker flags (Debug): -Wl,--exclude-libs,libippicv.a -Wl,--exclude-libs,libippiw.a -Wl,--gc-sections -Wl,--as-needed -Wl,--no-undefined
ccache: NO
Precompiled headers: NO
Extra dependencies: /usr/lib/x86_64-linux-gnu/libjpeg.so /usr/lib/x86_64-linux-gnu/libwebp.so /usr/lib/x86_64-linux-gnu/libpng.so /usr/lib/x86_64-linux-gnu/libtiff.so /usr/lib/x86_64-linux-gnu/libz.so dl m pthread rt
3rdparty dependencies: libprotobuf ade ittnotify libopenjp2 IlmImf ippiw ippicv
OpenCV modules:
To be built: calib3d core dnn features2d flann gapi highgui imgcodecs imgproc ml objdetect photo python3 stitching video videoio
Disabled: world
Disabled by dependency: -
Unavailable: java python2 ts
Applications: -
Documentation: NO
Non-free algorithms: NO
GUI: NONE
VTK support: NO
Media I/O:
ZLib: /usr/lib/x86_64-linux-gnu/libz.so (ver 1.2.13)
JPEG: /usr/lib/x86_64-linux-gnu/libjpeg.so (ver 62)
WEBP: /usr/lib/x86_64-linux-gnu/libwebp.so (ver encoder: 0x020f)
PNG: /usr/lib/x86_64-linux-gnu/libpng.so (ver 1.6.39)
TIFF: /usr/lib/x86_64-linux-gnu/libtiff.so (ver 42 / 4.5.0)
JPEG 2000: build (ver 2.5.0)
OpenEXR: build (ver 2.3.0)
HDR: YES
SUNRASTER: YES
PXM: YES
PFM: YES
Video I/O:
DC1394: YES (2.2.6)
FFMPEG: YES
avcodec: YES (59.37.100)
avformat: YES (59.27.100)
avutil: YES (57.28.100)
swscale: YES (6.7.100)
avresample: NO
GStreamer: NO
v4l/v4l2: YES (linux/videodev2.h)
Parallel framework: pthreads
Trace: YES (with Intel ITT)
Other third-party libraries:
Intel IPP: 2021.11.0 [2021.11.0]
at: /root/opencv-python-headless-4.10.0.84/_skbuild/linux-x86_64-3.11/cmake-build/3rdparty/ippicv/ippicv_lnx/icv
Intel IPP IW: sources (2021.11.0)
at: /root/opencv-python-headless-4.10.0.84/_skbuild/linux-x86_64-3.11/cmake-build/3rdparty/ippicv/ippicv_lnx/iw
VA: NO
Lapack: NO
Eigen: NO
Custom HAL: NO
Protobuf: build (3.19.1)
Flatbuffers: builtin/3rdparty (23.5.9)
OpenCL: YES (no extra features)
Include path: /root/opencv-python-headless-4.10.0.84/opencv/3rdparty/include/opencl/1.2
Link libraries: Dynamic load
Python 3:
Interpreter: /tmp/build-env-aog3lu_n/bin/python (ver 3.11.11)
Libraries: /usr/local/lib/libpython3.11.so (ver 3.11.11)
Limited API: YES (ver 0x03060000)
numpy: /tmp/build-env-aog3lu_n/lib/python3.11/site-packages/numpy/_core/include (ver 2.2.0)
install path: python/cv2/python-3
Python (for build): /tmp/build-env-aog3lu_n/bin/python
Java:
ant: NO
Java: NO
JNI: NO
Java wrappers: NO
Java tests: NO
Install to: /root/opencv-python-headless-4.10.0.84/_skbuild/linux-x86_64-3.11/cmake-install
-----------------------------------------------------------------
また、出力されたmp4v.mp4
、avc1.mp4
のffprobe
の結果は以下の通りです。
avc1.mp4
はちゃんとH.264になっていますね。
$ ffprobe -hide_banner mp4v.mp4
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'mp4v.mp4':
Metadata:
major_brand : isom
minor_version : 512
compatible_brands: isomiso2mp41
encoder : Lavf59.27.100
Duration: 00:00:01.00, start: 0.000000, bitrate: 28964 kb/s
Stream #0:0(und): Video: mpeg4 (Simple Profile) (mp4v / 0x7634706D), yuv420p, 640x480 [SAR 1:1 DAR 4:3], 28956 kb/s, 30 fps, 30 tbr, 15360 tbn, 30 tbc (default)
Metadata:
handler_name : VideoHandler
vendor_id : [0][0][0][0]
$ ffprobe -hide_banner avc1.mp4
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'avc1.mp4':
Metadata:
major_brand : isom
minor_version : 512
compatible_brands: isomiso2avc1mp41
encoder : Lavf59.27.100
Duration: 00:00:01.00, start: 0.000000, bitrate: 32737 kb/s
Stream #0:0(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p, 640x480, 32729 kb/s, 30 fps, 30 tbr, 15360 tbn, 60 tbc (default)
Metadata:
handler_name : VideoHandler
vendor_id : [0][0][0][0]
なお、pip freeze
するとopencv-python-headless @ file:///...#sha256=...
というハッシュ値付きの行が生成されますが、ビルドする度にハッシュ値は変わってしまうため、ハッシュ値の部分は削除することをオススメします。
おわりに
VSCodeに内蔵されている動画プレビュー機能は、avc1
には対応しているけれどmp4v
には対応しておらずプレビューできない…ということをきっかけに並列ビルド、マルチステージビルドに取り組みました。
並列ビルド、マルチステージビルドを活用することで、より快適にH.264対応のopencv-python-headless
パッケージを使えるようになりました。
Dockerfile
が少々長くなり、初回のビルドには時間が掛かりますが、ビルド済みバイナリを管理する手間もなく、手軽に利用できる点が個人的には気に入っています。
本記事が何らかの参考になれば幸いです。
Discussion