ステレオカメラで表示
概要
このプログラムはPCに接続された2台のカメラ映像を同時に表示するための方法について記載しています。このプログラムではGUIアプリケーションを作成するためQtと呼ばれるライブラリを使用しています。
サンプルプログラム
サンプル(Python) | qt_stereo_python.zip |
---|
pip install opencv-python
pip install numpy
pip install pyqt5
使用するライブラリはOpenCV,Numpy,Qtです。ライブラリのインストールがまだの場合はコンソール画面にて上記のコマンドを実行し、インストールしてください。
サンプルの出力
コード全体
import sys
import time
import ctypes
# GUI表記のために必要
import PyQt5.QtCore
import PyQt5.QtWidgets as qt5
# 画像処理・画像保存に使用
import cv2 as cv2
import numpy as np
#Theimagingsourceカメラを使用するために使用
import tisgrabber as tis
#tisgrabber_x64.dllをインポートする
ic = ctypes.cdll.LoadLibrary("./tisgrabber_x64.dll")
tis.declareFunctions(ic)
ic.IC_InitLibrary(0)
# カメラオブジェクト作成
LeftCamera = ic.IC_CreateGrabber()
RightCamera = ic.IC_CreateGrabber()
class CallbackUserdata(ctypes.Structure):
""" コールバック関数に渡されるユーザーデータの例"""
def __init__(self):
self.width = 0
self.height = 0
self.BytesPerPixel = 0
self.buffer_size = 0
self.oldbrightness = 0
self.getNextImage = 0
self.cvMat = None
def LeftCallback(hGrabber, pBuffer, framenumber, pData):
""" 左カメラ用:OpenCVを使用した画像処理のコールバック関数の例
:param:hGrabber:グラバーオブジェクトへのポインター
:param:pBuffer:最初のピクセルの最初のバイトへのポインタ
:param:framenumber:ストリームが開始されてからのフレーム数
:param:pData:追加のユーザーデータ構造へのポインター
"""
if pData.getNextImage == 1:
pData.getNextImage = 2
print(" Left")
if pData.buffer_size > 0:
image = ctypes.cast(pBuffer, ctypes.POINTER(ctypes.c_ubyte * pData.buffer_size))
pData.cvMat = np.ndarray(buffer=image.contents,
dtype=np.uint8,
shape=(pData.height.value,
pData.width.value,
pData.BytesPerPixel))
pData.getNextImage = 0
def RightCallback(hGrabber, pBuffer, framenumber, pData):
""" 右カメラ用:OpenCVを使用した画像処理のコールバック関数の例
:param:hGrabber:グラバーオブジェクトへのポインター
:param:pBuffer:最初のピクセルの最初のバイトへのポインタ
:param:framenumber:ストリームが開始されてからのフレーム数
:param:pData:追加のユーザーデータ構造へのポインター
"""
if pData.getNextImage == 1:
pData.getNextImage = 2
print(" Right")
if pData.buffer_size > 0:
image = ctypes.cast(pBuffer, ctypes.POINTER(ctypes.c_ubyte * pData.buffer_size))
pData.cvMat = np.ndarray(buffer=image.contents,
dtype=np.uint8,
shape=(pData.height.value,
pData.width.value,
pData.BytesPerPixel))
pData.getNextImage = 0
# コールバック関数のポインタを作成
LeftCallbackfunc = ic.FRAMEREADYCALLBACK(LeftCallback)
RightCallbackfunc = ic.FRAMEREADYCALLBACK(RightCallback)
# CallbackUserdataをインスタンス化
LeftUserData = CallbackUserdata()
RightUserData = CallbackUserdata()
#コールバック関数を使用する宣言
ic.IC_SetFrameReadyCallback(LeftCamera, LeftCallbackfunc, LeftUserData)
ic.IC_SetFrameReadyCallback(RightCamera, RightCallbackfunc, RightUserData)
##########################################################################
# カメラ関連の関数
def SelectLeftDevice():
#関数の中からグローバル変数LeftCameraを書き換える
global LeftCamera
ic.IC_StopLive(LeftCamera)
LeftCamera = ic.IC_ShowDeviceSelectionDialog(None)
if ic.IC_IsDevValid(LeftCamera):
ic.IC_SetFrameReadyCallback(LeftCamera, LeftCallbackfunc, LeftUserData)
ic.IC_SetHWnd(LeftCamera, LeftVideo.winId())
startCamera(LeftUserData, LeftCamera)
ic.IC_SaveDeviceStateToFile(LeftCamera, tis.T("left.xml"))
def ShowLeftProperties():
#関数の中からグローバル変数LeftCameraを書き換える
global LeftCamera
if ic.IC_IsDevValid(LeftCamera):
ic.IC_ShowPropertyDialog(LeftCamera)
ic.IC_SaveDeviceStateToFile(LeftCamera, tis.T("left.xml"))
def SelectRightDevice():
#関数の中からグローバル変数RightCameraを書き換える
global RightCamera
ic.IC_StopLive(RightCamera)
RightCamera = ic.IC_ShowDeviceSelectionDialog(None)
if ic.IC_IsDevValid(RightCamera):
ic.IC_SetFrameReadyCallback(RightCamera, RightCallbackfunc, RightUserData)
ic.IC_SetHWnd(RightCamera, RightVideo.winId())
startCamera(LeftUserData, RightCamera)
ic.IC_SaveDeviceStateToFile(RightCamera, tis.T("right.xml"))
def ShowRightProperties():
#関数の中からグローバル変数を書き換える
global RightCamera
if ic.IC_IsDevValid(RightCamera):
ic.IC_ShowPropertyDialog(RightCamera)
ic.IC_SaveDeviceStateToFile(RightCamera, tis.T("right.xml"))
def Close():
#関数の中からグローバル変数を書き換える
global RightCamera
global LeftCamera
if ic.IC_IsDevValid(LeftCamera):
ic.IC_StopLive(LeftCamera)
if ic.IC_IsDevValid(RightCamera):
ic.IC_StopLive(RightCamera)
app.quit()
def restorelastuseddevices():
''' 最後に使用したデバイスをxmlに保存
'''
ic.IC_LoadDeviceStateFromFile(LeftCamera, tis.T("left.xml"))
if ic.IC_IsDevValid(LeftCamera):
startCamera(LeftUserData, LeftCamera)
ic.IC_LoadDeviceStateFromFile(RightCamera, tis.T("right.xml"))
if ic.IC_IsDevValid(RightCamera):
startCamera(RightUserData, RightCamera)
def CreateUserData(ud, camera):
''' 渡されたカメラのコールバック用のユーザーデータを作成します
:param ud:作成するユーザーデータ
:param camera:ユーザーデータに接続されているカメラ
'''
ud.width = ctypes.c_long()
ud.height = ctypes.c_long()
iBitsPerPixel = ctypes.c_int()
colorformat = ctypes.c_int()
# 画像の値を取得する
ic.IC_GetImageDescription(camera, ud.width, ud.height, iBitsPerPixel, colorformat)
# バッファサイズを計算
ud.BytesPerPixel = int(iBitsPerPixel.value / 8.0)
ud.buffer_size = ud.width.value * ud.height.value * ud.BytesPerPixel
ud.getNextImage = 0
def startCamera(UserData, camera):
'''カメラを起動
:param UserData user:カメラに接続されているユーザーデータ
:param Camera:ライブスタートするカメラ
'''
ic.IC_SetContinuousMode(camera, 0)
ic.IC_StartLive(camera, 1)
CreateUserData(UserData, camera)
def OnSnapImagePair():
'''
左右のカメラから取得した画像を同時に保存
'''
print("wait")
RightUserData.getNextImage = 1
LeftUserData.getNextImage = 1
while RightUserData.getNextImage != 0 or LeftUserData.getNextImage != 0:
time.sleep(0.005)
print("done")
# OpenCVでimwriteとして保存するためにnumpyデータとして変換された画像を使用
cv2.imwrite("Left.bmp", cv2.flip(LeftUserData.cvMat, 0))
cv2.imwrite("right.bmp", cv2.flip(RightUserData.cvMat, 0))
#######################################################
# Qtを使ってGUI作成
# コンソール画面にて「pip install pyqt5」でPyQt5をインストールしてください。
# 必ず作らなければいけないオブジェクト
app = PyQt5.QtWidgets.QApplication(sys.argv)
# ウインドウを作る
w = PyQt5.QtWidgets.QMainWindow()
w.resize(1280, 480)
w.move(300, 300)
w.setWindowTitle('ステレオカメラ')
# メニューバーの作成
mainMenu = w.menuBar()
#fileメニュー
fileMenu = mainMenu.addMenu('&ファイル')
#exitメニュー
exitAct = PyQt5.QtWidgets.QAction("&終了", app)
exitAct.setStatusTip("終了")
exitAct.triggered.connect(Close)
fileMenu.addAction(exitAct)
#######################################################
#Qtを使ってGUI作成 左カメラのメニュー
LeftMenu = mainMenu.addMenu('&左カメラ')
devselAct = PyQt5.QtWidgets.QAction("&デバイスを選択", app)
devselAct.triggered.connect(SelectLeftDevice)
LeftMenu.addAction(devselAct)
devpropAct = PyQt5.QtWidgets.QAction("&プロパティを設定", app)
devpropAct.triggered.connect(ShowLeftProperties)
LeftMenu.addAction(devpropAct)
######################################################
#Qtを使ってGUI作成 カメラのメニュー
RightMenu = mainMenu.addMenu('&右カメラ')
devselAct = PyQt5.QtWidgets.QAction("&デバイスを選択", app)
devselAct.triggered.connect(SelectRightDevice)
RightMenu.addAction(devselAct)
devpropAct = PyQt5.QtWidgets.QAction("&プロパティを設定", app)
devpropAct.triggered.connect(ShowRightProperties)
RightMenu.addAction(devpropAct)
######################################################
# Qtを使ってGUI作成 画面のレイアウト作成
# 2つのビデオウィンドウでウィンドウレイアウトを作成する
MainWindow = PyQt5.QtWidgets.QWidget()
#縦方向に画面を配置する
vboxlayout = qt5.QVBoxLayout()
#横方向に画面を配置する
hboxlayout = qt5.QHBoxLayout()
LeftVideo = qt5.QWidget()
RightVideo = qt5.QWidget()
# カメラの映像は縦並び
hboxlayout.addWidget(LeftVideo)
hboxlayout.addWidget(RightVideo)
vboxlayout.addLayout(hboxlayout)
SnapButton = qt5.QPushButton("2つのカメラの画像を同時に保存")
#ボタンをクリックしたとき左右のカメラから取得した画像を同時に保存
SnapButton.clicked.connect(OnSnapImagePair)
vboxlayout.addWidget(SnapButton)
MainWindow.setLayout(vboxlayout)
w.setCentralWidget(MainWindow)
w.show()
# カメラから取得した画像を表示するためにウィンドウハンドルを左右のカメラに渡す
ic.IC_SetHWnd(LeftCamera, LeftVideo.winId())
ic.IC_SetHWnd(RightCamera, RightVideo.winId())
#xmlファイルに左右のカメラの設定情報を保存
restorelastuseddevices()
app.exec()
解説
# GUI表記のために必要
import PyQt5.QtCore
import PyQt5.QtWidgets as qt5
# 画像処理・画像保存に使用
import cv2 as cv2
import numpy as np
ライブラリをインストールしたうえで上記のOpenCV,Numpy,Qtを使用する宣言を行ってください。
カメラオブジェクトの作成
# カメラオブジェクト作成
LeftCamera = ic.IC_CreateGrabber()
RightCamera = ic.IC_CreateGrabber()
#-----------省略-----------
# CallbackUserdataをインスタンス化
LeftUserData = CallbackUserdata()
RightUserData = CallbackUserdata()
# コールバック関数のポインタを作成
LeftCallbackfunc = ic.FRAMEREADYCALLBACK(LeftCallback)
RightCallbackfunc = ic.FRAMEREADYCALLBACK(RightCallback)
#コールバック関数を使用する宣言
ic.IC_SetFrameReadyCallback(LeftCamera, LeftCallbackfunc, LeftUserData)
ic.IC_SetFrameReadyCallback(RightCamera, RightCallbackfunc, RightUserData)
左右のカメラオブジェクトを作成し、それぞれにカメラの情報をユーザーデータとして持たせています。
LeftCallback、RightCallbackのコールバックが呼ばれるように定義しています。
- IC_SetFrameReadyCallback(グラバーのポインタ, コールバック関数の関数ポインタ, ユーザーデータ)
- IC_SetContinuousMode(グラバーのポインタ, 0)
コールバック関数内(上記の例ではFrameCallback)はカメラから画像が送られるたびごとに関数が呼び出されます。
コールバック関数
def CreateUserData(ud, camera):
''' 渡されたカメラのコールバック用のユーザーデータを作成します
:param ud:作成するユーザーデータ
:param camera:ユーザーデータに接続されているカメラ
'''
ud.width = ctypes.c_long()
ud.height = ctypes.c_long()
iBitsPerPixel = ctypes.c_int()
colorformat = ctypes.c_int()
# 画像の値を取得する
ic.IC_GetImageDescription(camera, ud.width, ud.height, iBitsPerPixel, colorformat)
# バッファサイズを計算
ud.BytesPerPixel = int(iBitsPerPixel.value / 8.0)
ud.buffer_size = ud.width.value * ud.height.value * ud.BytesPerPixel
ud.getNextImage = 0
上記の関数はカメラをスタートした後に呼び出されるメソッドです。カメラのパラメータを渡して、カメラのユーザーデータとして解像度やバッファサイズを取得します。また、【ud.getNextImage = 0】とすることで画像バッファの初期化をしています。
def LeftCallback(hGrabber, pBuffer, framenumber, pData):
""" 左カメラ用:OpenCVを使用した画像処理のコールバック関数の例
:param:hGrabber:グラバーオブジェクトへのポインター
:param:pBuffer:最初のピクセルの最初のバイトへのポインタ
:param:framenumber:ストリームが開始されてからのフレーム数
:param:pData:追加のユーザーデータ構造へのポインター
"""
if pData.getNextImage == 1:
pData.getNextImage = 2
print(" Left")
if pData.buffer_size > 0:
image = ctypes.cast(pBuffer, ctypes.POINTER(ctypes.c_ubyte * pData.buffer_size))
pData.cvMat = np.ndarray(buffer=image.contents,
dtype=np.uint8,
shape=(pData.height.value,
pData.width.value,
pData.BytesPerPixel))
pData.getNextImage = 0
OpenCVで画像を処理するために型をuint8に変更します。uint8に変更するためには画像のnumpyライブラリのndarrayを使用する必要があります。OpenCVを使った例は【コールバック関数の設定方法(OpenCVで二値化)】をご確認ください。