連通域的外接矩形¶
概要¶
阿凱在連通域的輪廓點集 中介紹了尋找二值化圖像輪廓的方法. 接下來我們還想找到輪廓對應的矩形區域(外接矩形).
尋找外接矩形有兩種策略. 一種是尋找輪廓邊緣的部分, 找到最外面的那個外接矩形, 為了區分, 我們稱之為正外接矩形 boundingRect
, 如下圖綠色矩形部分.
另外一種策略是矩形可以旋轉, 找到面積最小的矩形, 剛剛好可以把輪廓套在里面,我們稱之為*最小外接矩形 * minAreaRect
, 如下圖藍色矩形部分.
圖片來源: Python+OpenCV教程14:輪廓特征
接下來阿凱會分別介紹opencv中的這兩個函數.
keywords 外接矩形 最小外借矩形 boundingRect minAreaRect
1. 知識回顧-findContours¶
阿凱手繪了這幾個數字. 接下來呢,阿凱想把這些數字所在的矩形區域表示出來, 并截取出單獨的圖片.
在連通域的輪廓點集 中阿凱介紹了四種提取輪廓的模式, 那么我們應該用哪個呢?
首先問幾個問題:
- 我們是否關心內輪廓 ? -> 否
如果識別內輪廓的話, 以6舉例, 就會識別到左邊的這個矩形區域.
- 我們是否關系繼承關系 ? -> 否
數字圖片 沒有繼承關系。
?
所以我們應該使用cv2.RETR_EXTERNAL
模式
源代碼
import numpy as np import cv2 # 讀入黑背景下的彩色手寫數字 img = cv2.imread("color_number_handwriting.png") # 轉換為gray灰度圖 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 尋找輪廓 bimg, contours, hier = cv2.findContours(gray, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
2. 正外接矩形 boudningRect¶
函數比較簡單, 傳入唯一的參數是輪廓點集(單個) Points.
rect = cv2.boundingRect(cnt) (x, y, w, h) = rect
返回值 rect , 數據結構是tuple, 分別為矩形左上角坐標(x, y), 與矩形的寬度w
高度h
我們依次打印矩形區域的信息.
for cidx,cnt in enumerate(contours): (x, y, w, h) = cv2.boundingRect(cnt) print('RECT: x={}, y={}, w={}, h={}'.format(x, y, w, h))
OUTPUT
RECT: x=92, y=378, w=94, h=64 RECT: x=381, y=328, w=69, h=102 RECT: x=234, y=265, w=86, h=70 RECT: x=53, y=260, w=61, h=95 RECT: x=420, y=184, w=49, h=66 RECT: x=65, y=124, w=48, h=83 RECT: x=281, y=71, w=70, h=108
繪制在畫布上比較直觀:
截取ROI圖片的操作比較簡單img[y:y+h, x:x+w]
# 截取ROI圖像 cv2.imwrite("number_boudingrect_cidx_{}.png".format(cidx), img[y:y+h, x:x+w])
這樣我們就截取到了獨立的單個數字的圖片.
源代碼 CH5.2_bounding_rect.py
import numpy as np import cv2 # 讀入黑背景下的彩色手寫數字 img = cv2.imread("color_number_handwriting.png") # 轉換為gray灰度圖 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 尋找輪廓 bimg, contours, hier = cv2.findContours(gray, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # 聲明畫布 拷貝自img canvas = np.copy(img) for cidx,cnt in enumerate(contours): (x, y, w, h) = cv2.boundingRect(cnt) print('RECT: x={}, y={}, w={}, h={}'.format(x, y, w, h)) # 原圖繪制圓形 cv2.rectangle(canvas, pt1=(x, y), pt2=(x+w, y+h),color=(255, 255, 255), thickness=3) # 截取ROI圖像 cv2.imwrite("number_boudingrect_cidx_{}.png".format(cidx), img[y:y+h, x:x+w]) cv2.imwrite("number_boundingrect_canvas.png", canvas)
3. 最小外接矩形 minAreaRect¶
minAreaRect
函數用于獲取最小面積的矩形。
minAreaRect = cv2.minAreaRect(cnt)
我們打印一下minAreaRect
查看其返回的數據結構:
((133.10528564453125, 404.7727966308594), (100.10702514648438, 57.51853942871094), -49.184913635253906)
數據結構解析
((cx, cy), (width, height), theta)
-
cx
矩形中心點x坐標 center x -
cy
矩形中心點y坐標 center y -
width
矩形寬度 -
height
矩形高度 -
theta
旋轉角度,角度(不是弧度)
注意: 上述值均為小數, 不可以直接用于圖片索引,或者矩形繪制.
詳情見圖
圖片來源 python opencv minAreaRect 生成最小外接矩形
注意:旋轉角度θ是水平軸(x軸)逆時針旋轉,與碰到的矩形的第一條邊的夾角。并且這個邊的邊長是width,另一條邊邊長是height。也就是說,在這里,width與height不是按照長短來定義的。
在opencv中,坐標系原點在左上角,相對于x軸,逆時針旋轉角度為負,順時針旋轉角度為正。
為了直觀起見, 我們可以直接這樣賦值
((cx, cy), (width, height), theta) = cv2.minAreaRect(cnt)
完整一些的演示樣例:
for cidx,cnt in enumerate(contours): ((cx, cy), (width, height), theta) = cv2.minAreaRect(cnt) print('center: cx=%.3f, cy=%.3f, width=%.3f, height=%.3f, roate_angle=%.3f'%(cx, cy, width, height, theta))
OUTPUT
center: cx=133.105, cy=404.773, width=100.107, height=57.519, roate_angle=-49.185 center: cx=415.190, cy=378.853, width=66.508, height=100.537, roate_angle=-1.710 center: cx=278.323, cy=296.089, width=71.608, height=78.065, roate_angle=-78.440 center: cx=83.000, cy=307.000, width=60.000, height=94.000, roate_angle=0.000 center: cx=448.346, cy=213.731, width=47.068, height=64.718, roate_angle=-11.310 center: cx=89.642, cy=164.695, width=17.204, height=88.566, roate_angle=-25.427 center: cx=330.578, cy=123.387, width=92.325, height=72.089, roate_angle=-66.666
*如何獲取四個頂點? *
利用cv2.boxPoints
函數, 我們可以獲取矩形區域的四個頂點的坐標。序號如上圖所示。
minAreaRect = cv2.minAreaRect(cnt) print(cv2.boxPoints(minAreaRect))
樣例輸出
[[ 122.15498352 461.4520874 ] [ 78.62363434 423.85681152] [ 144.05558777 348.09350586] [ 187.58694458 385.68878174]]
你看這個數據結構是不是跟contour
的數據結構是類似的。
阿凱突然想到, 其實可以使用drawContours
函數繪制。
前提我們還需要將浮點數坐標轉換成整數。
# 聲明畫布 拷貝自img canvas = np.copy(img) for cidx,cnt in enumerate(contours): minAreaRect = cv2.minAreaRect(cnt) # 將浮點數坐標轉換成整數 rectCnt = np.int64(cv2.boxPoints(minAreaRect)) cv2.drawContours(canvas, [rectCnt], 0, (0,255,0), 3) cv2.imwrite("number_minarearect_canvas.png", canvas)
我們也可以使用多邊形繪制的函數進行繪制, 可能你需要復習一下 OpenCV繪圖-章節導引
這里阿凱換了一種顏色繪制。
# 繪制多邊形 cv2.polylines(img=canvas, pts=[rectCnt], isClosed=True, color=(0,0,255), thickness=3)
完整一些的代碼
import numpy as np import cv2 # 讀入黑背景下的彩色手寫數字 img = cv2.imread("color_number_handwriting.png") # 轉換為gray灰度圖 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 尋找輪廓 bimg, contours, hier = cv2.findContours(gray, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # 聲明畫布 拷貝自img canvas = np.copy(img) for cidx,cnt in enumerate(contours): minAreaRect = cv2.minAreaRect(cnt) # 轉換為整數點集坐標 rectCnt = np.int64(cv2.boxPoints(minAreaRect)) # 繪制多邊形 cv2.polylines(img=canvas, pts=[rectCnt], isClosed=True, color=(0,0,255), thickness=3) cv2.imwrite("number_minarearect_canvas.png", canvas)
## 4. 作業 Homework
Task01 - 獲取棋子底部的坐標并繪制¶
給大家準備了一下測試樣例.
你需要提取圖片中的棋子的位置,找到棋子所在的矩形區域,并且標注棋子底部中心點.
請自行學習opencv輪廓contours的其他屬性opencv文檔-py_contour_features
-
質心 moment
-
面積 Area
-
周長 perimeter
-
邊緣近似
-
凸包 與凸包性
-
矩形區域 bounding rectangle
-
最小矩形區域
-
最小閉合圓形區域 minimum encolosing circle
-
橢圓區域 fitting an ellipse
-
線 fitting a line
并用于輪廓的濾波,排除不相干色塊區域的干擾.
除此之外你還可以利用橫寬比,寬度,高度作為濾波條件.
Task02 - 提取旋轉的矩形¶
我們目前可以繪制圖片中旋轉的矩形了, 那么你有辦法將他們提取出來么?
請自行搜索圖像變換的資料,將數字提取出來。
提示: 圖像旋轉 變換
-
cv2.getRotationMatrix2D
獲取旋轉矩陣 -
cv2.warpAffine
變換
拓展:將矩形區域放縮到統一尺寸, 例如20*10