一、什麼是OpenCV
OpenCV 是一個開源的計算機視覺庫,在 1999 年由英特爾的 Gary Bradski 啓動。OpenCV 庫由 C 和 C++ 語言編寫,涵蓋計算機視覺各個領域內的 500 多個函數,可以在多種操作系統上運行。它旨在提供一個簡潔而又高效的接口,從而幫助開發人員快速地構建視覺應用。OpenCV 更像一個黑盒,讓我們專注於視覺應用的開發,而不必過多關注基礎圖像處理的具體細節。
我們可以在終端中使用 pip 安裝 OpenCV 模塊。默認是從國外的主站上下載,因此,我們可能會遇到網絡不好的情況導致下載失敗。我們可以在 pip 指令後通過 -i 指定國內鏡像源下載。
pip install opencv-python -i https://mirrors.aliyun.com/pypi/simple
二、OpenCV的基本使用
首先需要導入 cv2 模塊,大多數常用的 OpenCV 函數都在 cv2 模塊內。與 cv2 模塊所對應的 cv 模塊代表傳統版本的模塊。這裏的 cv2 模塊並不代表該模塊是專門針對 OpenCV 2 版本的,而是指該模塊引入了一個改善的 API 接口。在 cv2 模塊內部採用了面向對象的編程方式,而在 cv 模塊內更多采用的是面向過程的編程方式。
import cv2
【1】、讀取圖像
OpenCV 提供了 cv2.imread() 函數來 讀取圖像,該函數支持多種靜態圖像格式。該函數 返回值 是 讀取到的圖像。如果未讀取到圖像,則返回 None。
cv2.imread(filename: str, flags: int) -> cv2.typing.MatLike | None
其中,參數 filename 表示要 讀取的圖像的完整文件名。參數 flags 是 讀取標記。該標記用來控制讀取文件的類型。flags 標誌可以取值如下:
cv2.IMREAD_UNCHANGED # 保持原格式不變
cv2.IMREAD_GRAYSCALE # 將圖像調整為單通道的灰度圖像
cv2.IMREAD_COLOR # 默認值,將圖像調整為三通道彩色圖像
cv2.IMREAD_ANYDEPTH # 當載入的圖像深度為16或者32位時,返回對應的深度圖像,否則轉換為8位圖像
cv2.IMREAD_ANYCOLOR # 以任何可能的顏色格式讀取圖像
cv2.IMREAD_LOAD_GDAL # 使用dgal驅動程序加載圖像
cv2.IMREAD_REDUCED_GRAYSCALE_2 # 縮放圖像為1/2,並將其調整為單通道的灰度圖像
cv2.IMREAD_REDUCED_COLOR_2 # 縮放圖像為1/2,並將其調整為三通道的彩色圖像
cv2.IMREAD_REDUCED_GRAYSCALE_4 # 縮放圖像為1/4,並將其調整為單通道的灰度圖像
cv2.IMREAD_REDUCED_COLOR_4 # 縮放圖像為1/4,並將其調整為三通道的彩色圖像
cv2.IMREAD_REDUCED_GRAYSCALE_8 # 縮放圖像為1/8,並將其調整為單通道的灰度圖像
cv2.IMREAD_REDUCED_COLOR_8 # 縮放圖像為1/8,並將其調整為三通道的彩色圖像
cv2.IMREAD_IGNORE_ORIENTATION # 不要根據EXIF標記旋轉圖像
【2】、創建窗口
OpenCV 提供了 cv2.namedWindow() 函數來 創建一個窗口。
cv2.namedWindow(winname: str, flags: int = cv.WINDOW_AUTOSIZE) -> None
其中,參數 winname 是要 創建的窗口的名稱。參數 flags 是 窗口的標誌,可以取值如下:
cv2.WINDOW_NORMAL # 用户可以調整窗口大小
cv2.WINDOW_AUTOSIZE # 窗口大小自動適應圖片大小,用户無法調整窗口大小
cv2.WINDOW_FULLSCREEN # 全屏窗口
【3】、調整窗口大小
OpenCV 提供了 cv2.resizeWindow() 函數來 調整窗口大小。
cv2.resizeWindow(winname: str, width: int, height: int)
其中,參數 winname 是 窗口名稱。參數 width 用來 設置窗口的寬度。參數 height 用來 設置窗口的高度。
如果創建窗口時窗口標誌選用
cv.WINDOW_AUTOSIZE,則resizeWindow()函數無效。
【4】、顯示圖像
OpenCV 提供了 cv2.imShow() 函數來 顯示圖像。
cv2.imshow(winname: str, mat: cv2.typing.MatLike) -> None
其中,參數 winname 是 窗口名稱。參數 mat 是 要顯示的圖像。
在實際使用中,我們可以先通過 cv2.namedWindow() 函數 創建一個窗口,再讓函數 cv2.imshow() 函數 引用該窗口來顯示圖像。我們可以不創建窗口,直接使用 cv2.imshow() 函數 引用一個不存在的窗口,並在其中顯示指定圖像。這樣 cv2.imshow() 函數實際上完成了如下兩步步驟。
cv2.imshow()函數創建一個指定名稱的新窗口。cv2.imshow()函數將顯示圖像在剛創建的窗口內。
在顯示圖像時,我們可能會遇到 error:(-215:Assertion failed)size.width>0&&size.height>0 in function 'cv::imshow' 錯誤。這個錯誤説明當前要顯示的圖像是空的(None),這通常是由於在讀取文件時沒有找到圖像文件造成的。一般來説,沒有找到要讀取的圖像文件,可能是因為文件名錯誤。如果確認要讀取的圖像的完整文件名(路徑名和文件名)沒有錯,那麼往往是工作路徑配置錯誤造成的。
為了避免上述錯誤,可以在讀取圖像前判斷圖像文件是否存在,並在顯示圖像前判斷圖像是否存在。
【5】、等待按鍵操作
OpenCV 提供了 cv2.waitKey() 函數用來 等待按鍵,當用户按下鍵盤按鍵後,該語句會被執行,並獲取返回值。如果在指定的等待時間(參數 delay 指定)內,有按鍵被按下,則返回該按鍵的 ASCII 碼。在等待時間(參數 delay 指定)結束後,如果沒有按鍵被按下,則返回 -1。
cv2.waitKey(delay: int = 0) -> int
參數 delay 表示 等待鍵盤觸發的時間,單位是 ms。當該值是負數或者零時,表示無限等待。該值默認為 0。
如果參數 delay 的值為 0,則程序會一直等待。直到有按下鍵盤按鍵的事件發生時,才返回按鍵的 ASCII 碼,執行後續程序。
如果參數 delay 的值為一個正數,則在這段時間內,程序等待按下鍵盤按鍵。當有按下鍵盤按鍵的事件發生時,返回該按鍵的 ASCII 碼,繼續執行後續程序語句。如果在 delay 參數所指定的時間內一直沒有這樣的事件發生,則在超過等待時間後,返回 -1,繼續執行後續的程序語句。
在實際使用中,可以通過 cv2.waitKey() 函數 獲取按下的按鍵,並針對不同的鍵做出不同的反應,從而實現交互功能。在 Python 中提供了 ord() 函數,該函數可以 用來獲取字符的 ASCII 碼值。因此,在判斷是否按下了某個特定的按鍵時,可以先使用 ord() 函數獲取該特定字符的 ASCII 碼值,再將該值與 cv2.waitKey() 函數的返回值進行比較,從而確定是否按下了某個特定的鍵。
【6】、保存圖像
OpenCV 提供了cv2.imwrite() 函數用來 保存圖像。如果保存成功,則返回 True,如果保存不成功,則返回 False。
cv2.imwrite(filename: str, imgage: cv2.typing.MatLike, params: _typing.Sequence[int] = ...) -> bool
參數 filename 是 要保存的目標文件的完整路徑名,包含文件擴展名。參數 image 是 被保存圖像的名稱。參數 params 是 保存類型參數,是可選的。
【7】、銷燬窗口
OpenCV 提供了 cv2.destroyWindow() 函數用來 釋放(銷燬)指定窗口。
cv2.destroyWindow(winname: str) -> None
其中,參數 winname 是 窗口名稱。
OpenCV 還提供了cv2.destroyWindow() 函數用來 釋放(銷燬)所有窗口。
cv2.destroyAllWindows() -> None
我們新建一個 template.py 文件。
import sys
import cv2
if __name__ == '__main__':
image = cv2.imread("assets/images/1.jpg") # 1.加載圖片
if image is None:
print("加載圖片失敗")
sys.exit(0)
cv2.namedWindow("Window") # 2.創建窗口
cv2.imshow("Window", image) # 3.顯示圖片
key = cv2.waitKey(0) # 4.等待按鍵
if key == ord("s"):
result= cv2.imwrite("assets/images/1.png", image) # 5.保存圖片窗
if result:
print("保存圖片成功")
else:
print("保存圖片失敗")
cv2.destroyAllWindows() # 6.銷燬所有窗口
sys.exit(0) # 7.退出程序
三、視頻採集
視頻是由圖片組成的,視頻的每一幀就是一幅圖片。視頻的幀率一般為 30 幀,即一秒顯示 30 張圖片。我們可以 VideoCapture 類捕獲視頻。如果,我們傳入的參數是數字,則捕獲攝像頭。如果傳入的參數是視頻文件路徑,則打開視頻。
cv2.VideoCapture(filename: str, apiPreference: int = ...)
cv2.VideoCapture(index: int, apiPreference: int = ...)
捕獲到攝像頭之後,我們使用 VideoCapture 類的 isOpened() 方法打開攝像頭。
isOpened() -> bool
打開攝像頭之後,我們可以使用 VideoCapture 類的 read() 方法讀取一幀數據,該方法返回一個 標記 和 讀取的幀數據。如果成功讀取到數據,則標記為 True,否則,標記為 False。
cv2.read(image: cv2.typing.MatLike | None = ...) -> tuple[bool, cv2.typing.MatLike]
最後,我們還需要調用 VideoCapture 類的 release() 方法 釋放資源。
release() -> None
import cv2
if __name__ == '__main__':
cv2.namedWindow("window") # 1.創建窗口
cv2.resizeWindow("window", 800, 600) # 2.調整窗口大小
capture = cv2.VideoCapture(0) # 3.捕獲攝像頭
if capture.isOpened(): # 4.檢查攝像頭是否打開成功
while True: # 循環讀取攝像頭每一幀
# 返回的標記和這一幀數據
flag, frame = capture.read() # 5.讀取攝像頭每一幀
if not flag:
break
cv2.imshow('window', frame) # 6.顯示數據
key = cv2.waitKey(10) # 7.等待用户按鍵
if key == ord('q'): # 按下q鍵退出
break
capture.release() # 8.釋放攝像頭
cv2.destroyAllWindows() # 9.關閉所有窗口
如果我們打開的是一個視頻,運行程序,可以發現視頻好像加速了,這是因為在 cv2.waitKey() 函數中,我們只等待了 10ms 就切換到下一幀數據。
import cv2
if __name__ == '__main__':
cv2.namedWindow("window") # 1.創建窗口
cv2.resizeWindow("window", 800, 600) # 2.調整窗口大小
capture = cv2.VideoCapture("assets/videos/1.mp4") # 3.打開視頻
if capture.isOpened(): # 4.檢查視頻是否打開成功
while True: # 循環讀取攝像頭每一幀
# 返回的標記和這一幀數據
flag, frame = capture.read() # 5.讀取視頻每一幀
if not flag:
break
cv2.imshow('window', frame) # 6.顯示數據
key = cv2.waitKey(1000 // 30) # 7.等待用户按鍵
if key == ord('q'): # 按下q鍵退出
break
capture.release() # 8.關閉視頻
cv2.destroyAllWindows() # 9.關閉所有窗口
四、視頻錄製
在獲取攝像頭之後,我們可以使用 cv2.VideoWriter() 方法 向本地磁盤中寫入一個視頻文件。
cv2.VideoWriter(filename: str, fourcc: int, fps: float, frameSize: cv2.typing.Size, isColor: bool = ...)
其中,參數 filename 是指 視頻文件的保存路徑,參數 fourcc 是指 視頻編/解碼類型,參數 fps 是指 幀率,參數 frameSize 是指 視頻每一幀的寬度和高度,參數 isColor 是指 是否為彩色圖像。
在 OpenCV 中,我們可以使用 cv2.VideoWriter_fourcc() 方法 指定視頻的編碼格式。 cv2.VideoWriter_fourcc() 的參數由 4 個字符組成,這 4 個字符參數構成了編/解碼的 4 字標記。
在我們使用 VideoCapture 類的 read() 方法 讀取一幀數據之後,我們需要使用 VideoWriter 類的 write() 方法 將一幀數據寫入到 VideoWriter 類對象中。當我們最後調用 VideoCapture 類 release() 方法 釋放資源 時,會自動將 VideoWriter 類對象中幀數據寫入到磁盤文件中。
import cv2
from datetime import datetime
if __name__ == '__main__':
esp_key_value = 27
capture = cv2.VideoCapture(0) # 1.打開攝像頭
fourcc = cv2.VideoWriter.fourcc(*"mp4v") # 2.創建視頻編碼
# 獲取當前時間並格式化為字符串
current_time = datetime.now()
formatted_time = current_time.strftime("%Y-%m-%d %H-%M-%S")
filename = f"assets/videos/{formatted_time}.mp4"
video_writer = cv2.VideoWriter(filename, fourcc, 30, (640, 480)) # 3.創建視頻寫入器
if capture.isOpened(): # 4.檢查攝像頭是否打開成功
while True: # 循環讀取攝像頭每一幀
# 返回的標記和這一幀數據
flag, frame = capture.read() # 5.讀取視頻每一幀
if not flag:
break
video_writer.write(frame) # 6.寫入數據
cv2.imshow('window', frame) # 7.顯示數據
key = cv2.waitKey(1) # 8.等待用户按鍵
if key == esp_key_value: # 按下esc鍵退出
break
capture.release() # 10.釋放攝像頭
video_writer.release() # 11.釋放視頻寫入器
cv2.destroyAllWindows() # 12.關閉所有窗口
如果攝像頭正常打開可以採集視頻,但退出程序時沒有保存到本地,且終端中沒有任何錯誤提示,那可能是保存到本地文件的文件名包含了一些不能正常保存的字符,比如
:。如果創建
VideoWriter對象時傳入的frameSize參數不合適,在運行程序的過程中,會報:[ WARN:0@3.567] global cap_ffmpeg.cpp:198 write FFmpeg: Failed to write frame,保存到本地的視頻文件無法打開。在新版本的 OpenCV 中,我們指定視頻編碼格式用
cv2.VideoWriter.fourcc(),在老版本中,需要使用cv2.VideoWriter_fourcc()。
五、鼠標事件
OpenCV 允許我們對窗口的鼠標動作做出響應。我們可以通過 setMouseCallback() 方法 設置鼠標動作的回調函數。
setMouseCallback(windowName: str, onMouse: _typing.Callable[[int, int, int, int, _typing.Any | None], None], param: _typing.Any | None = ...) -> None
其中,參數 windowName 是 窗口的名字,參數 onMouse 是 回調函數,參數 param 是 回調函數的參數。
onMous(event, x, y, flags, userdata)
onMous() 這個回調函數必須包含 5 個參數,其中,參數 event 是 鼠標事件,參數 x,y 是 按下鼠標的座標位置。參數 flags 主要用於 組合鍵,參數 userdata 就是 setMouseCallback() 函數傳入回調函數的參數。
我們常用的鼠標事件如下:
cv2.EVENT_MOUSEMOVE # 鼠標移動
cv2.EVENT_LBUTTONDOWN # 鼠標左鍵按下
cv2.EVENT_RBUTTONDOWN # 鼠標右鍵按下
cv2.EVENT_MBUTTONDOWN # 鼠標中鍵按下
cv2.EVENT_LBUTTONUP # 鼠標左鍵釋放
cv2.EVENT_RBUTTONUP # 鼠標右鍵釋放
cv2.EVENT_MBUTTONUP # 鼠標中鍵釋放
cv2.EVENT_LBUTTONDBLCLK # 鼠標左鍵雙擊
cv2.EVENT_RBUTTONDBLCLK # 鼠標右鍵雙擊
cv2.EVENT_MBUTTONDBLCLK # 鼠標中鍵雙擊
cv2.EVENT_MOUSEWHEEL # 鼠標滾輪滾動
cv2.EVENT_MOUSEHWHEEL # 鼠標水平滾輪滾動
常用的鼠標組合鍵如下:
cv2.EVENT_FLAG_LBUTTON # 按下左鍵
cv2.EVENT_FLAG_RBUTTON # 按下右鍵
cv2.EVENT_FLAG_MBUTTON # 按下中鍵
cv2.EVENT_FLAG_CTRLKEY # 按下Ctrl鍵
cv2.EVENT_FLAG_SHIFTKEY # 按下Shift鍵
cv2.EVENT_FLAG_ALTKEY # 按下Alt鍵
import cv2
import numpy as np
def mouse_callback(event, x, y, flags, params):
print(event, x, y, flags, params)
if __name__ == '__main__':
esp_key_value = 27
cv2.namedWindow("window") # 1.創建窗口
# 窗口的大小為寬640,高480
cv2.resizeWindow("window", 640, 480) # 2.調整窗口大小
# 矩陣為3維,行480,列640
image = np.zeros((480, 640, 3), np.uint8) # 3.創建一個全黑的圖像
cv2.setMouseCallback("window", mouse_callback, "123") # 4.設置鼠標回調函數
while True:
cv2.imshow("window", image) # 5.顯示圖像
key = cv2.waitKey(1) # 6.等待用户按鍵
if key == esp_key_value: # 按下esc鍵退出
break
六、滑動條
在 OpenCV 中,我們可以使用 createTrackbar() 函數 創建一個可以調整數值的滑動條,並將滑動條附加到指定的窗口上。
createTrackbar(trackbarName: str, windowName: str, value: int, count: int, onChange: _typing.Callable[[int], None]) -> None
其中,參數 trackbarName 為 滑動條的名字,參數 windowName 為 窗口的名字,參數 value 為 滑動條的默認值,參數 count 為 滑動條的最大值,最小為 0,參數 onChange 為 數值改動值調用的回調函數。
在創建滑動值之後,我們可以使用 getTrackbarPos() 方法 獲取滑動條的當前值。
getTrackbarPos(trackbarname: str, winname: str) -> int
import cv2
import numpy as np
if __name__ == '__main__':
esp_key_value = 27
cv2.namedWindow("window") # 1.創建窗口
# 窗口的大小為寬640,高480
cv2.resizeWindow("window", 640, 480) # 2.調整窗口大小
# 矩陣為3維,行480,列640
image = np.zeros((480, 640, 3), np.uint8) # 3.創建一個全黑的圖像
cv2.createTrackbar("R", "window", 0, 255, lambda x: None) # 4.創建一個滑動條
cv2.createTrackbar("G", "window", 0, 255, lambda x: None)
cv2.createTrackbar("B", "window", 0, 255, lambda x: None)
while True:
r = cv2.getTrackbarPos("R", "window") # 5.獲取滑動條的位置
g = cv2.getTrackbarPos("G", "window")
b = cv2.getTrackbarPos("B", "window")
image[:] = [b, g, r] # 6.根據滑動條的位置設置圖像的顏色
cv2.imshow("window", image) # 7.顯示圖像
key = cv2.waitKey(1) # 8.等待用户按鍵
if key == esp_key_value: # 按下esc鍵退出
break