opencv（python）のマウスイベントでカメラのライブ映像に線を描く

#はじめに
opencvのマウスイベントを使いこなしていなかったので、カメラのライブ映像にマウスで描画する実験をしたメモ。
記述言語はpython。

#pythonプラグラム

##参考文献

以下の情報を参考にして、プログラムを作成した。

+ [Mouse as a Paint-Brush](https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_gui/py_mouse_handling/py_mouse_handling.html#mouse-handling)
+ [OpenCV(Python)GUIの基礎、線を描く。](https://www.tech-tech.xyz/3132153.html)
+ [Pythonで辞書の要素を削除するclear, pop, popitem, del](https://note.nkmk.me/python-dict-clear-pop-popitem-del/)

オリジナルなのは、以下。

+ マウスでクリックした座標を辞書型変数に格納して、履歴を残せるようにした
+ 右クリックで直前の座標を削除できるようにした
+ 画像と座標を保存できるようにした

##ライブラリ

opencvがインストールされている前提。anaconda環境なので、condaでインストールする。バージョンは**3.4.2**だった。

```shell-session:
conda install opencv
```

##プログラム

opencvとjsonをインポートして、変数を初期化する。座標は**pt**に格納する。

```py:
import cv2
import json

#データの初期化
pt = {}
m = 0
n = 0
p = 0
```

マウスイベントに対するコールバック関数を記述する。今回はシンプルに、左クリックでマーカーと直線を描画していく。

```py:
#マウスの操作があるとき呼ばれる関数
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
```

カメラウィンドウやマウスのコールバック関数をセットする。

```py:
#カメラを設定
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**キーで終了

```py:
#処理ループ
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()
```

##プログラム全体

もう少しスマートにかけそうなところもあるが、やりたいことはできたのでこれで良しとする。

```py:draw_by_mouse.py
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アニメーションにした。

![anim.gif](https://qiita-image-store.s3.amazonaws.com/0/201981/235061d1-e61a-81d3-b416-b525f6d9d713.gif)

##おまけ

gifアニメーションの生成コマンド


```sh:
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
```