像邊緣、角點、特定的紋理模式等都是圖像的特征。提取這些特征是許多計算機視覺任務的關鍵第一步,例如圖像匹配、對象識別、圖像拼接等。目標檢測則是在圖像中找到特定對象(如人臉、汽車等)的位置。
本部分將涵蓋以下關鍵主題:
- 特征點提取
- 角點檢測(Harris 角點)
- SIFT/SURF 特征(概念介紹)
- ORB 特征提取與匹配
- 輪廓檢測與分析
- 輪廓查找
- 輪廓屬性(面積、周長、形狀)
- 實戰:簡單形狀識別
- 目標檢測入門
- 模板匹配
- 級聯分類器
- 實戰:人臉檢測
我們將提供詳細的解釋和代碼示例。公式部分將使用 LaTeX 格式方便復制和學習。
OpenCV 特征提取與目標檢測 (第三部分)
歡迎來到 OpenCV 教程的第三部分!在前兩部分,我們學習了圖像的基礎操作、像素訪問以及基本的圖像增強和濾波技術。現在,我們將提升一個層次,學習如何從圖像中提取更抽象、更有意義的信息:特征。
為什么需要特征?
想象一下,你要識別一張照片里的某個人。你不會逐個比較兩個圖像中所有像素點是否完全一致,因為光照、角度、距離等變化都會導致像素值改變。你會尋找這個人的眼睛、鼻子、嘴巴等關鍵部位的形狀、相對位置等信息,這些就是特征。
特征提取就是尋找這些有區分度、相對穩定(對光照、旋轉、縮放不敏感)的圖像區域或點。提取到特征后,我們可以用它們來:
- 匹配不同圖像中的同一個物體或場景(如圖像拼接、三維重建)。
- 識別圖像中的特定對象(如人臉、汽車)。
- 跟蹤視頻中移動的物體。
- 分析圖像的結構和內容。
讓我們開始學習這些強大的技術!
1. 特征點提取
特征點是圖像中具有獨特性、易于識別和跟蹤的點,例如角點、圖像紋理變化劇烈的點等。
1.1 角點檢測 (Corner Detection)
角點是兩條或多條邊相交的點,它在各個方向上的像素強度變化都非常明顯。因此,角點是圖像中非常重要的特征點。
Harris 角點檢測
Harris 角點檢測是一種經典的角點檢測算法。其基本思想是:如果在圖像中的一個窗口沿任何方向移動,窗口內的像素灰度都發生顯著變化,那么這個窗口所在的區域可能是一個角點。
OpenCV 提供了 cv2.cornerHarris()
函數來實現 Harris 角點檢測。
Python
import cv2
import numpy as np# --- 練習 1.1: Harris 角點檢測 ---# 1. 加載圖像并轉換為灰度圖 (角點檢測通常在灰度圖上進行)
# Harris 角點檢測的輸入圖像需要是 float32 類型
image_path = 'your_image.jpg'
try:image_color = cv2.imread(image_path)if image_color is None:raise FileNotFoundError(f"圖片文件未找到: {image_path}")image = cv2.cvtColor(image_color, cv2.COLOR_BGR2GRAY)
except FileNotFoundError as e:print(e)print("請確保你的圖片文件存在并位于正確路徑。")image_color = np.zeros((400, 600, 3), dtype=np.uint8)cv2.putText(image_color, "Placeholder Image", (100, 200), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2)image = cv2.cvtColor(image_color, cv2.COLOR_BGR2GRAY)# 將灰度圖像轉換為 float32
image_float = np.float32(image)# 2. 進行 Harris 角點檢測
# 參數: src (輸入圖像, float32), blockSize (用于角點檢測的鄰域大小),
# ksize (Sobel算子的大小,用于計算梯度), k (Harris檢測器參數,通常在0.04-0.06)
harris_corners = cv2.cornerHarris(image_float, blockSize=2, ksize=3, k=0.04)# 3. 標記檢測到的角點
# 結果 harris_corners 是一個浮點圖像,值越大表示越可能是角點
# 我們對其進行閾值處理,并將角點位置在原始彩色圖像上用圓圈標記出來
# 閾值可以根據 harris_corners 的最大值來設置,例如最大值的 1%
threshold = 0.01 * harris_corners.max()# 復制原始彩色圖像,用于標記
output_image = image_color.copy()# 遍歷結果圖像,在R值大于閾值的位置繪制圓圈
# 注意: harris_corners 的維度與輸入圖像相同
# np.where(condition) 會返回滿足條件的元素的索引 (y_coords, x_coords)
strong_corners_indices = np.where(harris_corners > threshold)# strong_corners_indices 是一個元組,第一個元素是所有滿足條件的行的索引,第二個是所有滿足條件的列的索引
y_coords, x_coords = strong_corners_indices# 在原彩色圖上繪制圓圈
for i in range(len(x_coords)):center = (x_coords[i], y_coords[i])cv2.circle(output_image, center, 5, (0, 255, 0), 2) # 綠色圓圈,半徑5,線寬2# 4. 顯示結果
cv2.imshow('Original Image', image_color)
cv2.imshow('Harris Corners', output_image)cv2.waitKey(0)
cv2.destroyAllWindows()print("\n--- 練習 1.1 完成 ---")
練習提示:
- 嘗試調整
blockSize
、ksize
和k
參數,觀察檢測到的角點數量和位置的變化。 - 調整閾值 (
threshold
) 也會顯著影響結果。較高的閾值會檢測到更少的、更明顯的角點。
1.2 SIFT/SURF 特征 (概念介紹)
SIFT (Scale-Invariant Feature Transform) 和 SURF (Speeded Up Robust Features) 是兩種非常著名的、對圖像縮放和旋轉具有不變性的局部特征點檢測和描述算法。
- 它們都能在圖像中找到具有獨特性的特征點(稱為關鍵點)。
- 對于每個關鍵點,它們都會計算一個描述符(一串數字),用來描述關鍵點周圍區域的紋理信息。這個描述符對于光照、視角、縮放、旋轉都有一定的魯棒性。
- SIFT 和 SURF 算法相對復雜且計算量較大。
重要提示: SIFT 和 SURF 算法在某些國家是受專利保護的。OpenCV 的主模塊 (opencv-python
) 可能不包含這些算法。你可能需要安裝 opencv-contrib-python
庫來使用它們。但是,由于專利問題和后續出現的更優秀的免費算法,SIFT 和 SURF 在學術界和工業界的使用受到一定限制。因此,本教程僅概念性介紹它們,我們將把重點放在一個免費且高效的替代品:ORB 特征。
1.3 ORB 特征提取與匹配
ORB (Oriented FAST and Rotated BRIEF) 是一種高效且免費的特征點檢測和描述算法。它結合了 FAST 算法來檢測關鍵點,并對 BRIEF 描述符進行了改進,使其具有方向和一定的尺度不變性。
ORB 的核心是兩個主要部分:
- 檢測器: 使用改進的 FAST 算法來尋找關鍵點。FAST 算法速度非常快,通過比較中心像素與周圍圓環像素的灰度值來判斷是否為關鍵點。ORB 在此基礎上增加了多尺度檢測和方向信息。
- 描述符: 使用改進的 BRIEF 描述符來描述關鍵點周圍的區域。BRIEF 描述符通過比較關鍵點周圍隨機選取的像素對的灰度值來生成一個二進制串。ORB 對 BRIEF 進行了旋轉使其具有旋轉不變性。
特征匹配
提取到圖像的特征點及其描述符后,我們可以進行特征匹配。特征匹配的目的是在兩幅圖像中找到對應于同一物理點的特征點對。常用的匹配方法是比較描述符之間的相似度(例如,對于 ORB 的二進制描述符,可以使用漢明距離)。
BFMatcher (Brute-Force Matcher)
BFMatcher 是一種簡單的特征匹配器。它對第一組描述符中的每個描述符,計算與第二組描述符中所有描述符的距離,然后找到最近的一個或幾個。
通常,我們會使用 KNN (K-Nearest Neighbors) 匹配,找到每個描述符的 k 個最近鄰,然后應用 Lowe's Ratio Test 進行過濾:如果最佳匹配的距離與次佳匹配的距離之比小于一個閾值(例如 0.75),則認為這是一個好的匹配點。
Python
import cv2
import numpy as np# --- 練習 1.3: ORB 特征提取與匹配 ---# 1. 加載兩張圖像 (最好包含一些相同的物體或場景)
# 請替換成你自己的圖片路徑
image_path1 = 'your_image1.jpg'
image_path2 = 'your_image2.jpg'
try:image1 = cv2.imread(image_path1, cv2.IMREAD_GRAYSCALE) # ORB通常在灰度圖上工作image2 = cv2.imread(image_path2, cv2.IMREAD_GRAYSCALE)if image1 is None:raise FileNotFoundError(f"圖片文件未找到: {image_path1}")if image2 is None:raise FileNotFoundError(f"圖片文件未找到: {image_path2}")print("成功加載兩張圖像。")
except FileNotFoundError as e:print(e)print("請確保你的圖片文件存在并位于正確路徑。")print("將使用模擬圖像代替,無法演示實際匹配效果。")# 如果圖片加載失敗,創建兩個簡單的模擬圖像image1 = np.zeros((300, 400), dtype=np.uint8)cv2.putText(image1, "Image 1 Placeholder", (50, 150), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2)image2 = np.zeros((300, 400), dtype=np.uint8)cv2.putText(image2, "Image 2 Placeholder", (50, 150), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2)# 2. 創建 ORB 特征檢測器對象
orb = cv2.ORB_create(nfeatures=5000, # 最多檢測的特征點數量scaleFactor=1.2, # 圖像金字塔的縮放因子nlevels=8, # 圖像金字塔的層數edgeThreshold=31, # 忽略邊緣區域的像素數量firstLevel=0, # 金字塔的第一層WTA_K=2, # 描述符生成算法scoreType=cv2.ORB_HARRIS_SCORE, # 使用Harris角點響應或FAST角點響應patchSize=31 # 描述符計算的區域大小
)# 3. 檢測關鍵點和計算描述符
# kp: 關鍵點列表
# des: 描述符 (numpy數組)
kp1, des1 = orb.detectAndCompute(image1, None)
kp2, des2 = orb.detectAndCompute(image2, None)# 確保描述符是 float32 類型,有些匹配器可能需要
# ORB 描述符是二進制的,這里不需要轉換,但對于SIFT/SURF可能需要print(f"圖像1檢測到 {len(kp1)} 個關鍵點")
print(f"圖像2檢測到 {len(kp2)} 個關鍵點")# 4. 創建 Brute-Force 匹配器
# 對于ORB的二進制描述符,使用 cv2.NORM_HAMMING
bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=False) # crossCheck=True 會進行雙向匹配過濾# 5. 進行特征匹配 (使用 KNN 匹配)
# k=2 表示為每個關鍵點找到 2 個最近鄰
matches = bf.knnMatch(des1, des2, k=2)# 6. 應用 Lowe's Ratio Test 過濾好的匹配點
good_matches = []
ratio_threshold = 0.75 # 比例閾值for m, n in matches:# m是最佳匹配,n是次佳匹配if m.distance < ratio_threshold * n.distance:good_matches.append(m)print(f"經過 Lowe's Ratio Test 過濾后,找到 {len(good_matches)} 個好的匹配點")# 7. 繪制匹配結果
# 參數: img1, kp1, img2, kp2, matches, outImg, flags
# flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS 表示不繪制沒有匹配點的關鍵點
match_image = cv2.drawMatchesKnn(image1, kp1, image2, kp2, [good_matches], None, flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)# 8. 顯示結果
cv2.imshow('ORB Feature Matches', match_image)cv2.waitKey(0)
cv2.destroyAllWindows()print("\n--- 練習 1.3 完成 ---")
練習提示:
- 嘗試使用包含相同物體但拍攝角度、距離略有差異的兩張圖片。
- 調整
ORB_create()
函數中的參數,例如nfeatures
,觀察檢測到的關鍵點數量和匹配結果的變化。 - 調整 Lowe's Ratio Test 中的
ratio_threshold
,較低的閾值會得到更嚴格(更少但更可靠)的匹配點。
2. 輪廓檢測與分析
輪廓是連接所有連續的、具有相同顏色或亮度的點的曲線。它們代表了物體的邊界。輪廓在物體檢測、形狀分析和識別、圖像分割等領域非常有用。
查找輪廓通常在二值圖像(只有黑白兩種顏色)上進行。
2.1 輪廓查找
OpenCV 提供了 cv2.findContours()
函數來查找圖像中的輪廓。
Python
import cv2
import numpy as np# --- 練習 2.1: 輪廓查找 ---# 1. 加載圖像并轉換為灰度圖
image_path = 'your_image.jpg'
try:image_color = cv2.imread(image_path)if image_color is None:raise FileNotFoundError(f"圖片文件未找到: {image_path}")image_gray = cv2.cvtColor(image_color, cv2.COLOR_BGR2GRAY)
except FileNotFoundError as e:print(e)print("請確保你的圖片文件存在并位于正確路徑。")image_color = np.zeros((400, 600, 3), dtype=np.uint8)cv2.putText(image_color, "Placeholder Image", (100, 200), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2)image_gray = cv2.cvtColor(image_color, cv2.COLOR_BGR2GRAY)# 2. 對灰度圖進行二值化處理 (閾值分割)
# findContours 函數通常在二值圖像上操作
# cv2.THRESH_BINARY: 大于閾值的設為最大值 (255), 否則設為0
# cv2.THRESH_OTSU: 使用Otsu算法自動尋找最佳閾值
ret, binary_image = cv2.threshold(image_gray, 127, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)# 3. 查找輪廓
# cv2.findContours() 函數會返回輪廓列表和對應的層級關系
# 參數: image (輸入圖像, 8位單通道,通常是二值圖), mode (輪廓檢索模式), method (輪廓近似方法)
# RETR_TREE: 檢索所有輪廓并建立完整的層級樹
# CHAIN_APPROX_SIMPLE: 壓縮水平、垂直和對角線段,只保留端點
# 注意: OpenCV 4.x 版本 findContours 返回值順序變了,前面是image,后面是輪廓和層級
contours, hierarchy = cv2.findContours(binary_image, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)print(f"檢測到 {len(contours)} 個輪廓")# 4. 在原始彩色圖像上繪制所有輪廓
# 參數: image (繪制目標圖像), contours (輪廓列表), contourIdx (要繪制的輪廓索引,-1表示所有),
# color (顏色), thickness (線寬)
# 復制原始彩色圖像,以免修改原圖
image_with_contours = image_color.copy()
cv2.drawContours(image_with_contours, contours, -1, (0, 255, 0), 2) # 綠色,線寬2# 5. 顯示結果
cv2.imshow('Original Image', image_color)
cv2.imshow('Binary Image', binary_image)
cv2.imshow('Contours', image_with_contours)cv2.waitKey(0)
cv2.destroyAllWindows()print("\n--- 練習 2.1 完成 ---")
練習提示:
cv2.findContours()
函數會改變輸入圖像,所以在查找輪廓時通常傳入二值圖像的副本或查找后不再使用該二值圖像。- 嘗試不同的
mode
(如cv2.RETR_LIST
不建立層級關系) 和method
(如cv2.CHAIN_APPROX_NONE
存儲所有輪廓點) 參數,觀察它們對結果的影響。
2.2 輪廓屬性
一旦找到輪廓,我們可以計算它們的各種屬性來描述輪廓的形狀、大小、位置等。
Python
import cv2
import numpy as np# --- 練習 2.2: 輪廓屬性 ---# (沿用上一個練習的輪廓查找代碼)
image_path = 'your_image.jpg'
try:image_color = cv2.imread(image_path)if image_color is None:raise FileNotFoundError(f"圖片文件未找到: {image_path}")image_gray = cv2.cvtColor(image_color, cv2.COLOR_BGR2GRAY)
except FileNotFoundError as e:print(e)print("請確保你的圖片文件存在并位于正確路徑。")image_color = np.zeros((400, 600, 3), dtype=np.uint8)cv2.putText(image_color, "Placeholder Image", (100, 200), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2)image_gray = cv2.cvtColor(image_color, cv2.COLOR_BGR2GRAY)ret, binary_image = cv2.threshold(image_gray, 127, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)# 注意: findContours 修改了 binary_image,所以通常傳遞副本
contours, hierarchy = cv2.findContours(binary_image.copy(), cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)# 復制原始彩色圖像用于可視化屬性
image_with_properties = image_color.copy()print("\n--- 輪廓屬性 ---")
# 遍歷所有找到的輪廓
for i, contour in enumerate(contours):# 忽略非常小的輪廓(可能是噪聲)if cv2.contourArea(contour) < 100:continueprint(f"\n輪廓 {i+1}:")# 面積 (Area)area = cv2.contourArea(contour)print(f" 面積: {area:.2f}")# 周長 (Perimeter / Arc Length)# 參數: curve (輪廓), closed (是否閉合)perimeter = cv2.arcLength(contour, closed=True)print(f" 周長: {perimeter:.2f}")# 矩 (Moments) - 用于計算中心、面積等# M['m00'] 就是面積M = cv2.moments(contour)# 計算中心點 (Centroid)if M['m00'] != 0:cx = int(M['m10'] / M['m00'])cy = int(M['m01'] / M['m00'])print(f" 中心點 (cx, cy): ({cx}, {cy})")# 在中心點繪制圓圈cv2.circle(image_with_properties, (cx, cy), 5, (0, 0, 255), -1) # 紅色實心圓# 外接矩形 (Bounding Rectangle) - 直立矩形# 參數: points (輪廓點)x, y, w, h = cv2.boundingRect(contour)print(f" 外接矩形 (x, y, w, h): ({x}, {y}, {w}, {h})")# 繪制外接矩形cv2.rectangle(image_with_properties, (x, y), (x+w, y+h), (255, 0, 0), 2) # 藍色# 最小外接圓 (Minimum Enclosing Circle)# 參數: points (輪廓點)(center_x, center_y), radius = cv2.minEnclosingCircle(contour)center_circle = (int(center_x), int(center_y))radius_int = int(radius)print(f" 最小外接圓 (中心: {center_circle}, 半徑: {radius_int})")# 繪制最小外接圓cv2.circle(image_with_properties, center_circle, radius_int, (0, 255, 255), 2) # 黃色# 輪廓近似 (Contour Approximation) - 簡化輪廓# 使用 Ramer-Douglas-Peucker 算法# 參數: curve (輪廓), epsilon (近似精度,原始輪廓與近似輪廓之間的最大距離), closed (是否閉合)epsilon = 0.02 * perimeter # 精度通常設為周長的百分比approx = cv2.approxPolyDP(contour, epsilon, closed=True)print(f" 近似輪廓點數量: {len(approx)}")# 繪制近似輪廓 (可選,可以用 drawContours)# 6. 顯示結果
cv2.imshow('Original Image', image_color)
cv2.imshow('Contours with Properties', image_with_properties)cv2.waitKey(0)
cv2.destroyAllWindows()print("\n--- 練習 2.2 完成 ---")
練習提示:
- 理解不同屬性的含義及其計算方法。
- 注意如何使用這些屬性來描述輪廓的特征。例如,可以通過比較輪廓面積和其外接矩形面積的比例來判斷輪廓的“緊湊”程度。
2.3 實戰:簡單形狀識別
結合輪廓查找和輪廓屬性,我們可以嘗試識別一些簡單的幾何形狀,如矩形、圓形。
思路:
3. 目標檢測入門
目標檢測比簡單的形狀識別更進一步,它旨在圖像中找到特定類別的對象(如人臉、汽車、貓等)的位置,并通常用一個邊界框標記出來。
3.1 模板匹配 (Template Matching)
模板匹配是一種簡單直觀的目標檢測方法。它需要一個已知的模板圖像(你要尋找的對象的小圖像),然后在源圖像中滑動這個模板,計算模板與源圖像中對應區域的相似度。相似度最高的區域就被認為是找到了模板。
3.2 級聯分類器 (Cascade Classifiers)
級聯分類器是一種基于機器學習的目標檢測方法。它使用一系列經過訓練的分類器(弱分類器)組成一個級聯結構。在檢測時,圖像區域會依次通過這些分類器。如果在任何一個階段被判定為非目標,就會被立即丟棄,大大提高了檢測速度。只有通過所有分類器的區域才會被認為是目標。
OpenCV 提供了一個使用 Haar-like 特征(類似于簡單的邊緣、線條特征)訓練的級聯分類器框架。盡管現代深度學習方法在許多目標檢測任務上取得了更好的效果,但級聯分類器(尤其是用于人臉檢測)因為其速度快、實時性好,在一些場景下仍然有應用價值。
使用級聯分類器進行目標檢測需要:
Python
import cv2
import numpy as np# --- 練習 3.2: 級聯分類器入門 ---# 1. 加載預訓練的級聯分類器模型
# 你需要找到 OpenCV 安裝目錄下的 haarcascades 文件夾,或者從網上下載
# 例如: '/usr/local/share/opencv4/haarcascades/haarcascade_frontalface_default.xml'
# 或者在項目目錄下放置下載好的xml文件
cascade_path = 'haarcascade_frontalface_default.xml' # 替換成你的xml文件路徑# 創建級聯分類器對象
face_cascade = cv2.CascadeClassifier()# 加載分類器模型
if not face_cascade.load(cv2.samples.findFile(cascade_path)):print(f"錯誤: 無法加載級聯分類器文件: {cascade_path}")print("請檢查文件路徑是否正確,或從以下位置下載:")print("https://github.com/opencv/opencv/tree/master/data/haarcascades")# 如果加載失敗,創建一個空的分類器,后續檢測會失敗face_cascade = None# 2. 加載圖像并轉換為灰度圖 (級聯分類器通常在灰度圖上工作)
image_path = 'your_image_with_faces.jpg' # 最好找一張包含人臉的圖片
try:image_color = cv2.imread(image_path)if image_color is None:raise FileNotFoundError(f"圖片文件未找到: {image_path}")print(f"成功加載圖像: {image_path}")image_gray = cv2.cvtColor(image_color, cv2.COLOR_BGR2GRAY)image_gray = cv2.equalizeHist(image_gray) # 可選: 直方圖均衡化,有時能提高檢測效果except FileNotFoundError as e:print(e)print("請確保你的圖片文件存在并位于正確路徑。")print("將使用模擬圖像代替,無法演示實際檢測效果。")# 創建模擬圖像,無法包含真實人臉image_color = np.zeros((400, 600, 3), dtype=np.uint8)cv2.putText(image_color, "Placeholder Image", (100, 200), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2)image_gray = cv2.cvtColor(image_color, cv2.COLOR_BGR2GRAY)# 3. 進行目標檢測 (人臉檢測)
# 參數: image (灰度圖), scaleFactor (圖像縮放因子), minNeighbors (每個矩形應該具有的鄰近個數),
# minSize (最小可能對象大小), maxSize (最大可能對象大小)
if face_cascade: # 確保分類器加載成功faces = face_cascade.detectMultiScale(image_gray,scaleFactor=1.1, # 每次圖像尺寸減小的比例minNeighbors=5, # 每個目標至少被檢測到5次才被確認為目標minSize=(30, 30) # 最小目標尺寸 (寬, 高)# maxSize=(200, 200) # 最大目標尺寸)
else:faces = [] # 如果分類器加載失敗,檢測結果為空列表print(f"檢測到 {len(faces)} 個人臉")# 4. 在原始彩色圖像上繪制檢測到的目標 (矩形框)
output_image = image_color.copy()
for (x, y, w, h) in faces:cv2.rectangle(output_image, (x, y), (x+w, y+h), (255, 0, 0), 2) # 藍色矩形框# 5. 顯示結果
cv2.imshow('Original Image', image_color)
cv2.imshow('Detected Faces', output_image)cv2.waitKey(0)
cv2.destroyAllWindows()print("\n--- 練習 3.2 完成 ---")
練習提示:
3.3 實戰:人臉檢測
這個實戰練習就是將上一節學習的級聯分類器應用到人臉檢測任務上。你只需要確保有包含人臉的圖片以及正確加載人臉分類器的 XML 文件。
- 找到輪廓。
- 過濾掉過小或過大的輪廓。
- 對輪廓進行近似。
- 根據近似輪廓的頂點數量來判斷形狀:
- 如果頂點數量接近 4 且是凸多邊形,可能是矩形/正方形。
- 如果頂點數量很多(近似后變化不大)且是凸多邊形,可能是圓形。
- 進一步細化:對于頂點數為 4 的,可以檢查長寬比來區分正方形和矩形。對于圓形,可以檢查面積與最小外接圓面積的比例。Python
import cv2 import numpy as np# --- 實戰練習: 簡單形狀識別 ---# 1. 加載圖像并轉換為灰度圖 image_path = 'your_image.jpg' # 最好找一張有明顯幾何圖形的圖片 try:image_color = cv2.imread(image_path)if image_color is None:raise FileNotFoundError(f"圖片文件未找到: {image_path}")image_gray = cv2.cvtColor(image_color, cv2.COLOR_BGR2GRAY) except FileNotFoundError as e:print(e)print("請確保你的圖片文件存在并位于正確路徑。")# 創建一個包含簡單形狀的模擬圖像image_color = np.zeros((400, 400, 3), dtype=np.uint8)cv2.rectangle(image_color, (50, 50), (150, 150), (255, 0, 0), -1) # 藍色方塊cv2.circle(image_color, (300, 100), 50, (0, 255, 0), -1) # 綠色圓圈cv2.rectangle(image_color, (200, 200), (350, 300), (0, 0, 255), -1) # 紅色矩形image_gray = cv2.cvtColor(image_color, cv2.COLOR_BGR2GRAY)# 2. 二值化圖像 ret, binary_image = cv2.threshold(image_gray, 127, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)# 3. 查找輪廓 (注意使用副本) contours, hierarchy = cv2.findContours(binary_image.copy(), cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)# 復制原始彩色圖像用于繪制結果 output_image = image_color.copy()print("\n--- 形狀識別結果 ---")# 4. 遍歷輪廓并進行識別 for contour in contours:# 過濾小輪廓area = cv2.contourArea(contour)if area < 1000: # 過濾面積小于1000的輪廓continue# 計算周長perimeter = cv2.arcLength(contour, closed=True)# 輪廓近似epsilon = 0.04 * perimeter # 提高 epsilon 可以減少近似點數量,有助于識別多邊形approx = cv2.approxPolyDP(contour, epsilon, closed=True)# 獲取外接矩形屬性x, y, w, h = cv2.boundingRect(approx)aspect_ratio = float(w) / hbbox_area = w * h# 識別形狀shape = "未知"M = cv2.moments(contour)if M['m00'] != 0:cx = int(M['m10'] / M['m00'])cy = int(M['m01'] / M['m00'])center = (cx, cy)else:center = (x + w // 2, y + h // 2) # 使用外接矩形中心代替# 繪制輪廓并標注形狀cv2.drawContours(output_image, [contour], -1, (0, 255, 0), 2) # 繪制當前輪廓num_vertices = len(approx)if num_vertices == 3:shape = "三角形"elif num_vertices == 4:# 判斷是正方形還是矩形if 0.9 <= aspect_ratio <= 1.1 and 0.85 <= area / bbox_area <= 1.15: # 檢查長寬比和面積比例shape = "正方形"else:shape = "矩形"elif num_vertices == 5:shape = "五邊形"else:# 對于頂點數量多的輪廓,可能是圓形或其它復雜形狀# 可以通過檢查面積與最小外接圓面積的比例來判斷是否接近圓形(center_circle_x, center_circle_y), radius = cv2.minEnclosingCircle(contour)circle_area = np.pi * radius**2if area / circle_area > 0.8: # 如果輪廓面積占最小外接圓面積的大部分shape = "圓形"else:shape = "其他" # 或者 "復雜形狀"print(f" 輪廓點數量: {len(contour)}, 近似點數量: {num_vertices}, 識別為: {shape}")# 在中心點附近顯示形狀名稱cv2.putText(output_image, shape, (center[0] - 20, center[1]), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 2)# 5. 顯示結果 cv2.imshow('Original Image', image_color) cv2.imshow('Simple Shape Recognition', output_image)cv2.waitKey(0) cv2.destroyAllWindows()print("\n--- 實戰練習: 簡單形狀識別 完成 ---")
實戰提示:
- 這個簡單的形狀識別方法對于理想的幾何圖形效果較好,對于真實照片中形狀會受光照、遮擋、變形等影響,效果會下降。
- 調整
epsilon
參數會影響近似輪廓的頂點數量,從而影響識別結果。 - 圓形識別可以通過檢查面積與最小外接圓面積的比例、或者周長與半徑的關系等多種方法來實現。
- Python
import cv2 import numpy as np# --- 練習 3.1: 模板匹配 ---# 1. 加載源圖像和模板圖像 # 請替換成你自己的圖片路徑,確保 template_image.jpg 是 source_image.jpg 的一部分 source_image_path = 'your_source_image.jpg' template_image_path = 'your_template_image.jpg' # 模板圖像要小于源圖像try:source_image = cv2.imread(source_image_path)template_image = cv2.imread(template_image_path)if source_image is None:raise FileNotFoundError(f"源圖片文件未找到: {source_image_path}")if template_image is None:raise FileNotFoundError(f"模板圖片文件未找到: {template_image_path}")print("成功加載源圖像和模板圖像。") except FileNotFoundError as e:print(e)print("請確保你的圖片文件存在并位于正確路徑。")print("將使用模擬圖像代替,無法演示實際匹配效果。")# 創建模擬圖像source_image = np.zeros((300, 400, 3), dtype=np.uint8)cv2.circle(source_image, (200, 150), 50, (0, 255, 0), -1)cv2.putText(source_image, "Source (Green Circle)", (50, 250), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)template_image = np.zeros((100, 100, 3), dtype=np.uint8)cv2.circle(template_image, (50, 50), 40, (0, 255, 0), -1)cv2.putText(template_image, "Template", (10, 80), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1)# 確保兩張圖像都是單通道或三通道,且類型一致 # 模板匹配也可以在灰度圖上進行,通常更快 source_gray = cv2.cvtColor(source_image, cv2.COLOR_BGR2GRAY) template_gray = cv2.cvtColor(template_image, cv2.COLOR_BGR2GRAY)# 獲取模板圖像的寬度和高度 w, h = template_gray.shape[::-1] # shape返回(h, w),[::-1]反轉得到(w, h)# 2. 進行模板匹配 # 參數: image (源圖像), templ (模板圖像), method (匹配方法) # cv2.TM_CCOEFF_NORMED: 歸一化相關系數匹配,值越接近1越好 # cv2.TM_SQDIFF_NORMED: 歸一化平方差匹配,值越接近0越好 method = cv2.TM_CCOEFF_NORMED result = cv2.matchTemplate(source_gray, template_gray, method)# 3. 查找最佳匹配位置 # cv2.minMaxLoc() 找到匹配結果矩陣中的最小值、最大值及其位置 min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)# 根據匹配方法,最佳匹配位置可能是 min_loc 或 max_loc # 對于 TM_CCOEFF_NORMED, TM_CCORR_NORMED, TM_CCOEFF,最大值表示最佳匹配 # 對于 TM_SQDIFF, TM_SQDIFF_NORMED, TM_CCORR,最小值表示最佳匹配 if method in [cv2.TM_SQDIFF, cv2.TM_SQDIFF_NORMED]:top_left = min_loc # 最佳匹配的左上角坐標 else:top_left = max_loc# 計算最佳匹配區域的右下角坐標 bottom_right = (top_left[0] + w, top_left[1] + h)# 4. 在源圖像上繪制匹配結果 (矩形框) # 復制源圖像以免修改原圖 output_image = source_image.copy() cv2.rectangle(output_image, top_left, bottom_right, (0, 0, 255), 2) # 紅色矩形框# 5. 顯示結果 cv2.imshow('Source Image', source_image) cv2.imshow('Template Image', template_image) cv2.imshow('Template Matching Result', output_image) # 匹配結果矩陣通常數值范圍較大,直接顯示可能不直觀,可以先歸一化再顯示 # cv2.imshow('Match Result Matrix', cv2.normalize(result, None, 0, 255, cv2.NORM_MINMAX, cv2.CV_8U))cv2.waitKey(0) cv2.destroyAllWindows()print("\n--- 練習 3.1 完成 ---")
練習提示:
- 模板匹配對模板圖像的大小、旋轉、光照變化非常敏感。如果模板和目標在源圖像中的外觀有較大差異,匹配可能會失敗。
- 嘗試不同的
method
參數,比較它們的效果。 cv2.matchTemplate
也可以返回多個匹配區域(例如,使用閾值過濾匹配結果矩陣)。- 一個預先訓練好的分類器模型文件(通常是 XML 格式)。OpenCV 庫自帶了一些常用的分類器模型,如人臉、眼睛等。你可以在 OpenCV 的 GitHub 倉庫中找到它們(
opencv/data/haarcascades
或opencv_extra/testdata/cv/face/
)。 - 使用
cv2.CascadeClassifier
類加載模型。 - 使用
cascade.detectMultiScale()
方法在圖像中進行檢測。 - 找到并替換
cascade_path
為你環境中正確的 XML 文件路徑。 - 嘗試不同的
scaleFactor
和minNeighbors
參數。減小scaleFactor
可以檢測到更小的目標,但速度變慢;增大minNeighbors
可以減少誤檢,但可能漏掉一些目標。 - 嘗試使用不同的圖像進行測試。
- 除了人臉,OpenCV 還提供了眼睛、身體等其他預訓練的級聯分類器。
- Python
import cv2 import numpy as np# --- 實戰練習: 人臉檢測 ---# 1. 指定人臉分類器模型文件路徑 # 這是 OpenCV 官方提供的模型,你需要確保你的OpenCV安裝目錄有這個文件,或者手動下載放在項目目錄 # 例如: '/usr/local/share/opencv4/haarcascades/haarcascade_frontalface_default.xml' # 或者直接放在當前腳本同一目錄下 # 你可以從這里下載: https://github.com/opencv/opencv/blob/master/data/haarcascades/haarcascade_frontalface_default.xml cascade_filename = 'haarcascade_frontalface_default.xml'# 創建人臉分類器對象 face_cascade = cv2.CascadeClassifier()# 嘗試加載模型文件 if not face_cascade.load(cv2.samples.findFile(cascade_filename)):print(f"錯誤: 無法加載人臉分類器文件: {cascade_filename}")print("請確保文件存在,并可能需要手動下載或找到OpenCV安裝路徑下的haarcascades文件夾。")print("使用一個空的分類器,后續檢測將不會找到任何目標。")face_cascade = None # 設置為 None 表示加載失敗# 2. 加載你要檢測人臉的圖像 image_path = 'your_image_to_detect_faces.jpg' # 請替換為你想要檢測人臉的圖片路徑 try:image_color = cv2.imread(image_path)if image_color is None:raise FileNotFoundError(f"圖片文件未找到: {image_path}")print(f"成功加載圖像: {image_path}") except FileNotFoundError as e:print(e)print("請確保你的圖片文件存在并位于正確路徑。")# 創建一個空白圖像作為備用image_color = np.zeros((400, 600, 3), dtype=np.uint8)cv2.putText(image_color, "Placeholder Image", (100, 200), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2)# 3. 將圖像轉換為灰度圖 (級聯分類器工作在灰度圖上) gray_image = cv2.cvtColor(image_color, cv2.COLOR_BGR2GRAY) # 可以選擇性地進行直方圖均衡化以增強對比度 # gray_image = cv2.equalizeHist(gray_image)# 4. 進行人臉檢測 # 使用 detectMultiScale 方法 # 參數: scaleFactor, minNeighbors, minSize, maxSize (這些參數會影響檢測精度和速度) if face_cascade: # 只有在分類器加載成功時才執行檢測faces = face_cascade.detectMultiScale(gray_image,scaleFactor=1.1, # 圖像縮小比例minNeighbors=5, # 構成檢測目標的最小鄰近矩形數量minSize=(30, 30) # 最小檢測窗口大小) else:faces = [] # 如果分類器加載失敗,結果為空print(f"檢測到 {len(faces)} 個人臉")# 5. 在原始圖像上繪制檢測到的人臉矩形框 output_image = image_color.copy() # faces 變量是一個包含檢測到的人臉矩形信息的列表,每個元素是 (x, y, w, h) for (x, y, w, h) in faces:cv2.rectangle(output_image, (x, y), (x+w, y+h), (0, 255, 0), 2) # 綠色矩形框# 6. 顯示結果 cv2.imshow('Original Image', image_color) cv2.imshow('Face Detection Result', output_image)# 7. 等待按鍵,然后關閉窗口 cv2.waitKey(0) cv2.destroyAllWindows()print("\n--- 實戰練習: 人臉檢測 完成 ---")
實戰提示:
- 最關鍵的一步是正確指定
cascade_filename
的路徑。 - 嘗試使用不同光照、角度、表情的人臉圖片進行測試,觀察檢測效果。
- 調整
detectMultiScale
的參數,尤其是scaleFactor
和minNeighbors
,會顯著影響檢測結果。