メモ OpenCV のマウスイベントの自作ラッパークラス heno.py

henoは ハワイの言葉で 可愛いだそうです。 
ディープラーニングのデータセット用に へのへのもへじを大量に必要なので 小さい専用ペイントツールが欲しくなって ちょっと作りかけ。 未完成だけど できたところまでメモ

今どき github とかgistじゃなく blogにメモする時点でお察しではあります。


ステートマシンの部分だけ抜き出すと、こんな感じで書きます


if __name__=="__main__":
app=MyApp()
home=app.home #appの初期状態 Stateクラスノインスタンス
draw=home.appendChildren("draw")[0] #homeの子供に状態 drawを追加

#ステートマシンの各状態はStateクラスのインスタンスなので、
#こんな感じにデコレータで指定してイベントハンドラを設定していきます
#e に event x y flag app stateが詰め込んであります
@home.on.RButtonUp #ホーム画面 マウス右ボタンで消去
def rightup(e):
e.app.image[:,:,:]=0

@home.on.LButtonDown #ホーム画面 マウス左ボタンで描画開始
def leftdown(e):
e.app.goto(draw)


@draw.on.MouseMove #描画中にマウス移動で小さい丸を書く
def mousemove(e):
cv2.circle(e.app.image,(e.x,e.y),5,(0,0,255),-1)

@draw.on.LButtonUp
def leftup(e): #マウス左ボタン離すと一階層戻る
e.app.back()

app.activate() #起動
while(app.active): #メインループ
app.onUpdate()
print("done")


テストコード部分抜きで100行ぐらいでした ちっちゃい

TODO: スライダーのコールバック

歴史的経緯?で collection.pyが必要です()
https://boxheadroom.hatenablog.com/entry/2019/05/14/160327

がVar をimportしなくても一行足せば動くかも。


class Var(object):pass

heno.py


import cv2
from collection import Var,Enum
events={getattr(cv2,e):e for e in dir(cv2)
if e.startswith("EVENT_") and
not e.startswith("EVENT_FLAG")}
flags={getattr(cv2,e):e for e in dir(cv2)
if e.startswith("EVENT_FLAG")}
rep={
"EVENT_":"",
"MOUSE":"Mouse",
"BUTTON":"Button",
"CLK":"Click",
"WHEEL":"Wheel",
"MOVE":"Move",
"UP":"Up",
"DOWN":"Down",
"DBL":"Double",
}


def camel(s):
for before, after in rep.items():
s=s.replace(before,after)
return s
camels={eid:camel(ename) for eid,ename in events.items()}
class State(object):
def __init__(self,name,parent=None):
self.home=self
self.parent=parent
self.children=[]
self.name=name
self.eventHandler=EventHandler(self)
self.on=self.eventHandler.on
def appendChildren(self,names):
self.children.extend([State(n,parent=self)
for n in names.split(" ") if n!=""])
return self.children
def __repr__(self):
return self.name
class EventHandler(object):
class On(object):
def __init__(self,parent):
self.parent=parent
for eid,ename in events.items():
def closure1(eid,ename):
def deco1(handler):
self.parent.callbacks[eid]=handler
return handler
return deco1
deco=closure1(eid,ename)
self.__dict__[camels[eid]]=deco

def __init__(self,state):
self.callbacks={eid:self.nothing for eid in events}
self.state=state
self.on=self.On(self)
def nothing(self,param):
pass
class App(object):
def __init__(self):
self.state=self.home=State("home")

self.param=Var()
def onEvent(self,event,x,y,flag,param):
param.x=x
param.y=y
param.flag=flag
param.event=event
param.state=self.state
param.app=self
self.state.eventHandler.callbacks[event](param)
def activate(self,):
cv2.setMouseCallback(self.wnd,self.onEvent,self.param)
self.active=True
def deactivate(self):
cv2.setMouseCallback(self.wnd,self.nothing,None)
self.active=False
def onUpdate(self):
pass
def nothing(self,event,x,y,flag,param):
pass
def goto(self,state):
self.state=state
def back(self):
if not self.state.parent is None:
self.state= self.state.parent

def home(self):
self.state=self.home

class MyApp(App):
def __init__(self,wnd="MyApp"):
App.__init__(self)
self.wnd=wnd
cv2.namedWindow(self.wnd)
self.image=np.zeros((300,300,3),dtype=np.uint8)
def onUpdate(self):
cv2.imshow(self.wnd,self.image)
key=cv2.waitKey(10)
if key==27:
self.deactivate()
if __name__=="__main__":
import numpy as np
app=MyApp()
home=app.home
draw=home.appendChildren("draw")[0]

@home.on.LButtonDown
def leftdown(e):
e.app.goto(draw)
@home.on.RButtonUp
def leftdown(e):
e.app.image[:,:,:]=0

@draw.on.MouseMove
def mousemove(e):
cv2.circle(e.app.image,(e.x,e.y),5,(0,0,255),-1)

@draw.on.LButtonUp
def leftup(e):
e.app.back()

app.activate()
while(app.active):
app.onUpdate()

print("done")