はじめに
opencvのマウスイベントを使いこなしていなかったので、カメラのライブ映像にマウスで描画する実験をしたメモ。
記述言語はpython。
pythonプラグラム
参考文献
以下の情報を参考にして、プログラムを作成した。
オリジナルなのは、以下。
- マウスでクリックした座標を辞書型変数に格納して、履歴を残せるようにした
- 右クリックで直前の座標を削除できるようにした
- 画像と座標を保存できるようにした
ライブラリ
opencvがインストールされている前提。anaconda環境なので、condaでインストールする。バージョンは3.4.2だった。
conda install opencv
プログラム
opencvとjsonをインポートして、変数を初期化する。座標はptに格納する。
import cv2
import json
# データの初期化
pt = {}
m = 0
n = 0
p = 0
マウスイベントに対するコールバック関数を記述する。今回はシンプルに、左クリックでマーカーと直線を描画していく。
# マウスの操作があるとき呼ばれる関数
def callback(event, x, y, flags, param):
	global pt, m, n, p
	#マウスの左ボタンがクリックされたとき
	if event == cv2.EVENT_LBUTTONDOWN:
		m = n
		pt[n] = (x, y)
		#print(n)
		#print(pt)
		n = n + 1
		p = p + 1
	#マウスの右ボタンがクリックされたとき
	if event == cv2.EVENT_RBUTTONDOWN and n > 0:
		#print(m, n)
		#print(pt[m])
		pt.pop(m)
		m = m - 1
		n = n - 1
		p = p + 1
カメラウィンドウやマウスのコールバック関数をセットする。
# カメラを設定
cap = cv2.VideoCapture(0)
# ウィンドウの名前を設定
cv2.namedWindow("img", cv2.WINDOW_NORMAL)
# コールバック関数の設定
cv2.setMouseCallback("img", callback)
# 処理開始のプロンプト
print(">>> Start !!")
カメラのライブ画像を読み込んで描画する。画像ファイルの場合は ret, frame = cap.read() はコメントアウトして、 frame = cv2.imread("lena.jpg") を有効化する。
操作とキーコマンドは以下。
- 左クリックするごとに、描画が進行
- 右クリックすると、直前の描画を削除
- sキーで画像と座標をファイルに保存(画像は連番ファイル)
- cキーで座標履歴クリア(描画は辞書型変数ptに基づくので、描画もクリアされる)
- ESCキーで終了
# 処理ループ
while True:
	#カメラ画像を読み込む
	ret, frame = cap.read()
	#画像ファイルを読み込む
	#frame = cv2.imread("lena.jpg")
	if n >= 1:
		cv2.drawMarker(frame, pt[m], (0, 255, 255), markerType=cv2.MARKER_TILTED_CROSS, markerSize=15)
	if n >= 2:
		for i in range(1, n):
			cv2.line(frame, pt[i - 1], pt[i], (0, 255, 255), 1)
	cv2.imshow("img", frame)
	k = cv2.waitKey(1)
	#Escキーを押すと終了
	if k == 27:
		print(">>> Exit")
		break
	#cを押すと描画クリア(ptを初期化)
	elif k == ord("c"):
		print(">>> Clear Coordinates.")
		pt.clear()
		m = 0
		n = 0
	#sを押すと画像を保存
	elif k == ord("s"):
		print(">>> Save Image and Coordinates.")
		str = "painted{no:03}.png".format(no = p)
		cv2.imwrite(str, frame)
		with open("painted.json","w") as f:
			json.dump(pt, f, indent = 4)
cap.release()
cv2.destroyAllWindows()
プログラム全体
もう少しスマートにかけそうなところもあるが、やりたいことはできたのでこれで良しとする。
import cv2
import json
# データの初期化
pt = {}
m = 0
n = 0
p = 0
# マウスの操作があるとき呼ばれる関数
def callback(event, x, y, flags, param):
	global pt, m, n, p
	#マウスの左ボタンがクリックされたとき
	if event == cv2.EVENT_LBUTTONDOWN:
		m = n
		pt[n] = (x, y)
		#print(n)
		#print(pt)
		n = n + 1
		p = p + 1
	#マウスの右ボタンがクリックされたとき
	if event == cv2.EVENT_RBUTTONDOWN and n > 0:
		#print(m, n)
		#print(pt[m])
		pt.pop(m)
		m = m - 1
		n = n - 1
		p = p + 1
# カメラを設定
cap = cv2.VideoCapture(0)
# ウィンドウの名前を設定
cv2.namedWindow("img", cv2.WINDOW_NORMAL)
# コールバック関数の設定
cv2.setMouseCallback("img", callback)
# 処理開始のプロンプト
print(">>> Start !!")
# 処理ループ
while True:
	#カメラ画像を読み込む
	ret, frame = cap.read()
	#画像ファイルを読み込む
	#frame = cv2.imread("lena.jpg")
	if n >= 1:
		cv2.drawMarker(frame, pt[m], (0, 255, 255), markerType=cv2.MARKER_TILTED_CROSS, markerSize=15)
	if n >= 2:
		for i in range(1, n):
			cv2.line(frame, pt[i - 1], pt[i], (0, 255, 255), 1)
	cv2.imshow("img", frame)
	k = cv2.waitKey(1)
	#Escキーを押すと終了
	if k == 27:
		print(">>> Exit")
		break
	#cを押すと描画クリア(ptを初期化)
	elif k == ord("c"):
		print(">>> Clear Coordinates.")
		pt.clear()
		m = 0
		n = 0
	#sを押すと画像を保存
	elif k == ord("s"):
		print(">>> Save Image and Coordinates.")
		str = "painted{no:03}.png".format(no = p)
		cv2.imwrite(str, frame)
		with open("painted.json","w") as f:
			json.dump(pt, f, indent = 4)
cap.release()
cv2.destroyAllWindows()
実行結果
思ったようにできた。これは画像を使った場合で、ffmpegで連番画像ファイルをgifアニメーションにした。
おまけ
gifアニメーションの生成コマンド
ffmpeg -i painted%03d.png -vf palettegen palette.png
ffmpeg -f image2 -r 1 -i painted%03d.png -i palette.png -filter_complex paletteuse anim.gif
