IC Imaging Control (Python)

  • サンプルプログラムトップページ
  • デバイスのオープンとハンドリング
    • デバイスを開く
    • ダイアログボックスを使用してデバイスを開く
    • デバイスプロパティ設定の保存と復元
    • プロパティの設定方法
    • 接続しているカメラ一覧を表示
    • ROIの設定方法
    • オートフォーカスで焦点を調整する範囲を指定
    • デバイスロストの検出と再接続方法
  • イメージの取得
    • 8bit静止画保存
    • 16bit静止画保存
    • Enterキーを押下したときに画像保存
    • メモリーレコーディング
    • AVIファイル保存
  • 画像処理
    • コールバック関数の設定方法(OpenCVで二値化)
    • OpenCVで画像処理して表示する方法
    • ソフトウェアトリガー・外部トリガーの使用方法
    • ステレオカメラで表示
    • 2つのカメラで取得した画像の輝度値を平均してバーに表示する
  • IC Imaging Control3.5(pythonnet編)
    • Qtを使ったデモアプリ(pythonnet編)
    • カメラで取得した画像の輝度値を平均してバーに表示する
      (pythonnet編)
    • 動画保存(MediaStreamSink コーデック:H.264)
    • 露光時間・ゲインを設定し、静止画保存をする(pythonnet編)
  • リンク集

    ICImagingControl WEB APIリファレンスマニュアル/サンプルプログラム

    :日本語対応済み :日本語化準備中
    開発言語 APIリファレンスマニュアル サンプルプログラム
    C 4.0
    ()
    - - 4.0
    ()
    - -
    C++ 4.0
    ()
    3.5
    ()
    3.4
    ()
    4.0
    ()
    3.5 3.4
    ()
    C#/VB.NET 4.0
    ()
    3.5
    ()
    3.4
    ()
    4.0
    ()
    3.5
    ()
    3.4
    ()
    Python 4.0
    ()
    3.5 3.4
    ()
    4.0
    ()
    3.5 3.4
    ()

カメラで取得した画像の輝度値を平均してバーに表示する(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を使って受け取った画像の輝度値を平均し、表示のために戻り値としてメモリバッファを返しています。

▲ このページの先頭に戻る

Copyright © ARGO Corporation. All Rights Reserved.