【Python】當 TensorFlow & OpenCV 遇上 Pyinstaller

  • 5804
  • 0
  • 2019-02-21

當這三個套件碰在一起時,除了一團亂之外,還是一團亂。
那些包羅萬象的錯誤,讓本恩以為來到某個戰爭大戲的片場。

通常寫程式的最後一步,就是包裝成執行檔,然後送到客戶端。
雖然大家都知道 python 的執行檔又肥又大,但這是必要之惡。

因為我們不可能幫每個新用戶安裝執行環境,所以就認命吧!
又肥又大什麼的,當作沒看到就好。

在一開始,先介紹一下夏恩所使用的套件版本:

作業系統:Window 10 專業版
環境:Python 3.5.4 | Anaconda custom (64-bit)
主要套件版本:
  --  pyinstaller 3.3,下載來源:conda-forge
  --  tensorflow-gpu 1.4.0,下載來源:pip

一般而言,常見的 python 執行檔包裝方法有三個:py2exe、PyInstaller 以及 cx_Freeze。
經多次嘗試,三個方法的後續問題都很多,不過 PyInstaller 用起來最順手。

首先,我們先來聊聊 TensorFlow 遇上 Pyinstaller 的問題。

問題1:編譯失敗,出現解碼錯誤的訊息。

夏恩的測試程式只有一行:import tensorflow。
載入其他套件時,像是 numpy 之類的,編譯都沒問題,唯有tensorflow不合群。

這個問題的錯誤訊息大概是長這樣:
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff in position 152: invailid...

這個問題是花了本恩最多時間。
關於這個問題的討論串非常多,非常雜亂。
上面有附上眾多討論串的其中一個,僅供大家參觀一下。

夏恩嘗試了非常多討論串所提供的方法,不過都沒用,最後的解決方式為:

更換編譯器。

是的,各位觀眾!
本年度最大隻的鴕鳥在此,就是夏恩本人。

打架打不贏,跑還不行嗎?

原本使用的編譯器為:Visual Studio Community 2017
後來更換後編譯器為:Visual Studio 2015

然後就好了,從此沒有再看到解碼錯誤的訊息出現。

甚至,夏恩找來另外一台沒有安裝過Visual Studio的電腦來測試,
直接安裝pyinstaller之後,編譯程式也不會報錯,這表示有內建的編譯器可供套件使用,
若我們自己的編譯器和內建編譯器產生衝突時,才會導致編譯錯誤的情況發生。

這邊再補充一點,檔案的放置位置也有可能會導致錯誤發生。
例如之前夏恩把檔案放在 D: 路徑下,編譯會出錯,把檔案放到 C: 路徑下就沒事。
還有中文路徑也可能會導致錯誤發生,這些都必須注意。

問題2:作業系統錯誤。

明明編譯成功的程式,為什麼搬到其他電腦就掛了呢?
這時候請考慮到是否是作業系統的問題。

最簡單的例子就是,在Win10上面所編譯出來執行檔,無法在Linux上執行的,反之亦然。
若想要讓同樣 python 程式以執行檔的型態執行,那麼就必須更換編譯時的系統環境。

這個問題可以繼續延伸:

在 Win10 上所編譯出來的執行檔,在 Win7 不能用。
在 Win10 - 64bit 上所編譯出來的執行檔,在 Win10 - 32 bit 不能用...

甚至!若原環境安裝的是 tensorflow-gpu ,若把執行檔搬到沒有顯示卡的電腦上,也是不能用的!
只有一開始安裝的套件就是 tensorflow cpu 版而非 gpu 版,才能在沒有顯卡的電腦上執行。

所以編譯前,請確認後續的工作環境,進而選擇同樣的作業系統來完成編譯的動作。

問題3:相依套件未加載。

這是最常見的錯誤。
解決方法最簡單,那就是缺什麼給什麼。

例如:
ImportError: No module named decimal.

這時候就在程式內加入:import decimal
接著重新編譯就沒問題了。

這邊附帶一提,夏恩這邊安裝的pyinstaller的指令是使用:

> conda install -c conda-forge pyinstaller

之前有選到 menpo 路徑的安裝檔,執行編譯時一直出現找不到 pefile 的問題。
後來改使用 conda-forge 這個路徑,問題便不再發生。

問題4:編譯沒問題,執行後報錯。

例如:
AttributeError: type object 'pandas._libs.tslib._TSObject' has no attribute '__reduce_cython__'

這個是 pandas 套件的問題,解決辦法是把 pandas 從 0.22 降為 0.20.3 版。
另外一個方法是到 C:\Anaconda\envs\py35\Lib\site-packages\PyInstaller\hooks 這個路徑下,
手動新增一個檔案 hook-pandas.py,檔案內容就一句話:hiddenimports = ['pandas._libs.tslibs.timedeltas']
經過夏恩自己測試可行,不過直接降低版本比較快。

其他類似的問題,像是:
AttributeError: type object 'cytoolz.itertools.remove' has no attribute '__reduce_cython__'
AttributeError: type object 'scipy...' has no attribute '__reduce_cython__'
......

大多是版本的問題,降低為可用的版本就好了。

以上四個問題,都是編譯 TensorFlow 套件時常遇到的問題。
接下來的問題和 OpenCV 有關。

問題5:編譯沒問題,OpenCV 相關套件執行後報錯。

且讓我們先來看看出問題的程式碼:

# -*- coding: utf-8 -*-
import cv2

# ip camera 的擷取路徑
URL = "rtsp://admin:admin@192.168.1.1/video.h264"

# 建立 VideoCapture 物件
ipcam = cv2.VideoCapture(URL)

# 使用無窮迴圈擷取影像,直到按下Esc鍵結束
while True:
    # 使用 read 方法取回影像
    stat, I = ipcam.read()

    # imshow 和 waitkey 需搭配使用才能展示影像
    cv2.imshow('Image', I)
    if cv2.waitKey(1) == 27:
        ipcam.release()
        cv2.destroyAllWindows()
        break

該段程式編譯時後沒有問題,執行後出現錯誤內容:
This application failed to start because it could not find or load the Qt platform plugin "windows" is "".
Reinstalling the application may fix this problem.

夏恩第一次看到這個錯誤訊息時毫無頭緒,又花了不少時間嘗試。
失敗的方法就不多說了,既然問題是少了莫名其妙的platform,那麼解決方法就是給他一個platform。

platform的位置如下:
C:\Anaconda\Library\plugins\platforms

找到這個資料夾,然後把整個資料夾複製,貼到執行檔的所在位置,如下圖。
重新執行程式,可以成功執行。

在此補充說明一下,若程式中沒有使用cv2.imshow的話,並不會出現這個錯誤。
因此產生這個錯誤的原因為程式須 "展開視窗卻找不到可用的平台" 所導致的。

問題6:執行檔執行後,cv2.VideoCapture() 無法順利開啟攝影機。

這個問題很不起眼,因為程式會執行,只是一直抓不到影像。
可以看到的情況就是使用 ipcam.read() 函數時,回傳的狀態都是 False。

為什麼原始程式可以抓得到,編譯成執行檔之後就抓不到了呢?

到底是要該考慮程式的問題?還是攝影機的問題?還是網路線壞掉的問題?

在您動身去查看網路線之前,也許可以先試試以下方法。
先去把 opencv_ffmpeg330_64.dll 檔抓過來放著,如下圖。

放在和執行檔相同的資料夾底下。

這邊請注意 OpenCV 的版本,64位元和32位元不一樣,不要抓到不相符的檔案。
這個檔案的位置在:
C:\Anaconda\envs\py35\Library\bin 或是 C:\Anaconda\Library\bin

因為夏恩的工作環境是在虛擬環境底下,所以會經過 " \envs\py35 "。
這個 dll 檔案的位置,取決於您將 OpenCV 安裝在什麼地方。

如無意外,應該就可以抓到影像了。

小結:

隨著程式愈寫愈大,內容日益繁雜,編譯時缺漏的模組也會跟著增多、不知所云、莫名其妙...。
夏恩在此無法列舉出所有的錯誤,也無從得知所有錯誤的解法。
若閱讀本篇文章後,仍無法解決您的問題,那麼還是得回到最原始的方式:

1. 耐心地去察看錯誤訊息。
2. 尋問 google 大神。
3. 重複以上兩點,不斷地嘗試,一而再,再而三。

最後夏恩也要向 google 大神虔誠的祈禱:

願所有 Bug 終將得到救贖。