項目實戰-自制繪圖板¶
概要¶
本期,阿凱從易到難, 實現了四個版本的繪圖板程序。在這節, 阿凱提高了作業的難度, 讓你做出功能豐富的繪圖板出來。
keywords 繪圖板 Painter 鼠標事件監聽
1. 實戰:繪圖板v1-圓形點點¶
我們首先設定一個鼠標回調事件,
我們需要指定窗口名稱windowName
, 只在這個指定窗口下觸發事件.
然后我們需要指定一下,當Mouse事件觸發時, 對應的回調函數onMouse
# 設置鼠標事件回調 cv2.setMouseCallback(windowName,onMouse)
使用舉例
# 設置鼠標事件回調 cv2.setMouseCallback('image',draw_circle)
當每次鼠標事件產生的時候, 例如鼠標移動鼠標點擊等, 就會觸發draw_circle
這個函數。
上文提及的三個onMouse
函數你都可以嘗試一下哦。 尤其是第三個。
2.1 繪圖板v1源碼¶
diy-painter-v1.py
''' 鼠標 每次雙擊,觸發回調函數, 在點擊處繪制一個圓圈 ''' import cv2 import numpy as np # 鼠標回調函數 # x, y 都是相對于窗口內的圖像的位置 def draw_circle(event,x,y,flags,param): # 判斷事件是否為 Left Button Double Clicck if event == cv2.EVENT_LBUTTONDBLCLK: cv2.circle(img,(x,y),20,(255,0,0),-1) # 創建一個黑色圖像,并綁定窗口和鼠標回調函數 img = np.zeros((512,512,3), np.uint8) cv2.namedWindow('image') # 設置鼠標事件回調 cv2.setMouseCallback('image',draw_circle) while(True): cv2.imshow('image',img) if cv2.waitKey(20) == ord('q'): break cv2.destroyAllWindows() # 保存圖片 cv2.imwrite("MousePaint01.png", img)
2.2 阿凱的靈魂畫作01¶
3. 實戰:繪圖板v2-線條繪制¶
有了單個圓圈的繪制, 我們想一下, 如何才能繪制一條曲線呢?
如果我移動鼠標的時候,一直繪制圓圈, 鼠標慢慢移動,這個軌跡不就是一個有寬度的曲線嘛~~
關鍵點在于, 我們需要判斷,鼠標是否按下, 以此來判定是否需要繪制圖片。
3.1 繪圖板v2源碼¶
我們先來看一下,不用flags
參數我們需要如何實現。
我們使用isMouseLBDown
這個布爾值,記錄當前鼠標的狀態。
diy-painter-v2.py
''' 鼠標按下繪制線條 ''' import cv2 import numpy as np # 鼠標回調函數 # x, y 都是相對于窗口內的圖像的位置 isMouseLBDown = False def draw_circle(event,x,y,flags,param): # 判斷事件是否為 Left Button Double Clicck print(event) global isMouseLBDown if event == cv2.EVENT_LBUTTONDOWN: # 檢測到鼠標左鍵按下 print("mouse down") isMouseLBDown = True cv2.circle(img,(x,y),5,(255,0,0),-1) elif event == cv2.EVENT_LBUTTONUP: # 檢測到鼠標左鍵抬起 isMouseLBDown = False print("mouse up") elif event == cv2.EVENT_MOUSEMOVE: if isMouseLBDown: print("drawing") cv2.circle(img,(x,y),5,(255,0,0),-1) # 創建一個黑色圖像,并綁定窗口和鼠標回調函數 img = np.zeros((512,512,3), np.uint8) cv2.namedWindow('image') # 設置鼠標事件回調 cv2.setMouseCallback('image',draw_circle) while(True): cv2.imshow('image',img) if cv2.waitKey(1) == ord('q'): break cv2.destroyAllWindows() cv2.imwrite("MousePaint02.png", img)
當然, 我們有了flags
參數, EVENT_FLAG_LBUTTON
本身就可以判定鼠標左鍵是否按下, 所以改寫一下:
diy-painter-v3.py
''' 鼠標按下繪制線條 ''' import cv2 import numpy as np # 鼠標回調函數 # x, y 都是相對于窗口內的圖像的位置 def draw_circle(event,x,y,flags,param): if flags == cv2.EVENT_FLAG_LBUTTON: cv2.circle(img,(x,y),5,(255,0,0),-1) # 創建一個黑色圖像,并綁定窗口和鼠標回調函數 img = np.zeros((512,512,3), np.uint8) cv2.namedWindow('image') # 設置鼠標事件回調 cv2.setMouseCallback('image',draw_circle) while(True): cv2.imshow('image',img) if cv2.waitKey(1) == ord('q'): break cv2.destroyAllWindows() cv2.imwrite("MousePaint03.png", img)
看, 是不是通過flags
參數, 大大減少了你的代碼復雜度。
我猜你的內心獨白可能是這樣的:“”哦,原來flags是這么用的?!?/p>
3.2 阿凱的靈魂畫作02¶
3.3 存在問題¶
-
如果鼠標運動快了, 就會變成散點, 不連續。
-
只支持單個顏色, 不美麗。
-
不支持調節筆刷粗細。
4. 實戰:繪圖板v3-彩色線條筆觸調節¶
4.1 繪圖板v4源碼¶
diy-painter-v4.py
''' 鼠標按下繪制線條 可以調整線條粗細 變換顏色 線條也更流暢 ''' import cv2 import numpy as np isMouseLBDown = False # 判斷鼠標是否摁下的標志 circleColor = (0, 0, 0) # 畫筆的顏色 circleRadius = 5 # 畫筆的粗細 lastPoint = (0, 0) # 鼠標回調函數 繪制圖像 # x, y 都是相對于窗口內的圖像的位置 def draw_circle(event,x,y,flags,param): # 判斷事件是否為 Left Button Double Clicck # print(event) global img global isMouseLBDown global color global lastPoint if event == cv2.EVENT_LBUTTONDOWN: # 檢測到鼠標左鍵按下 # print("mouse down") isMouseLBDown = True cv2.circle(img,(x,y), int(circleRadius/2), circleColor,-1) lastPoint = (x, y) elif event == cv2.EVENT_LBUTTONUP: # 檢測到鼠標左鍵抬起 isMouseLBDown = False # print("mouse up") elif event == cv2.EVENT_MOUSEMOVE: if isMouseLBDown: # print("drawing") cv2.line(img, pt1=lastPoint, pt2=(x, y), color=circleColor, thickness=circleRadius) lastPoint = (x, y) # cv2.circle(img,(x,y), circleRadius, circleColor,-1) # 更新顏色 def updateCircleColor(x): global circleColor global colorPreviewImg r = cv2.getTrackbarPos('Channel_Red','image') g = cv2.getTrackbarPos('Channel_Green','image') b = cv2.getTrackbarPos('Channel_Blue','image') circleColor = (b, g, r) colorPreviewImg[:] = circleColor # 更新畫筆的寬度 def updateCircleRadius(x): global circleRadius global radiusPreview circleRadius = cv2.getTrackbarPos('Circle_Radius', 'image') # 重置畫布 radiusPreview[:] = (255, 255, 255) # 繪制圓形 cv2.circle(radiusPreview, center=(50, 50), radius=int(circleRadius / 2), color=(0, 0, 0), thickness=-1) # 創建一個畫布,并綁定窗口和鼠標回調函數 img = np.ones((512,512,3), np.uint8) img[:] = (255, 255, 255) # 用于預覽畫筆的顏色 colorPreviewImg = np.ones((100, 100, 3), np.uint8) colorPreviewImg[:] = (0, 0, 0) # 用于預覽畫筆的粗細 radiusPreview = np.ones((100, 100, 3), np.uint8) radiusPreview[:] = (255, 255, 255) # 用于展示繪圖區域的窗口 cv2.namedWindow('image') # 用于預覽顏色的窗口 cv2.namedWindow('colorPreview') # 用于預覽畫筆寬度的窗口 cv2.namedWindow('radiusPreview') # 在window‘image’ 上創建一個滑動條,起名為Channel_XXX, 設定滑動范圍為0-255, # onChange事件回調 啥也不做 cv2.createTrackbar('Channel_Red','image',0,255,updateCircleColor) cv2.createTrackbar('Channel_Green','image',0,255,updateCircleColor) cv2.createTrackbar('Channel_Blue','image',0,255,updateCircleColor) cv2.createTrackbar('Circle_Radius','image',1,20,updateCircleRadius) # 設置鼠標事件回調 cv2.setMouseCallback('image',draw_circle) while(True): cv2.imshow('colorPreview', colorPreviewImg) cv2.imshow('radiusPreview', radiusPreview) cv2.imshow('image',img) if cv2.waitKey(1) == ord('q'): break cv2.destroyAllWindows() cv2.imwrite("MousePaint04.png", img)
4.2 阿凱的靈魂畫作04¶
繪制了自己的靈魂畫作之后, 記得發空間的時候@一下阿凱哦。
5. 作業CH2.4¶
5.1 作業1: 改寫繪圖板v1, 用滑稽臉替換圓形¶
鼠標點擊過的地方都替換成滑稽臉。
你可能需要去了解這么幾個操作:
-
縮放圖片, 控制滑稽臉的尺寸
-
圖片的局部替換/賦值 (需要用到numpy的索引)
為了控制難度, 就矩形區域賦值吧,不摳滑稽臉。
如果還是有難度, 阿凱提供給你一個我縮小過的滑稽臉吧。代碼也先提供吧, 開心比較重要
需要就直接下載吧, 注意修改一下后綴。
哈哈,壞壞的阿凱, 這次不會給你代碼注釋, 自己去官網看文檔哦
Geometric Transformations of Images - 圖像的幾何變換
寫個博客,做做筆記也是極好的。
import cv2 import numpy as np def imgResize(img, ratio = 1): height, width = img.shape[:2] res = cv2.resize(img,(int(ratio*width), int(ratio*height)), interpolation = cv2.INTER_CUBIC) return res huaji = cv2.imread("20180126huaji.jpg") cv2.imwrite("smallhuaji.png",imgResize(huaji, 0.2))
5.2 作業2: 給繪圖板添加繪制幾何形狀的功能¶
例如,你可以給畫圖板添加矩形繪制的函數,第一次點擊的點為矩形的左上角的起點.
接下來鼠標拖動,動態繪制矩形,鼠標彈起,確定矩形,矩形保留在畫布上.
你也可以添加繪制圓形的功能,初始點擊的點為圓心,接下來鼠標向外拖動,半徑放大,鼠標彈起完成圓形繪制.
5.3 作業3: 在圖片的基礎上涂鴉¶
這個作業沒有解析,純開放題目
如果圖形化有困難,可以預先設定圖片的路徑。
通過對話框的形式,選擇一張圖片,將圖片導入到畫布,任意涂鴉,接下來保存為本地圖片.
你可以結合其他的Python GUI組件實現此功能. 例如PyQT
例如Tkinter
.
你甚至可以再加上一個保存按鈕, 可以選擇保存路徑。
哈哈, 還有, 還有, 撤銷按鈕,保存最近10步操作 (適合使用數據結構:棧)
5.4 作業4 實現SelectROI的功能¶
利用作業1同樣的原理, 去選擇一個矩形區域,并且將這個矩形區域的內容保存為另外一張圖片, 并且打印矩形區域的tuple
(x, y, w, h)
矩形區域在原來圖片的左上角坐標(x,y)
矩形區域的寬度w
矩形區域的高度h
6. Reference¶
How to Detect Mouse Clicks and Moves