2020/10/26
OpenCVとPyQtを組み合わせて使う
OpenCVはこのブログの中で何度も取り上げていますが、C++でプログラムを書いて、標準ウィンドウに画像処理結果を出す場合が多かったです。最近はPython環境を構築し、そちらでコードを実行する方がはるかに楽なので、C++を使う必要性もなくなってきています。ただ、C++もそうなんですが、OpenCVを実装したFormアプリを作るのは結構面倒なんですよね。以前のブログでPyQtを使ってGUIを作成したことがありました。DesignerソフトでGUIを設計し、作成されたuiファイルをpyファイルに変換して実装しました。ただ、このやり方だと、GUIのコントロール関係の情報がPythonコード内に組み込まれることで、コードが膨大になり、大変見にくくなって気になっていました。
また、tkinterでGUI実装したこともありました。ただし、tkinterのGUIって安っぽくて美しくないんですよね。(文句ばっかりですね・・)
今回、PyQtのuiファイルをpyファイルに変換し実装しなくても済む方法を見つけたので、試してみました。いまさらですが・・。
まず、DesignerソフトでGUIを作成しました。ここでの注意点は、画像はラベル(QLabel)で表示させることです。少し違和感がありますが・・。



<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>680</width>
<height>540</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<widget class="QLabel" name="viewlabel">
<property name="geometry">
<rect>
<x>10</x>
<y>10</y>
<width>640</width>
<height>480</height>
</rect>
</property>
<property name="frameShape">
<enum>QFrame::Box</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<property name="text">
<string/>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
</widget>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>23</height>
</rect>
</property>
<widget class="QMenu" name="menuFile_F">
<property name="title">
<string>File(&F)</string>
</property>
<addaction name="actionOpen_O"/>
<addaction name="actionQuit_Q"/>
</widget>
<widget class="QMenu" name="menuImage_Processing_P">
<property name="title">
<string>Image Processing(&P)</string>
</property>
<addaction name="actionGray_scale_transformation_G"/>
<addaction name="actionBinarization_B"/>
</widget>
<addaction name="menuFile_F"/>
<addaction name="menuImage_Processing_P"/>
</widget>
<widget class="QStatusBar" name="statusbar"/>
<action name="actionOpen_O">
<property name="text">
<string>Open(&O)</string>
</property>
</action>
<action name="actionQuit_Q">
<property name="text">
<string>Quit(&Q)</string>
</property>
</action>
<action name="actionGray_scale_transformation_G">
<property name="text">
<string>Gray scale transformation(&G)</string>
</property>
</action>
<action name="actionBinarization_B">
<property name="text">
<string>Binarization(&B)</string>
</property>
</action>
</widget>
<resources/>
<connections/>
</ui>
次にPythonコードですが、GUIのイベント関係のコード(OpenCV_Developer.py)と画像処理関係のコード(ImageProc.py)の2つに分けました。以下、作成したコードです。
# OpenCV_Developer.py10行目でuiファイルを読み込んでいます。17〜23行目でGUI上のlabelオブジェクトに画像をモノクロ/カラーに分けて描画します。
import sys
from PyQt5 import uic, QtWidgets
from PyQt5.QtGui import QImage, QPixmap
import ImageProc
class Ui(QtWidgets.QMainWindow):
def __init__(self):
super(Ui, self).__init__()
uic.loadUi('OpenCV_Developer.ui', self)
self.actionOpen_O.triggered.connect(self.func_Open)
self.actionQuit_Q.triggered.connect(self.func_Quit)
self.actionGray_scale_transformation_G.triggered.connect(self.func_Gray_scale_transformation)
self.actionBinarization_B.triggered.connect(self.func_Binarization)
self.show()
def func_ViewDisp(self, input_image):
if len(input_image.shape) == 2: # gray image
image = QImage(input_image.data, ImageProc.img_width, ImageProc.img_height, QImage.Format_Grayscale8)
elif len(input_image.shape) == 3: # color image
image = QImage(input_image.data, ImageProc.img_width, ImageProc.img_height, QImage.Format_RGB888)
pixmap = QPixmap.fromImage(image)
self.viewlabel.setPixmap(pixmap)
def func_Open(self):
input_image = ip.LoadImage('fossa.png')
self.func_ViewDisp(input_image)
def func_Quit(self):
ret = QtWidgets.QMessageBox.information(None, "Quit this software?", "Press a button!", QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No)
if ret == QtWidgets.QMessageBox.Yes:
sys.exit()
def func_Gray_scale_transformation(self):
gray_image = ip.GrayScaleTransformation(ImageProc.input_image)
self.func_ViewDisp(gray_image)
def func_Binarization(self):
bin_image = ip.Binarization(ImageProc.gray_image)
self.func_ViewDisp(bin_image)
app = QtWidgets.QApplication(sys.argv)
window = Ui()
ip = ImageProc.ImageProc()
app.exec_()
画像処理部のコードは以下の通りです。
# ImageProc.py
import cv2
import numpy as np
class ImageProc:
def LoadImage(self, filename):
global input_image
global img_height, img_width
input_image_temp = cv2.imread(filename, cv2.IMREAD_COLOR)
input_image = cv2.cvtColor(input_image_temp, cv2.COLOR_BGR2RGB)
img_height, img_width = input_image.shape[:2]
return input_image
def GrayScaleTransformation(self, inp_image):
global gray_image
gray_image = cv2.cvtColor(np.uint8(inp_image), cv2.COLOR_RGB2GRAY)
return gray_image
def Binarization(self, inp_image):
global bin_image
ret, bin_image = cv2.threshold(inp_image, 100, 255, cv2.THRESH_BINARY)
return bin_image
以下、実行結果です。「File-Open」を実行すると画像がOpenCVのimreadコマンドで読み込まれ、最終的にGUI上に表示されました。



最後に「File-Quit」を実行すると終了の有無を確認してきますので、Yesを押して終了しました。

今後、暇を見つけて、このGUIに種々の機能追加をしていこうと思います。