一、什麼是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() 函數實際上完成了如下兩步步驟。

  1. cv2.imshow() 函數創建一個指定名稱的新窗口。
  2. 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 類對象中。當我們最後調用 VideoCapturerelease() 方法 釋放資源 時,會自動將 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鼠標事件,參數 xy按下鼠標的座標位置。參數 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