opencv-python 3.3.1.11:Linux平台cv2讀視頻時代到來

背景

那個項目剛開始的時候,也就是大約2017年10月份的時候,我們項目組使用opencv的python介面(也就是opencv-python包或者cv2模塊)去讀視頻的時候,都要提前手工編譯這個python模塊,因為pip直接安裝的包並沒有能力讀視頻。當時,Gemfield團隊使用了下面的方式編譯python介面(cv2模塊):opencv讀視頻時的編碼問題

事情起了變化

2018年1月份的時候,Gemfield偶然發現pip安裝的cv2模塊也可以直接讀視頻。真是奇怪!僅僅2個多月前我們還確信pip安裝的cv2模塊讀視頻會返回error,不僅如此,cv2.VideoCapture這個介面壓根就不能返回fps、frame count這些信息。這兩個多月發生了什麼呢?

Gemfield曾經在ELF的世界裡遊盪了一年多的時間,養成了一些職業病,比方說遇到這個問題的時候,Gemfield首先做的是:

1,查看opencv-python當前可用的版本:

gemfield@ThinkPad-X1C:~$ pip install opencv-python==gemfieldCollecting opencv-python==gemfield Could not find a version that satisfies the requirement opencv-python==gemfield (from versions: 3.1.0.0, 3.1.0.1, 3.1.0.2, 3.1.0.3, 3.1.0.4, 3.1.0.5, 3.2.0.6, 3.2.0.7, 3.2.0.8, 3.3.0.9, 3.3.0.10, 3.3.1.11, 3.4.0.12)No matching distribution found for opencv-python==gemfield

在當前這個時間節點(2018年1月31日),opencv_python可用的版本是:3.1.0.0, 3.1.0.1, 3.1.0.2, 3.1.0.3, 3.1.0.4, 3.1.0.5, 3.2.0.6, 3.2.0.7, 3.2.0.8, 3.3.0.9, 3.3.0.10, 3.3.1.11, 3.4.0.12。而最新版本(3.4.0.12)已經被驗證可以直接支持讀視頻了。那麼是從哪個版本變化的呢?

2,試驗 3.3.0.10

發現該版本並不能讀視頻。

gemfield@ThinkPad-X1C:~/dataset$ pip install opencv_python==3.3.0.10Collecting opencv_python==3.3.0.10 Downloading opencv_python-3.3.0.10-cp27-cp27mu-manylinux1_x86_64.whl (15.4MB) 100% |████████████████████████████████| 15.5MB 86kB/s Collecting numpy>=1.11.1 (from opencv_python==3.3.0.10) Using cached numpy-1.14.0-cp27-cp27mu-manylinux1_x86_64.whlInstalling collected packages: numpy, opencv-pythongemfield@ThinkPad-X1C:~/dataset$ ldd /home/gemfield/.local/lib/python2.7/site-packages/cv2/cv2.so linux-vdso.so.1 => (0x00007ffde9d38000) libz-a147dcb0.so.1.2.3 => /home/gemfield/.local/lib/python2.7/site-packages/cv2/.libs/libz-a147dcb0.so.1.2.3 (0x00007f8c14f27000) libQtGui-6d0f14dd.so.4.8.7 => /home/gemfield/.local/lib/python2.7/site-packages/cv2/.libs/libQtGui-6d0f14dd.so.4.8.7 (0x00007f8c14096000) libQtTest-1183da5d.so.4.8.7 => /home/gemfield/.local/lib/python2.7/site-packages/cv2/.libs/libQtTest-1183da5d.so.4.8.7 (0x00007f8c13e69000) libQtCore-ba1dc80c.so.4.8.7 => /home/gemfield/.local/lib/python2.7/site-packages/cv2/.libs/libQtCore-ba1dc80c.so.4.8.7 (0x00007f8c1395d000) libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f8c135d7000) libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f8c133d3000) libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f8c131b4000) librt.so.1 => /lib/x86_64-linux-gnu/librt.so.1 (0x00007f8c12fac000) libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f8c12c56000) libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f8c12a3f000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f8c1265f000) /lib64/ld-linux-x86-64.so.2 (0x00007f8c16b54000) libgthread-2.0.so.0 => /usr/lib/x86_64-linux-gnu/libgthread-2.0.so.0 (0x00007f8c1245d000) libglib-2.0.so.0 => /lib/x86_64-linux-gnu/libglib-2.0.so.0 (0x00007f8c12149000) libSM.so.6 => /usr/lib/x86_64-linux-gnu/libSM.so.6 (0x00007f8c11f41000) libICE.so.6 => /usr/lib/x86_64-linux-gnu/libICE.so.6 (0x00007f8c11d26000) libXrender.so.1 => /usr/lib/x86_64-linux-gnu/libXrender.so.1 (0x00007f8c11b1c000) libXext.so.6 => /usr/lib/x86_64-linux-gnu/libXext.so.6 (0x00007f8c1190a000) libX11.so.6 => /usr/lib/x86_64-linux-gnu/libX11.so.6 (0x00007f8c115d1000) libpcre.so.3 => /lib/x86_64-linux-gnu/libpcre.so.3 (0x00007f8c1135f000) libuuid.so.1 => /lib/x86_64-linux-gnu/libuuid.so.1 (0x00007f8c1115a000) libbsd.so.0 => /lib/x86_64-linux-gnu/libbsd.so.0 (0x00007f8c10f45000) libxcb.so.1 => /usr/lib/x86_64-linux-gnu/libxcb.so.1 (0x00007f8c10d1e000) libXau.so.6 => /usr/lib/x86_64-linux-gnu/libXau.so.6 (0x00007f8c10b1a000) libXdmcp.so.6 => /usr/lib/x86_64-linux-gnu/libXdmcp.so.6 (0x00007f8c10914000)

3,試驗3.3.1.11

發現該版本可以讀視頻!!

gemfield@ThinkPad-X1C:~/dataset$ pip install opencv_python==3.3.1.11Collecting opencv_python==3.3.1.11 Downloading opencv_python-3.3.1.11-cp27-cp27mu-manylinux1_x86_64.whl (24.7MB) 100% |████████████████████████████████| 24.7MB 54kB/s Collecting numpy>=1.11.1 (from opencv_python==3.3.1.11) Using cached numpy-1.14.0-cp27-cp27mu-manylinux1_x86_64.whlInstalling collected packages: numpy, opencv-pythongemfield@ThinkPad-X1C:~/dataset$ ldd /home/gemfield/.local/lib/python2.7/site-packages/cv2/cv2.so linux-vdso.so.1 => (0x00007fffc3fb6000) libz-a147dcb0.so.1.2.3 => /home/gemfield/.local/lib/python2.7/site-packages/cv2/.libs/libz-a147dcb0.so.1.2.3 (0x00007efdd6f27000) libavcodec-7625dabe.so.58.6.103 => /home/gemfield/.local/lib/python2.7/site-packages/cv2/.libs/libavcodec-7625dabe.so.58.6.103 (0x00007efdd57a3000) libavformat-d3ca1b40.so.58.3.100 => /home/gemfield/.local/lib/python2.7/site-packages/cv2/.libs/libavformat-d3ca1b40.so.58.3.100 (0x00007efdd5362000) libavutil-eaec640f.so.56.5.100 => /home/gemfield/.local/lib/python2.7/site-packages/cv2/.libs/libavutil-eaec640f.so.56.5.100 (0x00007efdd50ed000) libswscale-bc8d848b.so.5.0.101 => /home/gemfield/.local/lib/python2.7/site-packages/cv2/.libs/libswscale-bc8d848b.so.5.0.101 (0x00007efdd4e64000) libQtGui-6d0f14dd.so.4.8.7 => /home/gemfield/.local/lib/python2.7/site-packages/cv2/.libs/libQtGui-6d0f14dd.so.4.8.7 (0x00007efdd3fd3000) libQtTest-1183da5d.so.4.8.7 => /home/gemfield/.local/lib/python2.7/site-packages/cv2/.libs/libQtTest-1183da5d.so.4.8.7 (0x00007efdd3da6000) libQtCore-ba1dc80c.so.4.8.7 => /home/gemfield/.local/lib/python2.7/site-packages/cv2/.libs/libQtCore-ba1dc80c.so.4.8.7 (0x00007efdd389a000) libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007efdd3514000) libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007efdd3310000) libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007efdd30f1000) librt.so.1 => /lib/x86_64-linux-gnu/librt.so.1 (0x00007efdd2ee9000) libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007efdd2b93000) libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007efdd297c000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007efdd259c000) /lib64/ld-linux-x86-64.so.2 (0x00007efdd9027000) libswresample-81cb7b3e.so.3.0.101 => /home/gemfield/.local/lib/python2.7/site-packages/cv2/.libs/libswresample-81cb7b3e.so.3.0.101 (0x00007efdd237e000) libgthread-2.0.so.0 => /usr/lib/x86_64-linux-gnu/libgthread-2.0.so.0 (0x00007efdd217c000) libglib-2.0.so.0 => /lib/x86_64-linux-gnu/libglib-2.0.so.0 (0x00007efdd1e68000) libSM.so.6 => /usr/lib/x86_64-linux-gnu/libSM.so.6 (0x00007efdd1c60000) libICE.so.6 => /usr/lib/x86_64-linux-gnu/libICE.so.6 (0x00007efdd1a45000) libXrender.so.1 => /usr/lib/x86_64-linux-gnu/libXrender.so.1 (0x00007efdd183b000) libXext.so.6 => /usr/lib/x86_64-linux-gnu/libXext.so.6 (0x00007efdd1629000) libX11.so.6 => /usr/lib/x86_64-linux-gnu/libX11.so.6 (0x00007efdd12f0000) libpcre.so.3 => /lib/x86_64-linux-gnu/libpcre.so.3 (0x00007efdd107e000) libuuid.so.1 => /lib/x86_64-linux-gnu/libuuid.so.1 (0x00007efdd0e79000) libbsd.so.0 => /lib/x86_64-linux-gnu/libbsd.so.0 (0x00007efdd0c64000) libxcb.so.1 => /usr/lib/x86_64-linux-gnu/libxcb.so.1 (0x00007efdd0a3d000) libXau.so.6 => /usr/lib/x86_64-linux-gnu/libXau.so.6 (0x00007efdd0839000) libXdmcp.so.6 => /usr/lib/x86_64-linux-gnu/libXdmcp.so.6 (0x00007efdd0633000)

更進一步的,你會看到該版本(3.3.1.11)的cv2.so庫相比之前的版本多鏈接了下面這些庫:

libavcodec-7625dabe.so.58.6.103 => /home/gemfield/.local/lib/python2.7/site-packages/cv2/.libs/libavcodec-7625dabe.so.58.6.103 (0x00007efdd57a3000)libavformat-d3ca1b40.so.58.3.100 => /home/gemfield/.local/lib/python2.7/site-packages/cv2/.libs/libavformat-d3ca1b40.so.58.3.100 (0x00007efdd5362000)libavutil-eaec640f.so.56.5.100 => /home/gemfield/.local/lib/python2.7/site-packages/cv2/.libs/libavutil-eaec640f.so.56.5.100 (0x00007efdd50ed000)libswscale-bc8d848b.so.5.0.101 => /home/gemfield/.local/lib/python2.7/site-packages/cv2/.libs/libswscale-bc8d848b.so.5.0.101 (0x00007efdd4e64000)

這些庫不就是ffmpeg的library嘛:

1,libavcodec包含了所有的FFmpeg 的音頻和視頻的編碼/解碼器,其中大多數編解碼器都是ffmpeg從頭開發的,從而保證代碼的復用性和性能;

2,libavformat包含了用於音視頻容器格式的demuxers和muxers;

3,libavutil包含了一些FFmpeg會用到的通用的演算法封裝,其中有哈稀演算法 (Adler-32, CRC, MD5, RIPEMD, SHA-1. SHA-2, MurmurHash3, HMAC MD-5, HMAC SHA-1 和 HMAC SHA-2)、加密演算法 (DES, RC4, AES, AES-CTR, TEA, XTEA, Blowfish, CAST-128, Twofish 和 Camellia)、LZO 解碼器 以及 Base64的編解碼;

4,libswscale包含了視頻圖像縮放和colorspace/pixelformat 轉換函數。

尋找(非)官方聲明

所以,我們期望能在opencv_python的項目主頁上看到這樣的信息:3.3.1.11 release 我們鏈接了ffmpeg的庫,可以直接提供在Linux平台上讀視頻的功能。

我們能看到嗎?

是的,我們看到了,在Github的項目release頁面上(skvark/opencv-python),我們看到3.3.1.11版本有下面的信息:

Changes:Linux and macOS wheels ship now with FFmpeg (#50, #49, #14)Linux wheels ship with libjpeg-turbo (#52)Haarcascade xml files ship with the package, their path can be accessed via cv2.data.haarcascades (#48)Package metadata updated (#57)__init__.py cleaned up (#55)

為什麼沒有提到寫視頻?

因為Gemfield團隊處理的主要是h264編碼的視頻,我們對其它編碼的視頻並不關心,所以本篇文章並不予以評價。就h264格式的視頻來說,因為ffmpeg的libavcodec庫包含了自己實現的h264解碼器,但是並沒有h264編碼器。ffmpeg的h264編碼要依靠第三方編碼器實現,一般來說有2個,VideoLAN 團隊的libx264或者思科的openh264。

因此,opencv_python 3.3.1.11鏈接了ffmpeg的庫,並不代表可以寫h264視頻了。

另外,編碼h264視頻有2種碼率控制方式(Constant Rate Factor (CRF) 或者 Two-Pass ABR)。

為什麼以前不鏈接ffmpeg的庫呢?

OpenCV video I/O 嚴重依賴於FFmpeg。在manylinux(PEP-0513)和macOS平台上,OpenCV的二進位文件並沒有鏈接FFmpeg的庫,FFmpeg使用的是GPL或者LGPL條款,也就是說在最寬鬆的條件下,OpenCV也只能使用FFmpeg的動態鏈接庫文件,而不能使用靜態鏈接方式。如果使用動態鏈接方式,OpenCV又面臨著各種Linux平台上的各種情況:是否已經安裝了FFmpeg?是否動態庫的版本符合要求?在這種情況下,我們的選擇有:

1,由opencv_python來提供預先編譯好的ffmpeg庫;這樣的問題是,預先編譯好的ffmpeg庫在面對不同種類不同版本的Linux發行版時,會遇到大量兼容性問題;

2,opencv_python只明確所需的ffmpeg依賴,由用戶自己手動在自己的操作系統上安裝ffmpeg。這太麻煩!

因此,添加FFmpeg作為OpenCV的額外依賴需要一個更通用的FFmpeg庫。這在開發和維護上都需要工作量。

為什麼現在鏈接ffmpeg庫了呢?

簡單來說,作者有時間搞這些了;

再進一步,就是opencv_python在windows平台早已鏈接ffmpeg庫,在maxOS平台上不久之前也已經鏈接了ffmpeg庫,那麼Linux作為最後的平台也水到渠成了;

再再進一步來說, PEP 513條款(manylinux1平台)為opencv_python提供預編譯的ffmpeg庫提供了有力支撐。

一直以來,通過pip的形式直接提供的預編譯二進位文件在Linux平台上都會遇到巨大的困難,一方面發行版眾多,另一方面很多軟體包都是由用戶自行安裝升級的。manylinux平台就是為此找到一個公約數,它把琳琅滿目的Linux發行版的一個最小子集剝離出來。如果一個python包裡面的二進位文件能在這個最小子集上面都沒有依賴問題,那大抵它在其它Linux發行版上也不會遇到問題。那麼這個最小子集是怎麼定義的呢?

要了解這個最小子集的定義,我們需要了解具體是什麼東西在妨礙python二進位文件的兼容性。有2大核心障礙:

1,二進位文件所依賴的動態庫文件在當前系統上並不存在;

2,雖然依賴的動態庫文件存在,但動態庫版本並不兼容。

比方說Linux上的基礎庫glibc,雖然我們可以靜態鏈接它(願意遵守GPL),但某些特定但又重要的函數,比如dlopen,只能在動態鏈接glibc的時候才能被調用。所以最終我們都會動態鏈接glibc。幸好,GNU C庫的維護者遵循了嚴格的symbol versioning scheme來提供後向兼容能力。鏈接了舊版本的glibc的二進位文件能夠在新版本的glibc環境上運行(反之不然)。

所以manylinux1(第一代manylinux)是這麼做的:

1,python wheels應該只依賴於那麼幾個特定的共享庫;

2,只依賴於這些共享庫的舊的版本;

3,只依賴於Linux廣泛使用的那些內核ABI。

對於1,manylinux1選擇的庫是:

libpanelw.so.5libncursesw.so.5libgcc_s.so.1libstdc++.so.6libm.so.6libdl.so.2librt.so.1libcrypt.so.1libc.so.6libnsl.so.1libutil.so.1libpthread.so.0libresolv.so.2libX11.so.6libXext.so.6libXrender.so.1libICE.so.6libSM.so.6libGL.so.1libgobject-2.0.so.0libgthread-2.0.so.0libglib-2.0.so.0

在Debian繫上,這些庫由下面這些包提供:

libncurses5libgcc1libstdc++6libc6libx11-6libxext6libxrender1libice6libsm6libgl1-mesa-glxlibglib2.0-0

在RPM繫上,這些庫由下面這些包提供:

ncurseslibgcclibstdc++glibclibXextlibXrenderlibICElibSMmesa-libGLglib2

對於2,manylinux1選擇CentOS 5.11 發行版作為足夠舊的標誌。(是的,你一定已經猜到了,使用docker環境):

上面所說的大多數庫都使用了symbol versioning schemes 來提供後向兼容能力,在CentOS 5.11上,這些庫的版本是:

GLIBC_2.5CXXABI_3.4.8GLIBCXX_3.4.9GCC_4.2.0

因此,python wheel只能依賴這些庫的這些版本:

GLIBC <= 2.5CXXABI <= 3.4.8GLIBCXX <= 3.4.9GCC <= 4.2.0

最終,python_opencv 3.3.1.11就基於manylinux1編譯的wheel來提供pip下載,其提供的預編譯的ffmpeg庫就基於manylinux1進行編譯。

然後再看看本文前述部分提到的cv2.so所鏈接的那些庫,是不是正是這裡介紹的這些最小集合呢?!

性能!性能!性能!

Gemfield發現,相比於opencv讀視頻時的編碼問題這裡面編譯的cv2.so,在這個pip直接安裝的3.3.1.11版本上,讀h264視頻的性能獲得了成倍的提升(不嚴謹的說法),那麼變化是什麼呢?

gemfield@ThinkPad-X1C:~$ python3Python 3.6.3 (default, Oct 3 2017, 21:45:48) [GCC 7.2.0] on linuxType "help", "copyright", "credits" or "license" for more information.>>> import cv2>>> print(cv2.getBuildInformation())General configuration for OpenCV 3.4.0 ===================================== Version control: 3.4.0 Platform: Timestamp: 2017-12-28T20:15:36Z Host: Linux 4.4.0-101-generic x86_64 CMake: 3.9.0 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 requested: SSE4_1 SSE4_2 AVX FP16 AVX2 SSE4_1 (3 files): + SSSE3 SSE4_1 SSE4_2 (1 files): + SSSE3 SSE4_1 POPCNT SSE4_2 FP16 (1 files): + SSSE3 SSE4_1 POPCNT SSE4_2 FP16 AVX AVX (5 files): + SSSE3 SSE4_1 POPCNT SSE4_2 AVX AVX2 (9 files): + SSSE3 SSE4_1 POPCNT SSE4_2 FP16 FMA3 AVX AVX2 C/C++: Built as dynamic libs?: NO ......

Gemfield將2次有區別的地方列出來:

1,Built as dynamic libs: pip是NO,之前是YES;

2,C和C++FLAGS:pip是多了個-fdata-sections;

3,LINK FLAGS:pip多了-L/root/ffmpeg_build/lib;

4,Extra dependencies:

pip是

/opt/Qt4.8.7/lib/libQtGui.so/opt/Qt4.8.7/lib/libQtTest.so/opt/Qt4.8.7/lib/libQtCore.so/lib64/libz.so/opt/libjpeg-turbo/lib64/libjpeg.a avcodecavformatavutilswscaledlmpthreadrt

之前的版本只有

dlmpthreadrt

5,3rdparty dependencies:

pip是

ittnotify libprotobuf libwebp libpng libtiff libjasper IlmImf

之前的版本是空

總之,看起來,性能的主要差異來自於,一個是靜態鏈接的opencv,一個是動態鏈接的opencv。

使用Nvidia顯卡進行h264的編解碼會得到性能提升嗎?

很難。參考HWAccelIntro - FFmpeg


推薦閱讀:

TAG:OpenCV | FFmpeg | H264MPEG4高級視頻編碼 |