輪廓(Contour)是圖像中連續且具有相同灰度值的像素集合,是描述目標形狀、位置和結構的核心特征。在計算機視覺中,輪廓分析廣泛應用于目標定位、形狀識別、尺寸測量等場景(如工業零件檢測、手寫數字識別)。本文將基于用戶提供的代碼,系統解析輪廓檢測的完整流程,包括輪廓提取、屬性計算(重心、面積、周長)、形狀逼近、包圍結構擬合、特征點定位及形狀匹配等核心操作。
一、輪廓檢測基礎:從二值圖像到輪廓提取
輪廓檢測的前提是圖像二值化(只有黑白兩色,前景目標為白色,背景為黑色),因為輪廓是基于像素灰度的連續性提取的。OpenCV 通過cv2.findContours()
函數實現輪廓檢測,需明確其參數含義和返回值。
1. 核心流程:二值化 → 輪廓檢測
用戶代碼的第一步是將彩色圖轉灰度圖,再通過閾值處理得到二值圖,最終提取輪廓。這是輪廓檢測的標準前置流程:
import cv2
import numpy as np# 1. 讀取圖像并預處理(灰度化 + 二值化)
# 處理第一張圖(02.png)
img1 = cv2.imread('02.png')
img1_gray = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY) # 轉灰度圖(減少計算量)
# 閾值二值化:>127設為255(白色,前景),<127設為0(黑色,背景)
ret1, thresh1 = cv2.threshold(img1_gray, 127, 255, cv2.THRESH_BINARY)# 處理第二張圖(1.jpg)
img2 = cv2.imread('1.jpg')
img2_gray = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
ret2, thresh2 = cv2.threshold(img2_gray, 127, 255, cv2.THRESH_BINARY)# 2. 提取輪廓:cv2.findContours(二值圖, 輪廓檢索模式, 輪廓逼近方法)
# 返回值:contours(輪廓列表,每個輪廓是像素坐標數組)、hierarchy(輪廓層級關系)
contours1, hierarchy1 = cv2.findContours(thresh1, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
contours2, hierarchy2 = cv2.findContours(thresh2, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)# 取第一個輪廓(假設圖像中只有一個主要目標)
cnt1 = contours1[0] # 第一張圖的輪廓
cnt2 = contours2[0] # 第二張圖的輪廓# (可選)繪制輪廓以便可視化:cv2.drawContours(原圖, 輪廓列表, 輪廓索引, 顏色, 線寬)
cv2.drawContours(img1, [cnt1], 0, (0, 255, 0), 2) # 綠色繪制輪廓,線寬2
cv2.imshow('Contour of 02.png', img1)
cv2.waitKey(0)
cv2.destroyAllWindows()
2.?cv2.findContours()
關鍵參數解析
參數 | 取值與含義 |
---|---|
輪廓檢索模式(mode) | cv2.RETR_LIST :僅提取所有輪廓,不建立層級關系(簡單場景首選);cv2.RETR_EXTERNAL :僅提取最外層輪廓(排除嵌套輪廓);cv2.RETR_CCOMP :建立兩層層級(外層 / 內層)。 |
輪廓逼近方法(method) | cv2.CHAIN_APPROX_SIMPLE :壓縮輪廓,只保留角點(如矩形只保留 4 個頂點,減少內存);cv2.CHAIN_APPROX_NONE :保留所有輪廓像素點(精度高但內存大)。 |
二、輪廓屬性計算:量化目標的形狀與位置
輪廓屬性是描述目標特征的量化指標,包括重心、面積、周長等,是后續形狀分析的基礎。用戶代碼中注釋了這些屬性的計算方法,以下是完整實現與解析。
1. 矩與重心(目標中心定位)
圖像的 “矩” 是像素灰度的統計特征,通過矩可以計算目標的重心(幾何中心),適用于目標定位(如機器人抓取目標中心)。
# 計算輪廓的矩(moments):包含一階矩、二階矩等統計信息
M1 = cv2.moments(cnt1) # 第一張圖輪廓的矩# 重心坐標公式:cx = m10/m00,cy = m01/m00(m00是零階矩,即輪廓面積)
cx1 = int(M1['m10'] / M1['m00']) # 重心x坐標
cy1 = int(M1['m01'] / M1['m00']) # 重心y坐標# 在原圖上繪制重心(紅色圓點,填充)
cv2.circle(img1, (cx1, cy1), 5, (0, 0, 255), -1)
cv2.putText(img1, f'Center: ({cx1}, {cy1})', (cx1+10, cy1), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1)cv2.imshow('Contour with Center', img1)
cv2.waitKey(0)
cv2.destroyAllWindows()
2. 面積與周長(目標大小量化)
- 面積:輪廓包圍的像素總數(通過
cv2.contourArea()
計算,或矩的m00
值)。 - 周長:輪廓的邊界長度(通過
cv2.arcLength()
計算,需指定是否為 “閉合輪廓”)。
# 1. 計算面積(兩種方法結果一致)
area1 = cv2.contourArea(cnt1) # 直接計算輪廓面積
area1_via_moments = M1['m00'] # 通過矩的m00獲取面積
print(f'Area of cnt1: {area1:.2f} (via contourArea), {area1_via_moments:.2f} (via moments)')# 2. 計算周長:參數2為True表示輪廓是閉合的(如圓形、矩形)
perimeter1 = cv2.arcLength(cnt1, closed=True)
print(f'Perimeter of cnt1: {perimeter1:.2f}')# 在原圖上標注面積和周長
cv2.putText(img1, f'Area: {area1:.0f}', (10, 20), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 0), 1)
cv2.putText(img1, f'Perimeter: {perimeter1:.0f}', (10, 40), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 0), 1)cv2.imshow('Contour with Area & Perimeter', img1)
cv2.waitKey(0)
cv2.destroyAllWindows()
三、形狀逼近與包圍結構:簡化輪廓與邊界擬合
對于復雜輪廓(如帶毛刺的形狀),需要通過形狀逼近簡化輪廓;同時,通過擬合包圍結構(如軸對齊矩形、旋轉矩形、圓),可快速獲取目標的尺寸和姿態。
1. 多邊形逼近(輪廓簡化)
通過cv2.approxPolyDP()
用更少的頂點擬合輪廓,核心參數是epsilon
(逼近精度,通常設為周長的 0.01~0.1 倍),epsilon
越小,擬合越接近原輪廓。
# 計算逼近精度epsilon(周長的10%,可調整)
epsilon1 = 0.1 * cv2.arcLength(cnt1, closed=True)# 多邊形逼近:返回簡化后的輪廓(頂點數組)
approx_cnt1 = cv2.approxPolyDP(cnt1, epsilon1, closed=True)# 繪制原輪廓(綠色)和簡化輪廓(紅色)
cv2.drawContours(img1, [cnt1], 0, (0, 255, 0), 2, label='Original Contour')
cv2.drawContours(img1, [approx_cnt1], 0, (0, 0, 255), 2, label='Approximated Contour')# 標注簡化后的頂點數量
vertex_count = len(approx_cnt1)
cv2.putText(img1, f'Approx Vertices: {vertex_count}', (10, 20), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1)cv2.imshow('Original vs Approximated Contour', img1)
cv2.waitKey(0)
cv2.destroyAllWindows()
應用場景:形狀分類(如通過頂點數判斷是三角形(3 個頂點)、矩形(4 個頂點))。
2. 包圍結構擬合(目標邊界與姿態)
OpenCV 提供多種包圍結構擬合函數,適用于不同場景(如軸對齊裁剪、旋轉校正):
(1)軸對齊包圍框(Bounding Rect)
擬合與圖像坐標軸平行的矩形,僅需左上角坐標(x,y)
和寬高(w,h)
,計算速度快。
# 擬合軸對齊包圍框
x, y, w, h = cv2.boundingRect(cnt1)# 繪制包圍框(藍色)
cv2.rectangle(img1, (x, y), (x + w, y + h), (255, 0, 0), 2)
cv2.putText(img1, f'Bounding Rect: w={w}, h={h}', (x, y-10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 0), 1)cv2.imshow('Bounding Rect', img1)
cv2.waitKey(0)
cv2.destroyAllWindows()
(2)旋轉包圍框(Min Area Rect)
擬合面積最小的矩形(可旋轉,與目標姿態一致),返回矩形的中心、寬高、旋轉角度,適用于目標旋轉校正。
# 擬合旋轉包圍框
min_area_rect = cv2.minAreaRect(cnt1) # 返回 (中心(x,y), (寬,高), 旋轉角度)
# 轉換為矩形的4個頂點坐標(需整數化)
box_vertices = cv2.boxPoints(min_area_rect)
box_vertices = np.int0(box_vertices) # 坐標轉為整數# 繪制旋轉包圍框(紫色)
cv2.drawContours(img1, [box_vertices], 0, (255, 0, 255), 2)
cv2.putText(img1, f'Rot Angle: {min_area_rect[2]:.1f}°', (int(min_area_rect[0][0]), int(min_area_rect[0][1])), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 255), 1)cv2.imshow('Min Area Rect (Rotated)', img1)
cv2.waitKey(0)
cv2.destroyAllWindows()
(3)最小外接圓與橢圓擬合
- 最小外接圓:包圍輪廓的最小圓形,適用于圓形目標的直徑測量。
- 橢圓擬合:用橢圓近似輪廓(要求輪廓至少有 5 個點),適用于橢圓形目標分析。
# 1. 最小外接圓
(x_circle, y_circle), radius = cv2.minEnclosingCircle(cnt1)
center_circle = (int(x_circle), int(y_circle))
radius = int(radius)
# 繪制外接圓(橙色)
cv2.circle(img1, center_circle, radius, (0, 165, 255), 2)# 2. 橢圓擬合(需輪廓點數量≥5)
if len(cnt1) >= 5:ellipse = cv2.fitEllipse(cnt1) # 返回 (中心, (長軸,短軸), 旋轉角度)# 繪制橢圓(青色)cv2.ellipse(img1, ellipse, (255, 255, 0), 2)cv2.imshow('Min Enclosing Circle & Ellipse', img1)
cv2.waitKey(0)
cv2.destroyAllWindows()
四、輪廓特征點與拓撲關系:深入分析目標結構
除了基礎屬性,輪廓的極值點(最左、最右、最上、最下)和凸包缺陷(目標凹陷處)能反映更精細的結構特征,而 “點與輪廓的關系” 可用于判斷點是否在目標內部。
1. 輪廓極值點(目標邊界極限位置)
通過尋找輪廓在 x/y 軸上的極值,確定目標的邊界極限點(如物體的最右端、最頂端):
# 1. 最左點:x坐標最小的點
leftmost = tuple(cnt1[cnt1[:, :, 0].argmin()][0]) # cnt1[:, :, 0]是所有點的x坐標
# 2. 最右點:x坐標最大的點
rightmost = tuple(cnt1[cnt1[:, :, 0].argmax()][0])
# 3. 最上點:y坐標最小的點(圖像y軸向下,所以y最小是最頂端)
topmost = tuple(cnt1[cnt1[:, :, 1].argmin()][0])
# 4. 最下點:y坐標最大的點
bottommost = tuple(cnt1[cnt1[:, :, 1].argmax()][0])# 繪制極值點(不同顏色的圓點)
cv2.circle(img1, leftmost, 5, (255, 0, 0), -1) # 藍色:最左
cv2.circle(img1, rightmost, 5, (0, 255, 0), -1) # 綠色:最右
cv2.circle(img1, topmost, 5, (0, 0, 255), -1) # 紅色:最上
cv2.circle(img1, bottommost, 5, (255, 255, 0), -1)# 青色:最下# 標注極值點
cv2.putText(img1, 'Left', leftmost, cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 0), 1)
cv2.putText(img1, 'Right', rightmost, cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1)cv2.imshow('Contour Extreme Points', img1)
cv2.waitKey(0)
cv2.destroyAllWindows()
2. 凸包與凸包缺陷(目標凹陷檢測)
- 凸包:包含輪廓的最小凸多邊形(類似 “橡皮筋包裹目標”)。
- 凸包缺陷:輪廓與凸包之間的凹陷區域(如手掌輪廓的指縫處),適用于手勢識別、缺陷檢測。
# 1. 計算凸包:returnPoints=False表示返回凸包頂點在原輪廓中的索引(用于后續缺陷計算)
hull = cv2.convexHull(cnt1, returnPoints=False)# 2. 計算凸包缺陷:需先判斷輪廓是否為凸形
is_convex = cv2.isContourConvex(cnt1)
print(f'Is the contour convex? {is_convex}')if not is_convex and len(hull) > 3: # 非凸輪廓且凸包頂點≥3才存在缺陷defects = cv2.convexityDefects(cnt1, hull) # 缺陷列表,每個缺陷含4個參數:s,e,f,d# 遍歷所有缺陷并繪制for i in range(defects.shape[0]):s, e, f, d = defects[i, 0] # s:缺陷起始點索引,e:結束點索引,f:最遠凹陷點索引,d:凹陷深度start = tuple(cnt1[s][0]) # 缺陷起始點end = tuple(cnt1[e][0]) # 缺陷結束點far = tuple(cnt1[f][0]) # 最遠凹陷點# 繪制凸包(黃色)和缺陷(綠色線段連接起始/結束點,紅色圓點標記凹陷點)cv2.drawContours(img1, [cv2.convexHull(cnt1)], 0, (0, 255, 255), 2)cv2.line(img1, start, end, (0, 255, 0), 2)cv2.circle(img1, far, 5, (0, 0, 255), -1)cv2.imshow('Convex Hull & Defects', img1)
cv2.waitKey(0)
cv2.destroyAllWindows()
3. 點與輪廓的關系(內外判斷)
通過cv2.pointPolygonTest()
判斷一個點是否在輪廓內部、外部或邊界上,返回值的含義:
- 正:點在輪廓內部(值為點到輪廓的距離)。
- 負:點在輪廓外部(值為距離的負值)。
- 0:點在輪廓邊界上。
# 測試點:假設兩個點(可根據實際圖像調整坐標)
test_point1 = (500, 500) # 測試點1
test_point2 = (cx1, cy1) # 測試點2(重心,應在內部)# 計算點與輪廓的關系
dist1 = cv2.pointPolygonTest(cnt1, test_point1, measureDist=True) # measureDist=True返回距離
dist2 = cv2.pointPolygonTest(cnt1, test_point2, measureDist=True)# 繪制測試點并標注結果
def draw_test_point(img, point, dist):if dist > 0:color = (0, 255, 0) # 綠色:內部label = f'Inside (dist: {dist:.1f})'elif dist < 0:color = (0, 0, 255) # 紅色:外部label = f'Outside (dist: {dist:.1f})'else:color = (255, 0, 0) # 藍色:邊界label = 'On Contour'cv2.circle(img, point, 5, color, -1)cv2.putText(img, label, (point[0]+10, point[1]), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 1)draw_test_point(img1, test_point1, dist1)
draw_test_point(img1, test_point2, dist2)cv2.imshow('Point-Polygon Relationship', img1)
cv2.waitKey(0)
cv2.destroyAllWindows()
五、形狀匹配:判斷兩個輪廓的相似度
cv2.matchShapes()
通過比較輪廓的Hu 矩(具有尺度、旋轉、平移不變性的特征),計算兩個輪廓的相似度,返回值越小,形狀越相似(通常 < 0.1 認為是同一類形狀),適用于目標識別(如零件分類、手寫數字匹配)。
用戶代碼的最后一步就是形狀匹配,以下是完整實現與結果解讀:
# 形狀匹配:cv2.matchShapes(輪廓1, 輪廓2, 匹配方法, 0)
# 匹配方法:1→cv2.CONTOURS_MATCH_I1,常用且魯棒性較好
match_score = cv2.matchShapes(cnt1, cnt2, cv2.CONTOURS_MATCH_I1, 0.0)# 輸出匹配結果并判斷相似度
print(f'Shape Match Score: {match_score:.4f}')
if match_score < 0.1:print('Conclusion: The two shapes are highly similar!')
else:print('Conclusion: The two shapes are different.')# 可視化兩個輪廓的對比
# 繪制第一張圖的輪廓
cv2.drawContours(img1, [cnt1], 0, (0, 255, 0), 2)
cv2.putText(img1, f'Match Score: {match_score:.4f}', (10, 20), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1)# 繪制第二張圖的輪廓
cv2.drawContours(img2, [cnt2], 0, (0, 255, 0), 2)
cv2.putText(img2, f'Match Score: {match_score:.4f}', (10, 20), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1)# 并排顯示兩張圖
combined_img = np.hstack((img1, img2)) # 水平拼接圖像
cv2.imshow('Shape Matching Comparison', combined_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
關鍵特性:Hu 矩具有尺度不變性(目標放大 / 縮小不影響)、旋轉不變性(目標旋轉不影響)、平移不變性(目標移動不影響),因此形狀匹配不受目標的位置、大小和姿態影響。
總結:輪廓分析的典型流程與應用場景
1. 輪廓分析完整流程
- 預處理:彩色圖→灰度圖→二值化(閾值處理),確保前景目標與背景分離。
- 輪廓檢測:用
cv2.findContours()
提取輪廓,選擇合適的檢索模式和逼近方法。 - 屬性計算:重心(定位)、面積 / 周長(大小)、極值點(邊界)。
- 形狀擬合:多邊形逼近(簡化)、包圍框 / 圓 / 橢圓(邊界與姿態)。
- 結構分析:凸包與缺陷(凹陷檢測)、點與輪廓關系(內外判斷)。
- 形狀匹配:用
cv2.matchShapes()
實現目標識別與分類。
2. 核心應用場景
技術模塊 | 應用場景 |
---|---|
輪廓檢測與屬性 | 工業零件尺寸測量(面積、周長、直徑) |
重心與包圍框 | 機器人目標抓取(定位目標中心與邊界) |
凸包缺陷 | 手勢識別(檢測指縫)、產品缺陷檢測(凹陷) |
形狀匹配 | 零件分類、手寫數字識別、目標檢索 |
通過掌握輪廓分析的全套技術,可解決計算機視覺中 “目標在哪里、是什么形狀、有多大、是否與模板一致” 等核心問題,是實現工業檢測、智能識別等任務的基礎。