カメラで取得した画像の輝度値を平均してバーに表示する(pythonnet編)
概要
IC Imaging Control 3.5のWindows専用APIを使用したPythonのプログラムで、 このプログラムはPCに接続された2台のカメラ映像を同時に表示しながら画像処理(輝度値を平均してバーに表示する)方法について記載しています。このプログラムではGUIアプリケーションを作成するためQtと呼ばれるライブラリを使用しています。
IC Imaging Control 3.4でのバージョンは【2つのカメラで取得した画像の輝度値を平均してバーに表示する】を参照くださいs。
サンプルプログラム
サンプル(Python) | pythonnet_qt5_imageprocessing_python.zip |
---|
サンプルツールの出力
コード全体
from PyQt5.QtGui import QPixmap, QImage
from PyQt5.QtWidgets import QWidget,QMainWindow, QLabel, QSizePolicy, QApplication, QAction, QHBoxLayout,QProgressBar
from PyQt5.QtCore import Qt,QEvent,QObject
from PyQt5.QtCore import *
import sys,traceback
import ctypes as C
import numpy as np
import cv2
# PyhtonNetをインポートする
import clr
# IC Imaging Control3.5を参照する
clr.AddReference('TIS.Imaging.ICImagingControl35')
# IC Imaging Control namespaceを宣言
import TIS.Imaging
class SinkData:
brightnes = 0
FrameBuffer = None
class DisplayBuffer:
'''
ビデオウィンドウに表示するために画像をpixmapにコピーするために必要
'''
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)
result = 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
# イメージバッファを受け取ったときに起動するクラス(イベントリスナー)
class Listener(TIS.Imaging.IFrameQueueSinkListener):
__namespace__ = 'ListenerNameSpace'
saveimage=False
signals = WorkerSignals()
#シンク(画像バッファ)に接続したときに発火
def SinkConnected(self, sink, frameType ):
print('Sink connected')
#バッファリストに割り当て、キューに割り当てる
sink.AllocAndQueueBuffers(2)
#シンク(画像バッファ)から接続を外したときに発火
def SinkDisconnected(self, sink,buffers):
print('Sink disconnected')
# デバイスからフレームを受け取った時に発火
def FramesQueued(self, sink):
frame = sink.PopOutputQueueBuffer()
if self.saveimage is True:
self.saveimage = False
TIS.Imaging.FrameExtensions.SaveAsJpeg(frame,"test.jpg",75)
data = SinkData()
data.FrameBuffer = frame
self.signals.result.emit(data)
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():
listener.saveimage = True
print("Snap")
def Close():
if ic.DeviceValid is True:
ic.LiveStop()
app.quit()
def OnResult(result):
imgcontent = C.cast(result.FrameBuffer.GetIntPtr().ToInt64(), C.POINTER(C.c_ubyte * result.FrameBuffer.FrameType.BufferSize))
img = np.ndarray(buffer = imgcontent.contents,
dtype = np.uint8,
shape = (result.FrameBuffer.FrameType.Height,
result.FrameBuffer.FrameType.Width,
int(result.FrameBuffer.FrameType.BitsPerPixel/8 )) )
# 平均輝度値の平均化
gray = cv2.cvtColor(img ,cv2.COLOR_BGR2GRAY)
mean = cv2.mean(gray)
brightnessbar.setValue(int(mean[0]))
sink.QueueBuffer(result.FrameBuffer)
def OnDisplay(dispBuffer):
videowindow.setPixmap(dispBuffer.pixmap)
dispBuffer.locked = False
app = QApplication(sys.argv)
w = QMainWindow()
w.resize(640, 480)
w.move(300, 300)
w.setWindowTitle('imageprocessing Camera')
# メニュー作成
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)
brightnessbar = QProgressBar()
brightnessbar.setRange(0,256)
brightnessbar.setOrientation( Qt.Vertical)
brightnessbar.setValue(25)
layout.addWidget(brightnessbar)
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))
# 画像処理するためのイベントリスナーを作成
listener = Listener()
sink = TIS.Imaging.FrameQueueSink(listener,TIS.Imaging.MediaSubtypes.RGB32)
ic.Sink = sink
# リスナーにシグナルを接続
listener.signals.result.connect(OnResult)
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 DisplayBuffer:
'''
ビデオウィンドウに表示するために画像をpixmapにコピーするために必要
'''
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)
result = 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
DisplayFilterはDisplayBufferクラスとWorkerSignalsクラスの内部クラスがあり、それぞれの役割は下記の通りです。
DisplayBufferクラス | ウィンドウに表示するために画像をバッファにコピーします。 |
---|---|
WorkerSignals | スロットがシグナルを検知するためにシグナルを定義します。 |
上記のように定義することでカメラから送られてくるフレームをバッファメモリ取込、バッファメモリをGUIに表示することが可能です。
イベントリスナーを使って画像処理をする
class SinkData:
brightnes = 0
FrameBuffer = None
class Listener(TIS.Imaging.IFrameQueueSinkListener):
__namespace__ = 'ListenerNameSpace'
saveimage=False
signals = WorkerSignals()
#シンク(画像バッファ)に接続したときに発火
def SinkConnected(self, sink, frameType ):
print('Sink connected')
#バッファリストに割り当て、キューに割り当てる
sink.AllocAndQueueBuffers(2)
#シンク(画像バッファ)から接続を外したときに発火
def SinkDisconnected(self, sink,buffers):
print('Sink disconnected')
# デバイスからフレームを受け取った時に発火
def FramesQueued(self, sink):
frame = sink.PopOutputQueueBuffer()
if self.saveimage is True:
self.saveimage = False
TIS.Imaging.FrameExtensions.SaveAsJpeg(frame,"test.jpg",75)
data = SinkData()
data.FrameBuffer = frame
#dataを送信する関数
self.signals.result.emit(data)
ListenerクラスはIFrameQueueSinkListenerを継承しており、状態変化の通知や新しいフレームのコールバックするようなクラスです。
FramesQueued | FrameQueueSinkがビデオキャプチャデバイスからフレームを受け取った際にコールされます。FramesQueuedの実行中に次のフレームを受け取った場合、戻った後すぐに次のFramesQueuedがコールされます。self.signals.result.emit(data)でバッファメモリをOnResultに送信します。 |
---|---|
SinkConnected | ICImagingControl.LiveStartまたはICImagingControl.LivePrepareの状態でFrameQueueSinkが接続された時にコールされます。 |
SinkDisconnected | ICImagingControl.LiveStopの状態でFrameQueueSinkが切断された際にコールされます。 |
def OnResult(result):
imgcontent = C.cast(result.FrameBuffer.GetIntPtr().ToInt64(), C.POINTER(C.c_ubyte * result.FrameBuffer.FrameType.BufferSize))
img = np.ndarray(buffer = imgcontent.contents,
dtype = np.uint8,
shape = (result.FrameBuffer.FrameType.Height,
result.FrameBuffer.FrameType.Width,
int(result.FrameBuffer.FrameType.BitsPerPixel/8 )) )
# 平均輝度値の平均化
gray = cv2.cvtColor(img ,cv2.COLOR_BGR2GRAY)
mean = cv2.mean(gray)
brightnessbar.setValue(int(mean[0]))
sink.QueueBuffer(result.FrameBuffer)
# リスナーにシグナルを接続
listener.signals.result.connect(OnResult)
ListenerクラスのFramesQueuedメソッドで送信されたメモリバッファは上記のOnResult関数で受け取ります。
上記ではOpenCVを使って受け取った画像の輝度値を平均し、表示のために戻り値としてメモリバッファを返しています。