もっちさんの明日はどっちだ

あした、なに観て 生きていく?

【スポンサーリンク】

Anaconda (Windows/Python 3.6) OpenCV+PyQt5 で作ったGUIツールをexe化してみた

罠がいっぱいあるので忘れないように。


個人的な作業用にGUIで動かせるPythonソフトを作っていたんですが、他の人から使わせてほしいということでexe化して配布することになった

……のはいいんですが、これがどうも Anaconda の Python 3.6 だと罠がいっぱいあり、しかも社内ネットがやたらとアクセス制限だらけで、やっとこさ最小構成で実現できたのでメモ。

Python 3.6.1 :: Anaconda 4.4.0 (64 bit)
opencv-python 3.3.0.10
cx-Freeze 5.0.2

結局 cx-Freeze による exe 化がうまくいきました。

とりあえずインストール。

pip install --upgrade cx_Freeze

今はexe化をするのが目的なので、以下のサンプルプログラムをお借りして、受け取った写真を漫画風にするという GUI を作ってみたいと思います。

algorithm.joho.info

この画像フィルタリングソフトに、ファイル選択と表示のGUIを作ったデモがこちら。

PyQtでファイルを選択して表示というGUIは以下を参考にさせて頂きました。

typea.info

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

import numpy.core._methods 
import numpy.lib.format
import numpy as np

import cv2
import PyQt5.QtWidgets as gui
from PyQt5.QtGui import QPixmap

# 加工後の画像を表示
class SubWindow(gui.QDialog):
    def __init__(self, fname, parent):
        self.parent = parent
        gui.QDialog.__init__(self, parent)
        self.fname = fname
        
    def show(self):
        hbox = gui.QHBoxLayout(self)
        pixmap = QPixmap(self.fname)
        
        self.label = gui.QLabel(self)
        self.label.setPixmap(pixmap)
        self.label.setGeometry(160, 40, 80, 30)
        
        hbox.addWidget(self.label)
        self.setLayout(hbox)
        
        self.move(300, 200)
        self.setWindowTitle(self.fname)
        self.exec_()

# 画像を漫画風に加工
# https://algorithm.joho.info/programming/python/opencv-manga-filter-py/
class ProcessManga():
    def manga_filter(self, src, screen, th1=60, th2=150):
        gray = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY)
        screen = cv2.cvtColor(screen, cv2.COLOR_BGR2GRAY)
        screen = cv2.resize(screen,(gray.shape[1],gray.shape[0]))
        
        edge = 255 - cv2.Canny(gray, 80, 120)
        gray[gray <= th1] = 0
        gray[gray >= th2] = 255
        gray[ np.where((gray > th1) & (gray < th2)) ] = screen[ np.where((gray > th1)&(gray < th2)) ]
        return cv2.bitwise_and(gray, edge)
	
    def run(self, fname, fname_scr, fname_mod):
        img = cv2.imread(fname) 
        screen = cv2.imread(fname_scr)
        manga = self.manga_filter(img, screen)
        cv2.imwrite(fname_mod, manga)

# ファイル選択画面
class MainWindow(gui.QWidget):
    def __init__(self, parent=None):
        gui.QMainWindow.__init__(self, parent)
        layout = gui.QGridLayout()
        form = gui.QFormLayout()
        
        # 入力画像とスクリーントーンのファイルを選択
        self.inputText = gui.QLineEdit()
        btnSepFile = gui.QPushButton(u'...')
        btnSepFile.setMaximumWidth(40)
        btnSepFile.clicked.connect(self.chooseDbFile)
        boxSepFile = gui.QHBoxLayout()
        boxSepFile.addWidget(self.inputText)
        boxSepFile.addWidget(btnSepFile)
        form.addRow(u'入力画像を選択', boxSepFile)
        
        self.inputTextScr = gui.QLineEdit()
        btnSepFileScr = gui.QPushButton(u'...')
        btnSepFileScr.setMaximumWidth(40)
        btnSepFileScr.clicked.connect(self.chooseDbFileScr)
        boxSepFileScr = gui.QHBoxLayout()
        boxSepFileScr.addWidget(self.inputTextScr)
        boxSepFileScr.addWidget(btnSepFileScr)
        form.addRow(u'スクリーントーンを選択', boxSepFileScr)
        
        # 実行ボタン
        boxCtrl = gui.QHBoxLayout()
        makeWindowButton = gui.QPushButton('実行')
        makeWindowButton.clicked.connect(self.makeWindow)
        boxCtrl.addWidget(makeWindowButton)
        
        layout.addLayout(form,0,0)
        layout.addLayout(boxCtrl,1,0)
        
        self.setLayout(layout)
        self.resize(300,100)
        
    def makeWindow(self):
        fname = self.inputText.text()
        fname_scr = self.inputTextScr.text()
        fname_mod = fname + '_mod.png'
        pm = ProcessManga()
        pm.run(fname, fname_scr, fname_mod)
        
        subWindow = SubWindow(fname_mod, parent=self)
        subWindow.show()
        
    def chooseDbFile(self):
        dialog = gui.QFileDialog()
        dialog.setFileMode(gui.QFileDialog.ExistingFile)
        if dialog.exec_():
            fileNames = dialog.selectedFiles()
            for f in fileNames:
                self.inputText.setText(f)
                return
        return self.inputText.setText('')
    
    def chooseDbFileScr(self):
        dialog = gui.QFileDialog()
        dialog.setFileMode(gui.QFileDialog.ExistingFile)
        if dialog.exec_():
            fileNames = dialog.selectedFiles()
            for f in fileNames:
                self.inputTextScr.setText(f)
                return
        return self.inputTextScr.setText('')
    
if __name__ == '__main__':
    app = gui.QApplication(sys.argv)
    myapp = MainWindow()
    myapp.show()
    sys.exit(app.exec_())


まず普通に作るところまではよくて、なぜか以下の2つをインポートしないとexeがうまく動きません…。(つまずきポイント1)

import numpy.core._methods 
import numpy.lib.format

こちらを gui.py として、cx_Freeze のビルド用にもう一つファイルを用意します。こちらも以下を参考にさせて頂いて、setup.py として保存します。

www.lisz-works.com

# coding: utf-8
# cx_Freeze 用セットアップファイル
 
import sys
from cx_Freeze import setup, Executable
 
import os
os.environ['TCL_LIBRARY'] = "C:\\Users\\****\\AppData\\Local\\Continuum\\Anaconda3\\tcl\\tcl8.6"
os.environ['TK_LIBRARY'] = "C:\\Users\\****\\AppData\\Local\\Continuum\\Anaconda3\\tcl\\tk8.6"

include_files = ["C:\\Users\\****\\AppData\\Local\\Continuum\\Anaconda3\\DLLs\\tcl86t.dll",
                 "C:\\Users\\****\\AppData\\Local\\Continuum\\Anaconda3\\DLLs\\tk86t.dll"]

build_exe_options = {'include_files': include_files}
base = None

# GUI=有効, CUI=無効 にする
if sys.platform == 'win32' : base = 'Win32GUI'
 
# exe にしたい python ファイルを指定
exe = Executable(script = 'gui.py',
                 base = base)
 
# セットアップ
setup(name = 'test',
      version = '0.1',
      description = 'GUI tool for TG',
      options = {'build_exe': build_exe_options},
      executables = [exe])

今回のデモでは不要なんですが、元のソフトが Anaconda だと TCL/TK の LIBRARY が見えないと怒られてしまい、無理やり環境変数に仕込んだ上でddlの場所を教える必要がありました (つまずきポイント2)

python setup.py build

そして上を実行すると、build/exe.win-amd64-3.6/gui.exe が完成です!だがまだ動かない……… (つまずきポイント3)

C:\Users\***\AppData\Local\Continuum\Anaconda3\Library\plugins\platforms というフォルダを、gui.exe と同じディレクトリにコピーすると、やっと Python ベースの exe ファイルが完成して配布となりました。

f:id:mocchitam:20170914151839p:plain

一応研究が本職なんですが、俺はなんで最近こんなエンジニアみたいなことをしてるんだろうか…。まあいいや、もし使ってみて何かあればフィードバックを頂ければです。

github.com

(も・ω・ち)

フリーズする脳 思考が止まる、言葉に詰まる (生活人新書)

フリーズする脳 思考が止まる、言葉に詰まる (生活人新書)

【スポンサーリンク】