産業用UVCカメラのすすめ 産業用UVCカメラのすすめ

【入門編】OpenCVで画像処理

TheImagingSource社が提供しているAPI(ICImagingControl)を使用して、Pythonでカメラを開く方法、画像データを取得する方法、OpenCVで画像処理する方法について示しています。ここでは下記のHPにあるサンプルプログラム:コールバック関数の設定方法(OpenCVで二値化)【callback_python.py】を例にして、2値化を実装する方法について解説します。
サンプルコードは下記を参考にしてください。

https://www.argocorp.com/software/sdk/ICImagingControl/Sample_program/Python_35/callback.html

実行結果

画像処理前画像(上下反転、収縮画像処理なし)

OpenCV処理あり(上下反転、2値化処理)

# リアルタイムでOpenCV処理
threshold = 80 # 二値化の閾値の設定
image = cv2.flip(image, 0) #画像反転
# OpenCVライブラリを使って収縮処理
image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) #モノクロ化
ret, img_THRESH_BINARY = cv2.threshold(image, threshold, 255, cv2.THRESH_BINARY) #二値化

OpenCVなどのサードパーティ製の画像処理ライブラリを使うことで、リアルタイムで画像処理を適用することができます。

コード全体

#########
# 解説1
#########
import ctypes
import tisgrabber as tis
import cv2
import numpy as np
import xml.etree.ElementTree as ET #XMLファイルを解析する

ic = ctypes.cdll.LoadLibrary("./tisgrabber_x64.dll")
tis.declareFunctions(ic)

#########
# 解説2
#########
"" "コールバック関数に渡されるユーザーデータの例。" ""
class CallbackUserdata(ctypes.Structure):
    def __init__(self):    # インスタンス生成時に自動的に呼ばれるメソッド
        self.Value1 = 0
        self.Value2 = 0
        self.camera = None      # grabberオブジェクトを参照

#########
# 解説3
#########
#### 解説3-1
def FrameCallback(hGrabber, pBuffer, framenumber, pData):
    """ これはコールバック関数の例です
    画像はtest.jpgに保存され、pData.Value1が1つインクリメントされます。
     :param:hGrabber:これはグラバーオブジェクトへの実際のポインター(使用禁止)
     :param:pBuffer:最初のピクセルのバイトへのポインタ
     :param:framenumber:ストリームが開始されてからのフレーム数
     :param:pData:追加のユーザーデータ構造へのポインター
    """
    #CallbackUserdataが機能しているか確認
    
    pData.Value1 = pData.Value1 + 1
    Width = ctypes.c_long()
    Height = ctypes.c_long()
    BitsPerPixel = ctypes.c_int()
    colorformat = ctypes.c_int()
    ic.IC_GetImageDescription(hGrabber, Width, Height, BitsPerPixel,colorformat)

#### 解説3-2
    # バッファサイズを計算しピクセルあたりのバイト数と
    # numpy配列のデータ型を取得します。
    elementsperpixel = 1
    dtype = np.uint8
    if colorformat.value == tis.SinkFormats.Y800:
        elementsperpixel = 1  # 1 byte per pixel
    if colorformat.value == tis.SinkFormats.Y16:
        dtype = np.uint16
        elementsperpixel = 1  # 1 uint16 per pixel
    if colorformat.value == tis.SinkFormats.RGB24:
        elementsperpixel = 3  # BGR format, 3 bytes
    if colorformat.value == tis.SinkFormats.RGB32:
        elementsperpixel = 4  # BGRA format, 4 bytes
#### 解説3-3
    # バッファサイズを計算
    buffer_size = Width.value * Height.value * int(float(BitsPerPixel.value) / 8.0)

    # イメージデータを取得
    imagePtr = ic.IC_GetImagePtr(hGrabber)
    imagedata = ctypes.cast(imagePtr,
                            ctypes.POINTER(ctypes.c_ubyte *
                                            buffer_size))
#### 解説3-4
    # NumPy配列で表された画像にする
    image = np.ndarray(buffer=imagedata.contents,
                        dtype=dtype,
                        shape=(Height.value,
                                Width.value,
                                elementsperpixel))


#### 解説3-5
    # リアルタイムでOpenCV処理
    threshold = 80 # 二値化の閾値の設定
    image = cv2.flip(image, 0) #画像反転
    # OpenCVライブラリを使って収縮処理
    image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) #モノクロ化
    ret, img_THRESH_BINARY = cv2.threshold(image, threshold, 255, cv2.THRESH_BINARY) #二値化
    resized_img = cv2.resize(img_THRESH_BINARY,(640, 480)) #表示を縮小
    cv2.imshow('Window', resized_img)
    cv2.waitKey(10)

#########
# 解説4
#########
Userdata = CallbackUserdata()
# 関数ポインタを作成
Callbackfuncptr = ic.FRAMEREADYCALLBACK(FrameCallback)

ic.IC_InitLibrary(0)

#ダイアログ画面を表示
hGrabber = tis.openDevice(ic)


#########
# 解説5
#########
#### 解説5-1 
#デバイスが有効か確認
if(ic.IC_IsDevValid(hGrabber)):
    ## XMLファイルを解析
    tree = ET.parse('device.xml') 
    # XMLを取得
    root = tree.getroot()
    
    # device.xmlからカラーフォーマットを取得
    colorFormat=""
    for name in root.iter('videoformat'):
        colorFormat=name.text
    
    # IC_SetFormatにカラーフォーマットをセット
    if ('Y800' in colorFormat):
        ic.IC_SetFormat(hGrabber, tis.SinkFormats.Y800)

    if ('Y16' in colorFormat):
        ic.IC_SetFormat(hGrabber, tis.SinkFormats.Y16)

    if ('RGB24' in colorFormat):
        ic.IC_SetFormat(hGrabber, tis.SinkFormats.RGB24)

    if ('RGB32' in colorFormat):
        ic.IC_SetFormat(hGrabber, tis.SinkFormats.RGB32)

#### 解説5-2

    ic.IC_SetFrameReadyCallback(hGrabber, Callbackfuncptr, Userdata)
    #連続モードでは、フレームごとにコールバックが呼び出されます。
    ic.IC_SetContinuousMode(hGrabber, 0) 
    ic.IC_SetDefaultWindowPosition(hGrabber, 0)
    ic.IC_SetWindowPosition(hGrabber, 0,0, 640, 480)
    ic.IC_StartLive(hGrabber, 0) #ライブスタート開始 引数:0の時非表示、引数:1の時表示
#### 解説5-3
    ic.IC_MsgBox(tis.T("OKで停止します。"), tis.T("Callback"))
    ic.IC_StopLive(hGrabber)
else:
    ic.IC_MsgBox(tis.T("No device opened"), tis.T("Callback"))

ic.IC_ReleaseGrabber(hGrabber)

使用するファイルについて

【callback_python.py】を動かす為に必ず下記の【dll(ライブラリ)】や【tisgrabber.py】が必要となります。dll(Dynamic Link Library、ダイナミックリンクライブラリ)とは、Windowsにおける共有ライブラリのことで、メーカーがユーザレベルで操作できるように管理しています。これらのdllを【tisgrabber.py】経由で読み込むことで、TheImagingSource社のカメラに簡単にアクセスできます。

tisgrabber.py

TheImagingSource社が提供するAPIであるIC Imaging Controlをカプセル化するtisgrabber.dllのラッパープログラム(IC Imaging Controlを使いやすくするためのもの)となっています。IC Imaging Control DLLの32ビットバージョン(tisgrabber.dll、TIS_UDSHL11.dll)と64ビットバージョン(tisgrabber_x64.dll、TIS_UDSHL11_64.dll)のどちらを使用するかを自動的にチェックしたり、IC Imaging Controlで使用されているメソッドを呼び出すために使われています。

tisgrabber.dll

IC Imaging ControlをPythonで呼び出すための32 bitバージョンのDLL

TIS_UDSHL11.dll

IC Imaging Controlの32 bitバージョンのDLL

tisgrabber_x64.dll

IC Imaging ControlをPythonで呼び出すための64 bitバージョンのDLL

TIS_UDSHL11_64.dll

IC Imaging Controlの64 bitバージョンのDLL

tis-OpenCV.py

実行されるメインプログラム。

解説1:importで宣言する

import ctypes
import tisgrabber as tis
import cv2
import numpy as np
import xml.etree.ElementTree as ET #XMLファイルを解析する

ic = ctypes.cdll.LoadLibrary("./tisgrabber_x64.dll")
tis.declareFunctions(ic)

Pythonで下記のライブラリを使用するためにimportを使います。

ctype このライブラリは C と互換性のあるデータ型を提供し、共有ライブラリ(Windowsでは*.dll)の関数を呼び出すことができます。
tisgrabber IC Imaging Controlを制御するラッパーファイルを読み込むために使用します。
cv2 OpenCVで画像処理を行うために使用します。
numpy Pythonでの機械学習の計算をより速く、効率的に行えるようにするために使用します。
xml.etree.ElementTree カメラ情報が書かれたxmlファイルを読み込むために使用しています。

上記のライブラリを使用するため、あらかじめターミナルにて下記のコマンドを入力し、各ライブラリをインストールしておいてください。

pip install opencv-python
pip install numpy

解説2:ユーザーデータで宣言する

class CallbackUserdata(ctypes.Structure):
    def __init__(self):
        self.Value1 = 0
        self.Value2 = 0
        self.camera = None      # grabberオブジェクトを参照

コールバック関数内で使用される変数を構造体で宣言しています。
データを保持したままコールバック関数に値を渡すことができますので、コールバック関数内で到着した画像のトータル枚数を数えることができます。

解説3:コールバック関数を宣言する

#### 解説3-1
def FrameCallback(hGrabber, pBuffer, framenumber, pData):
    #CallbackUserdataが機能しているか確認
    pData.Value1 = pData.Value1 + 1
    Width = ctypes.c_long()
    Height = ctypes.c_long()
    BitsPerPixel = ctypes.c_int()
    colorformat = ctypes.c_int()
    ic.IC_GetImageDescription(hGrabber, Width, Height, BitsPerPixel,colorformat)

#### 解説3-2
    # バッファサイズを計算しピクセルあたりのバイト数と
    # numpy配列のデータ型を取得します。
    elementsperpixel = 1
    dtype = np.uint8
    if colorformat.value == tis.SinkFormats.Y800:
        elementsperpixel = 1  # 1 byte per pixel
    if colorformat.value == tis.SinkFormats.Y16:
        dtype = np.uint16
        elementsperpixel = 1  # 1 uint16 per pixel
    if colorformat.value == tis.SinkFormats.RGB24:
        elementsperpixel = 3  # BGR format, 3 bytes
    if colorformat.value == tis.SinkFormats.RGB32:
        elementsperpixel = 4  # BGRA format, 4 bytes
#### 解説3-3   
    # バッファサイズを計算
    buffer_size = Width.value * Height.value * int(float(BitsPerPixel.value) / 8.0)

    # イメージデータを取得
    imagePtr = ic.IC_GetImagePtr(hGrabber)
    imagedata = ctypes.cast(imagePtr,
                            ctypes.POINTER(ctypes.c_ubyte *
                                            buffer_size))
#### 解説3-4
    # NumPy配列で表された画像にする
    image = np.ndarray(buffer=imagedata.contents,
                        dtype=dtype,
                        shape=(Height.value,
                                Width.value,
                                elementsperpixel))

#### 解説3-5
    # リアルタイムでOpenCV処理
    threshold = 80 # 二値化の閾値の設定
    image = cv2.flip(image, 0) #画像反転
    # OpenCVライブラリを使って収縮処理
    image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) #モノクロ化
    ret, img_THRESH_BINARY = cv2.threshold(image, threshold, 255, cv2.THRESH_BINARY) #二値化
    resized_img = cv2.resize(img_THRESH_BINARY,(640, 480)) #表示を縮小
    cv2.imshow('Window', resized_img)
    cv2.waitKey(10)

TISカメラから送られてくる画像をPCに受け取るごとに呼び出されるコールバック関数を宣言しています。受け取った画像をOpenCVで画像処理するために受け取った画像を配列化しています。
主な手順は下記の通りです。

解説3-1:カメラから送られた画像の情報を取得

def FrameCallback(hGrabber, pBuffer, framenumber, pData):
    #CallbackUserdataが機能しているか確認
    pData.Value1 = pData.Value1 + 1
    Width = ctypes.c_long()
    Height = ctypes.c_long()
    BitsPerPixel = ctypes.c_int()
    colorformat = ctypes.c_int()
    ic.IC_GetImageDescription(hGrabber, Width, Height, BitsPerPixel,colorformat)

カメラからデータを受け取り、IC_GetImageDescription関数で受け取った画像の横幅、高さ、1ピクセル当たりのデータ量、カラーフォーマットを取得します。

解説3-2:バッファサイズを計算

# バッファサイズを計算しピクセルあたりのバイト数と
# numpy配列のデータ型を取得します。
elementsperpixel = 1
dtype = np.uint8
if colorformat.value == tis.SinkFormats.Y800:
    elementsperpixel = 1  # 1 byte per pixel
if colorformat.value == tis.SinkFormats.Y16:
    dtype = np.uint16
    elementsperpixel = 1  # 1 uint16 per pixel
if colorformat.value == tis.SinkFormats.RGB24:
    elementsperpixel = 3  # BGR format, 3 bytes
if colorformat.value == tis.SinkFormats.RGB32:
    elementsperpixel = 4  # BGRA format, 4 bytes

データ量をカラーフォーマットによって変換しています。データ量はそれぞれ下記の通りです。

Y800 モノクロ8bit(1Byte)
Y16 モノクロ16bit(2Byteだが16 ビット符号なし(uint16)として処理するため、uint16の1Byteとして処理)
RGB24 カラー8bit(3Byte RGBそれぞれ8bit)
RGB32 カラー8bit(4Byte RGBAそれぞれ8bit)アルファ値(透過度)はIC Imaging Controlでは使っていないため常に"0"です。

なお、RGB64はIC Imaging Control3.4ではサポートしていませんが、IC Imaging Control3.5ではサポートしており、Tiff形式で保存することも可能です。
https://www.argocorp.com/software/sdk/ICImagingControl/Sample_program/Python_35/pythonnet-snap-save-image.html

解説3-3:イメージデータを取得

# バッファサイズを計算
buffer_size = Width.value * Height.value * int(float(BitsPerPixel.value) / 8.0)

# イメージデータを取得
#bit形式のイメージデータのポインタを取得しています。
imagePtr = ic.IC_GetImagePtr(hGrabber)
imagedata = ctypes.cast(imagePtr,
                        ctypes.POINTER(ctypes.c_ubyte *
                                        buffer_size))

IC_GetImagePtrでbit形式のイメージデータのポインタimagePtrを取得しています。imagePtrのポインタ型を使って、画像データの先頭から画像の最後のアドレスを指定し、ポインタ型に変換(ctypes.cast) しています。ポインタ変数imagedata が格納している画像データが入っているアドレスそのものですので、imagedataには画像データが格納されていることになります。

解説3-4:NumPy配列化する

# NumPy配列で表された画像にする
image = np.ndarray(buffer=imagedata.contents,
                    dtype=dtype,
                    shape=(Height.value,
                            Width.value,
                            elementsperpixel))

上記によって取得したimagedataを使ってNumpy配列を生成します。OpenCVで読み込む画像イメージがNumPyの配列であるndarray形式で表現されるため、上記のように変換する必要があります。

解説3-5:リアルタイムでOpenCVの画像処理し画面表示する

# リアルタイムでOpenCV処理
threshold = 80 # 二値化の閾値の設定
image = cv2.flip(image, 0) #画像反転
# OpenCVライブラリを使って収縮処理
image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) #モノクロ化
ret, img_THRESH_BINARY = cv2.threshold(image, threshold, 255, cv2.THRESH_BINARY) #二値化
resized_img = cv2.resize(img_THRESH_BINARY,(640, 480)) #表示を縮小
cv2.imshow('Window', resized_img)
cv2.waitKey(10)

下記のOpenCVの画像処理を行うことで最終的に2値化に変換された画像が画面表示されます。

flip 読み込んだ画像を上下左右に反転させます。
cvtColor 画像をモノクロ化しています。
threshold 配列の要素に対して,ある定数(上記では80)での閾値処理をし、二値化しています。
resize 表示サイズを変更します。
imshow 画面に表示します。

解説4:カメラをオープンする

Userdata = CallbackUserdata() #ユーザーデータをインスタンス化
# 関数ポインタを作成
Callbackfuncptr = ic.FRAMEREADYCALLBACK(FrameCallback)

#ICImagingControlクラスライブラリを初期化します。
#この関数は、このライブラリの他の関数が呼び出される前に1回だけ呼び出す必要があります。
ic.IC_InitLibrary(0)

#ダイアログ画面を表示
hGrabber = tis.openDevice(ic)

コールバック関数を設定するための下準備として解説2で登場したCallbackUserdataの使用を宣言します(インスタンス化)。 また、解説3で宣言したコールバック関数FrameCallbackの関数ポインタを宣言し、IC_SetFrameReadyCallbackの引数に渡せるように(後述)しておきます。「ic.IC_InitLibrary(0)」でICImagingControlクラスライブラリを初期化します。その後、「tis.openDevice(ic)」で下図のDevice Settingsダイアログ画面を起動します。

Device Settingsダイアログでは接続しているカメラを画面(GUI)上で選択することができます。Device Settingsダイアログでは解像度やフレームレート、Device Propetiesダイアログ画面ではカメラのゲインや露光時間といったプロパティの設定、Select Custom Format画面では画像の切出しを行い任意の解像度に設定することができます(機種によってはビニング機能を有効化できます)。Device SettingsダイアログでOKボタンを押すタイミングでデバイスの情報やプロパティの設定が記録されたdevice.xmlがPythonプログラムと同じ場所に出力されます。device.xmlが存在した場合にはDevice Settingsダイアログ画面は起動せず、device.xmlに記載された情報をもとに自動的にカメラと設定情報を読み込み次の処理に遷移します。もう一度ダイアログ画面を呼び出したいときにはdevice.xmlを削除してください。

解説5

#### 解説5-1
#デバイスが有効か確認
if(ic.IC_IsDevValid(hGrabber)):
    ## XMLファイルを解析
    tree = ET.parse('device.xml') 
    # XMLを取得
    root = tree.getroot()
    
    # device.xmlからカラーフォーマットを取得
    colorFormat=""
    for name in root.iter('videoformat'):
        colorFormat=name.text
    
    # IC_SetFormatにカラーフォーマットをセット
    if ('Y800' in colorFormat):
        ic.IC_SetFormat(hGrabber, tis.SinkFormats.Y800)

    if ('Y16' in colorFormat):
        ic.IC_SetFormat(hGrabber, tis.SinkFormats.Y16)

    if ('RGB24' in colorFormat):
        ic.IC_SetFormat(hGrabber, tis.SinkFormats.RGB24)

    if ('RGB32' in colorFormat):
        ic.IC_SetFormat(hGrabber, tis.SinkFormats.RGB32)


#### 解説5-2
    ic.IC_SetFrameReadyCallback(hGrabber, Callbackfuncptr, Userdata)
    #連続モードでは、フレームごとにコールバックが呼び出されます。
    ic.IC_SetContinuousMode(hGrabber, 0) 
    ic.IC_SetDefaultWindowPosition(hGrabber, 0)
    ic.IC_SetWindowPosition(hGrabber, 0,0, 640, 480)
    ic.IC_StartLive(hGrabber, 0) #ライブスタート開始 引数:0の時非表示、引数:1の時表示
    
    ic.IC_MsgBox(tis.T("OKで停止します。"), tis.T("Callback"))
    ic.IC_StopLive(hGrabber)
else:
    ic.IC_MsgBox(tis.T("No device opened"), tis.T("Callback"))

ic.IC_ReleaseGrabber(hGrabber)

カメラのストリーミングを開始し、解説2のユーザーデータと解説3のコールバック関数を紐づけ呼び出すようにしています。
また、呼び出し後に終了できるようにメッセージボックスを表示しています。
主な手順は下記の通りです。

解説5-1:カラーフォーマットを設定する

#デバイスが有効か確認
if(ic.IC_IsDevValid(hGrabber)):
    ## XMLファイルを解析
    tree = ET.parse('device.xml') 
    # XMLを取得
    root = tree.getroot()
    
    # device.xmlからカラーフォーマットを取得
    colorFormat=""
    for name in root.iter('videoformat'):
        colorFormat=name.text
    
    # IC_SetFormatにカラーフォーマットをセット
    if ('Y800' in colorFormat):
        ic.IC_SetFormat(hGrabber, tis.SinkFormats.Y800)

    if ('Y16' in colorFormat):
        ic.IC_SetFormat(hGrabber, tis.SinkFormats.Y16)

    if ('RGB24' in colorFormat):
        ic.IC_SetFormat(hGrabber, tis.SinkFormats.RGB24)

    if ('RGB32' in colorFormat):
        ic.IC_SetFormat(hGrabber, tis.SinkFormats.RGB32)

IC_IsDevValidでカメラがPCで認識されているか確認をします。その後、解説4の「hGrabber = tis.openDevice(ic)」で生成されたdevice.xmlのカラーフォーマットを読み込むために、「tree = ET.parse('device.xml')」 でxmlファイルを読み込みます。その後「for name in root.iter('videoformat'):」でxmlファイルの中にあるタグから"videoformat"の記載のあるタグを探索し、「colorFormat=name.text」で変数にカラーフォーマットを保存します。colorFormatに保存されたカラーフォーマットはY800、Y16、RGB24、RGB32のいずれかに当てはまりますので、if文でY800、Y16、RGB24、RGB32の内どれに当てはまるかチェックし、「ic.IC_SetFormat」でカラーフォーマット設定します。ic.IC_SetFormatで正しいカラーフォーマットを設定していないと解説3-1で登場したIC_GetImageDescription関数で正しいカラーフォーマット、1ピクセル当たりのデータ量が正しく出力されないのでご注意ください。

解説5-2:コールバック関数を使用し画像取得を開始する

ic.IC_SetFrameReadyCallback(hGrabber, Callbackfuncptr, Userdata)
#連続モードでは、フレームごとにコールバックが呼び出されます。
ic.IC_SetContinuousMode(hGrabber, 0) 
ic.IC_SetDefaultWindowPosition(hGrabber, 0)
ic.IC_SetWindowPosition(hGrabber, 0,0, 640, 480)
ic.IC_StartLive(hGrabber, 0) #ライブスタート開始 引数:0の時非表示、引数:1の時表示

「IC_SetFrameReadyCallback」を使って、使用するカメラhGrabber・関数ポインタCallbackfuncptr・ユーザーデータUserdataを引数に渡すことで解説2のユーザーデータと解説3のコールバック関数をそれぞれカメラに紐づけることができます。hGrabberを使ってコールバック関数を自動的に引き渡すために「ic.IC_SetContinuousMode(hGrabber, 0) 」と宣言をします。(コールバック関数を使用しないメソッド「IC_SnapImage」を使う場合には「ic.IC_SetContinuousMode(hGrabber, 1) 」と宣言をします。
「IC_SetWindowPosition」にて画像の表示位置を指定し、「IC_StartLive」でカメラから画像を送るように指示します。この時、引数:0の時には画面表示がなし、引数:1の時画面表示がある状態となります。「IC_StartLive」でコールバック関数がようやく呼ばれるようになります。

解説5-3:処理を停止する

    ic.IC_MsgBox(tis.T("OKで停止します。"), tis.T("Callback"))
    ic.IC_StopLive(hGrabber)
else:
    ic.IC_MsgBox(tis.T("No device opened"), tis.T("Callback"))

ic.IC_ReleaseGrabber(hGrabber)

「ic.IC_MsgBox(tis.T("OKで停止します。"), tis.T("Callback"))」で下図のメッセージボックスが画面を起動し、OKボタンが押されるまでコールバック関数が動き続けるようにしています。

メッセージボックスのOKボタンが押されると「ic.IC_ReleaseGrabber(hGrabber)」でhGrabberオブジェクトが解放され処理が終了します。