【入門編】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オブジェクトが解放され処理が終了します。