6.4 用OpenCV實(shí)現(xiàn)數(shù)據(jù)標(biāo)注小工具
除了對圖像的處理,OpenCV的圖形用戶界面(Graphical User Interface, GUI)和繪圖等相關(guān)功能也是很有用的功能,無論是可視化,圖像調(diào)試還是我們這節(jié)要實(shí)現(xiàn)的標(biāo)注任務(wù),都可以有所幫助。這節(jié)先介紹OpenCV窗口的最基本使用和交互,然后基于這些基礎(chǔ)和之前的知識實(shí)現(xiàn)一個(gè)用于物體檢測任務(wù)標(biāo)注的小工具。
6.4.1 OpenCV窗口循環(huán)
OpenCV顯示一幅圖片的函數(shù)是cv2.imshow(),第一個(gè)參數(shù)是顯示圖片的窗口名稱,第二個(gè)參數(shù)是圖片的array。不過如果直接執(zhí)行這個(gè)函數(shù)的話,什么都不會發(fā)生,因?yàn)檫@個(gè)函數(shù)得配合cv2.waitKey()一起使用。cv2.waitKey()指定當(dāng)前的窗口顯示要持續(xù)的毫秒數(shù),比如cv2.waitKey(1000)就是顯示一秒,然后窗口就關(guān)閉了。比較特殊的是cv2.waitKey(0),并不是顯示0毫秒的意思,而是一直顯示,直到有鍵盤上的按鍵被按下,或者鼠標(biāo)點(diǎn)擊了窗口的小叉子才關(guān)閉。cv2.waitKey()的默認(rèn)參數(shù)就是0,所以對于圖像展示的場景,cv2.waitKey()或者cv2.waitKey(0)是最常用的:
import cv2
img = cv2.imread('Aitutaki.png')
cv2.imshow('Honeymoon Island', img)
cv2.waitKey()
執(zhí)行這段代碼得到如下窗口:
cv2.waitKey()參數(shù)不為零的時(shí)候則可以和循環(huán)結(jié)合產(chǎn)生動態(tài)畫面,比如在6.2.4的延時(shí)小例子中,我們把延時(shí)攝影保存下來的所有圖像放到一個(gè)叫做frames的文件夾下。下面代碼從frames的文件夾下讀取所有圖片并以24的幀率在窗口中顯示成動畫:
import os
from itertools import cycle
import cv2
# 列出frames文件夾下的所有圖片
filenames = os.listdir('frames')
# 通過itertools.cycle生成一個(gè)無限循環(huán)的迭代器,每次迭代都輸出下一張圖像對象
img_iter = cycle([cv2.imread(os.sep.join(['frames', x])) for x in filenames])
key = 0
while key & 0xFF != 27:
cv2.imshow('Animation', next(img_iter))
key = cv2.waitKey(42)
在這個(gè)例子中我們采用了Python的itertools模塊中的cycle函數(shù),這個(gè)函數(shù)可以把一個(gè)可遍歷結(jié)構(gòu)編程一個(gè)無限循環(huán)的迭代器。另外從這個(gè)例子中我們還發(fā)現(xiàn),cv2.waitKey()返回的就是鍵盤上出發(fā)的按鍵。對于字母就是ascii碼,特殊按鍵比如上下左右等,則對應(yīng)特殊的值,其實(shí)這就是鍵盤事件的最基本用法。
6.4.2 鼠標(biāo)和鍵盤事件
因?yàn)镚UI總是交互的,所以鼠標(biāo)和鍵盤事件基本使用必不可少,上節(jié)已經(jīng)提到了cv2.waitKey()就是獲取鍵盤消息的最基本方法。比如下面這段循環(huán)代碼就能夠獲取鍵盤上按下的按鍵,并在終端輸出:
while key != 27:
cv2.imshow('Honeymoon Island', img)
key = cv2.waitKey()
# 如果獲取的鍵值小于256則作為ascii碼輸出對應(yīng)字符,否則直接輸出值
msg = '{} is pressed'.format(chr(key) if key < 256 else key)
print(msg)
通過這個(gè)程序我們能獲取一些常用特殊按鍵的值,比如在筆者用的機(jī)器上,四個(gè)方向的按鍵和刪除鍵對應(yīng)的值如下:
- 上(↑):65362
- 下(↓):65364
- 左(←):65361
- 右(→):65363
- 刪除(Delete):65535
需要注意的是在不同的操作系統(tǒng)里這些值可能是不一樣的。鼠標(biāo)事件比起鍵盤事件稍微復(fù)雜一點(diǎn)點(diǎn),需要定義一個(gè)回調(diào)函數(shù),然后把回調(diào)函數(shù)和一個(gè)指定名稱的窗口綁定,這樣只要鼠標(biāo)位于畫面區(qū)域內(nèi)的事件就都能捕捉到。把下面這段代碼插入到上段代碼的while之前,就能獲取當(dāng)前鼠標(biāo)的位置和動作并輸出:
# 定義鼠標(biāo)事件回調(diào)函數(shù)
def on_mouse(event, x, y, flags, param):
# 鼠標(biāo)左鍵按下,抬起,雙擊
if event == cv2.EVENT_LBUTTONDOWN:
print('Left button down at ({}, {})'.format(x, y))
elif event == cv2.EVENT_LBUTTONUP:
print('Left button up at ({}, {})'.format(x, y))
elif event == cv2.EVENT_LBUTTONDBLCLK:
print('Left button double clicked at ({}, {})'.format(x, y))
# 鼠標(biāo)右鍵按下,抬起,雙擊
elif event == cv2.EVENT_RBUTTONDOWN:
print('Right button down at ({}, {})'.format(x, y))
elif event == cv2.EVENT_RBUTTONUP:
print('Right button up at ({}, {})'.format(x, y))
elif event == cv2.EVENT_RBUTTONDBLCLK:
print('Right button double clicked at ({}, {})'.format(x, y))
# 鼠標(biāo)中/滾輪鍵(如果有的話)按下,抬起,雙擊
elif event == cv2.EVENT_MBUTTONDOWN:
print('Middle button down at ({}, {})'.format(x, y))
elif event == cv2.EVENT_MBUTTONUP:
print('Middle button up at ({}, {})'.format(x, y))
elif event == cv2.EVENT_MBUTTONDBLCLK:
print('Middle button double clicked at ({}, {})'.format(x, y))
# 鼠標(biāo)移動
elif event == cv2.EVENT_MOUSEMOVE:
print('Moving at ({}, {})'.format(x, y))
# 為指定的窗口綁定自定義的回調(diào)函數(shù)
cv2.namedWindow('Honeymoon Island')
cv2.setMouseCallback('Honeymoon Island', on_mouse)
評論
查看更多