Qtを使ったデモアプリ(pythonnet編)
概要
IC Imaging Control 3.5のWindows専用APIを使用したPythonのプログラムで、GUI上で簡単にデバイスの選択・プロパティの設定・bmpで保存ができる方法について記載しています。
サンプルプログラム
サンプル(Python) | pythonnet-qt5-simple.zip |
---|
サンプルツールの出力
コード全体
from PyQt5.QtGui import QPixmap, QImage
from PyQt5.QtWidgets import QWidget,QMainWindow, QLabel, QSizePolicy, QApplication, QAction, QHBoxLayout,QProgressBar
from PyQt5.QtCore import *
import sys,traceback
import ctypes as C
import numpy as np
import cv2
# PyhtonNetをインポートする
import clr
# 同じフォルダ内にあるIC Imaging Control3.5のDllを参照する
clr.AddReference('TIS.Imaging.ICImagingControl35')
clr.AddReference('System')
# IC Imaging Control namespaceを宣言
import TIS.Imaging
from System import TimeSpan
class DisplayBuffer:
'''
このクラスは、ビデオウィンドウに表示するために画像をピックスマップにコピーするために必要
'''
locked = False
pixmap = None
def Copy( self, FrameBuffer):
if( int(FrameBuffer.FrameType.BitsPerPixel/8 ) == 4):
imgcontent = C.cast(FrameBuffer.GetIntPtr().ToInt64(), C.POINTER(C.c_ubyte * FrameBuffer.FrameType.BufferSize))
qimage = QImage(imgcontent.contents, FrameBuffer.FrameType.Width,FrameBuffer.FrameType.Height, QImage.Format_RGB32).mirrored()
self.pixmap = QPixmap(qimage)
class WorkerSignals(QObject):
display = pyqtSignal(object)
class DisplayFilter(TIS.Imaging.FrameFilterImpl):
'''
このフレームフィルターは、着信フレームをDisplayBufferオブジェクトにコピーし、
新しいバッファーでQApplicationに信号を送ります。
'''
__namespace__ = "DisplayFilterClass"
signals = WorkerSignals()
dispBuffer = DisplayBuffer()
def GetSupportedInputTypes(self, frameTypes):
frameTypes.Add( TIS.Imaging.FrameType(TIS.Imaging.MediaSubtypes.RGB32))
def GetTransformOutputTypes(self,inType, outTypes):
outTypes.Add(inType)
return True
def Transform(self, src, dest):
dest.CopyFrom(src)
if self.dispBuffer.locked is False:
self.dispBuffer.locked = True
self.dispBuffer.Copy(dest)
self.signals.display.emit(self.dispBuffer)
return False
####################################################################################
#カメラデバイスの制御
def SelectDevice():
ic.LiveStop()
ic.ShowDeviceSettingsDialog()
if ic.DeviceValid is True:
ic.LiveStart()
ic.SaveDeviceStateToFile("device.xml")
def ShowProperties():
if ic.DeviceValid is True:
ic.ShowPropertyDialog()
ic.SaveDeviceStateToFile("device.xml")
def SnapImage():
'''
画像をスナップして保存する
'''
image = snapsink.SnapSingle(TimeSpan.FromSeconds(1))
TIS.Imaging.FrameExtensions.SaveAsBitmap(image,"test.bmp")
def Close():
if ic.DeviceValid is True:
ic.LiveStop()
app.quit()
def OnDisplay(dispBuffer):
videowindow.setPixmap(dispBuffer.pixmap)
dispBuffer.locked = False
################################################
# QtでGUI作成
app = QApplication(sys.argv)
w = QMainWindow()
w.resize(640, 480)
w.move(300, 300)
w.setWindowTitle('Simple Camera')
# QtでGUI作成
mainMenu = w.menuBar()
fileMenu = mainMenu.addMenu('&ファイル')
exitAct = QAction("&終了",app)
exitAct.setStatusTip("Exit program")
exitAct.triggered.connect(Close)
fileMenu.addAction(exitAct)
deviceMenu = mainMenu.addMenu('&デバイス')
devselAct = QAction("&選択",app)
devselAct.triggered.connect(SelectDevice)
deviceMenu.addAction(devselAct)
devpropAct = QAction("&プロパティ",app)
devpropAct.triggered.connect(ShowProperties)
deviceMenu.addAction(devpropAct)
snapAct = QAction("bmpで保存",app)
snapAct.triggered.connect(SnapImage)
deviceMenu.addAction(snapAct)
layout = QHBoxLayout()
mainwindow = QWidget()
videowindow = QLabel()
layout.addWidget(videowindow)
mainwindow.setLayout(layout)
w.setCentralWidget(mainwindow)
################################################
#IC ImagingControlの設定
# IC ImagingControlオブジェクトを作成
ic = TIS.Imaging.ICImagingControl()
# ライブ表示用に表示フィルターオブジェクトをインスタンス化します
displayFilter = DisplayFilter()
# ディスプレイ信号ハンドラーをフィルターに接続します
displayFilter.signals.display.connect(OnDisplay)
ic.DisplayFrameFilters.Add( ic.FrameFilterCreate(displayFilter))
# メニューから"bmpで保存"をクリックしたときに画像が保存できるようにするためにsinkを作成
snapsink = TIS.Imaging.FrameSnapSink(TIS.Imaging.MediaSubtypes.RGB32)
ic.Sink = snapsink
ic.LiveDisplay = True
try:
ic.LoadDeviceStateFromFile("device.xml",True)
if ic.DeviceValid is True:
ic.LiveStart()
except Exception as ex:
print(ex)
pass
w.show()
app.exec()
解説
オブジェクト作成
ic = TIS.Imaging.ICImagingControl()
displayFilter = DisplayFilter()
IC Imaging ControlのAPIを使用するためにオブジェクトの作成しDisplayFilterクラスからオブジェクトを作成します。
ディスプレイ表示のAPIとGUIを紐づけるための処理
■【シグナル】と【スロット】について
GUIプログラミングでは、GUI を構成する要素やオブジェクトの状態が変わった際に何かしらの処理を行うために、他の要素やオブジェクトに通知するイベントハンドラやコールバックのような仕組みが必要となります。Qtでは【シグナル】と【スロット】と呼ばれるオブジェクト間のやり取りをの仕組みが設けられています。
シグナル | 要素やオブジェクトの状態が変わったときに他の要素やオブジェクトに通知するための関数 |
---|---|
スロット | シグナルに反応して実行される関数 |
Qtのプログラムでは【シグナル】と【スロット】を接続して使用し、シグナルが発生した際にスロットが自動的に実行されます。例:ボタンをクリックしたというシグナルを設定した場合には、メッセージボックスを表示するという【スロット】をセットにすることで、ボタンをクリック時にメッセージボックスを表示することができます。
displayFilter.signals.display.connect(OnDisplay)
ic.DisplayFrameFilters.Add( ic.FrameFilterCreate(displayFilter))
displayFilterで表示する【シグナル】とGUIに表示させるためのOnDisplayメソッドを呼び出す【スロット】を結びつけて、カメラから送られてくる画像をリアルタイムにウィンドウ上に表示させます。
def OnDisplay(dispBuffer):
videowindow.setPixmap(dispBuffer.pixmap)
dispBuffer.locked = False
OnDisplayメソッドは上記の通り、バッファメモリ上にある画像データをウィンドウ上に表示しています。
ディスプレイ表示するためにバッファメモリを処理する
class DisplayFilter(TIS.Imaging.FrameFilterImpl):
'''
このフレームフィルターは、着信フレームをDisplayBufferオブジェクトにコピーし、
新しいバッファーでQApplicationに信号を送ります。
'''
__namespace__ = "DisplayFilterClass"
signals = WorkerSignals()
dispBuffer = DisplayBuffer()
def GetSupportedInputTypes(self, frameTypes):
frameTypes.Add( TIS.Imaging.FrameType(TIS.Imaging.MediaSubtypes.RGB32))
def GetTransformOutputTypes(self,inType, outTypes):
outTypes.Add(inType)
return True
def Transform(self, src, dest):
dest.CopyFrom(src)
if self.dispBuffer.locked is False:
self.dispBuffer.locked = True
self.dispBuffer.Copy(dest)
#スロットにシグナルを送信する。
self.signals.display.emit(self.dispBuffer)
return False
class DisplayBuffer:
'''
このクラスは、ウィンドウに表示するために画像をバッファにコピーするために必要
'''
locked = False
pixmap = None
def Copy( self, FrameBuffer):
if( int(FrameBuffer.FrameType.BitsPerPixel/8 ) == 4):
imgcontent = C.cast(FrameBuffer.GetIntPtr().ToInt64(), C.POINTER(C.c_ubyte * FrameBuffer.FrameType.BufferSize))
qimage = QImage(imgcontent.contents, FrameBuffer.FrameType.Width,FrameBuffer.FrameType.Height, QImage.Format_RGB32).mirrored()
self.pixmap = QPixmap(qimage)
class WorkerSignals(QObject):
display = pyqtSignal(object)
DisplayFilterはFrameFilterImplのクラスを継承し、下記を定義しています。
GetSupportedInputTypes | 変換フィルタが入力タイプとしてカラーフォーマットRGB32を使用することを定義 |
---|---|
GetTransformOutputTypes | 利用可能な出力タイプを定義 |
Transform | ソースにあるフレームをコピーし、画面表示のためのバッファメモリを受け取ったタイミングで発火するように定義 |
DisplayFilterはDisplayBufferクラスとWorkerSignalsクラスの内部クラスがあり、それぞれの役割は下記の通りです。
DisplayBufferクラス | ウィンドウに表示するために画像をバッファにコピーします。 |
---|---|
WorkerSignals | スロットがシグナルを検知するためにシグナルを定義します。 |
上記のように定義することでカメラから送られてくるフレームをバッファメモリ取込、バッファメモリをGUIに表示することが可能です。
bmpで画像を保存する
snapsink = TIS.Imaging.FrameSnapSink(TIS.Imaging.MediaSubtypes.RGB32)
ic.Sink = snapsink
FrameSnapSinkは"bmpで保存"をクリックしたタイミングでフレームを取得するための画像の受け皿(sink)です。
def SnapImage():
'''
画像をスナップして保存する
'''
image = snapsink.SnapSingle(TimeSpan.FromSeconds(1))
TIS.Imaging.FrameExtensions.SaveAsBitmap(image,"test.bmp")
#省略#
#GUIメニュー
snapAct = QAction("bmpで保存",app)
snapAct.triggered.connect(SnapImage)
deviceMenu.addAction(snapAct)
QAction"bmpで保存"が押されたとき、シグナル関数SnapImageが発生するようにしています。