環(huán)境
- Python 3.8
- OpenCV 4.4
最小外接矩形
矩形操作是我們?cè)?code>OpenCV里最常用的操作,其中最為常見的就是包圍框(Bounding Box
)和旋轉(zhuǎn)矩形(Rotated Box
)仿畸。 其中包圍框是最為常見的释漆,對(duì)應(yīng)OpenCV
中的boundingRect()
算谈,使用正矩形框處物體桦锄,一般多用在目標(biāo)檢測(cè)中睬棚。使用包圍框框柱目標(biāo)物體,這種操作比較簡(jiǎn)單杜秸,但是通撤耪蹋框中也會(huì)有一些其他的區(qū)域。其次就是使用旋轉(zhuǎn)矩形撬碟,也叫最小外接矩形诞挨,對(duì)應(yīng)OpenCV
中的minAreaRect()
,用來(lái)使用旋轉(zhuǎn)矩形最大限度的框出目標(biāo)物體,減小背景干擾,在OCR
任務(wù)中較為常用咨察。
def drow_box(img, cnt):
rect_box = cv2.boundingRect(cnt)
rotated_box = cv2.minAreaRect(cnt)
cv2.rectangle(img, (rect_box[0], rect_box[1]), (rect_box[0] + rect_box[2], rect_box[1] + rect_box[3]), (0, 255, 0), 2)
box = cv2.boxPoints(rotated_box)
box = np.int0(box)
cv2.drawContours(img, [box], 0, (0, 0, 255), 2)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# plt.imshow(img)
# plt.show()
return img, rotated_box, box
minAreaRect()
返回了所需區(qū)域的最小斜矩形的參數(shù)坞古,與包圍框直接返回四個(gè)頂點(diǎn)的坐標(biāo)不同亩进,最小外接矩形返回的是矩形的((x, y), (w, h), angle)
,對(duì)應(yīng)了矩形的中心,寬度,高度和旋轉(zhuǎn)角度蜈敢。
旋轉(zhuǎn)角度angle
是水平軸(x
軸)逆時(shí)針旋轉(zhuǎn),與碰到的矩形的第一條邊的夾角汽抚。并且這個(gè)邊的邊長(zhǎng)是width
抓狭,另一條邊邊長(zhǎng)是height
。也就是說造烁,在這里width
與height
不是按照長(zhǎng)短來(lái)定義的否过。
在OpenCV
中,坐標(biāo)系原點(diǎn)在左上角惭蟋,相對(duì)于x
軸苗桂,逆時(shí)針旋轉(zhuǎn)角度為負(fù),順時(shí)針旋轉(zhuǎn)角度為正告组,所以函數(shù)minAreaRect()
返回的角度范圍時(shí)[-90~0)
煤伟。想象一個(gè)平放的長(zhǎng)矩形,調(diào)用minAreaRect()
返回的角度為-90
度惹谐。如果我們旋轉(zhuǎn)圖像持偏,直到矩形樹立起來(lái)驼卖,這是調(diào)用minAreaRect()
得到的角度依然是-90
度氨肌。
圖像摳取
仿射變換
第一種裁剪旋轉(zhuǎn)矩形的方法是通過仿射變換旋轉(zhuǎn)圖像的方式。
仿射變換(Affine Transformation
) 是一種二維坐標(biāo)到二維坐標(biāo)之間的線性變換酌畜,保持二維圖形的“平直性”(straightness
怎囚,即變換后直線還是直線不會(huì)打彎,圓弧還是圓弧)和“平行性”(parallelness
恳守,其實(shí)是指保二維圖形間的相對(duì)位置關(guān)系不變考婴,平行線還是平行線,相交直線的交角不變催烘。)沥阱。
計(jì)算過程:
- 計(jì)算旋轉(zhuǎn)矩形。
- 基于旋轉(zhuǎn)矩形的中心和角度計(jì)算得到一個(gè)變換矩陣伊群。這里我想要得到的車牌是一個(gè)橫著的正矩形考杉,因此需要判斷一下
angle
,width
和height
,保證旋轉(zhuǎn)后的矩形是橫著的矩形舰始。 - 以旋轉(zhuǎn)矩形的中心為基準(zhǔn)點(diǎn)崇棠,對(duì)整張圖進(jìn)行旋轉(zhuǎn),這里旋轉(zhuǎn)的實(shí)現(xiàn)是基于仿射變換實(shí)現(xiàn)的丸卷。
- 由于旋轉(zhuǎn)之后矩形的中點(diǎn)坐標(biāo)是不變的枕稀,以中心為基礎(chǔ),通過
width
和height
摳出正矩形谜嫉。
def crop1(img, cnt):
horizon = True
img, rotated_box, _ = drow_box(img, cnt)
center, size, angle = rotated_box[0], rotated_box[1], rotated_box[2]
center, size = tuple(map(int, center)), tuple(map(int, size))
print(angle)
if horizon:
if size[0] < size[1]:
angle -= 270
w = size[1]
h = size[0]
else:
w = size[0]
h = size[1]
size = (w, h)
height, width = img.shape[0], img.shape[1]
M = cv2.getRotationMatrix2D(center, angle, 1)
img_rot = cv2.warpAffine(img, M, (width, height))
img_crop = cv2.getRectSubPix(img_rot, size, center)
show([img, img_rot, img_crop])
如果不做邊長(zhǎng)和角度的判斷萎坷,則只會(huì)沿著x
軸的順時(shí)針方向做相同大小角度的旋轉(zhuǎn),不能保證旋轉(zhuǎn)后的視角是正確的視角:
根據(jù)任務(wù)目標(biāo)的類型骄恶,做邊長(zhǎng)和角度的判斷并進(jìn)行相應(yīng)的調(diào)整食铐,可以保證旋轉(zhuǎn)后的視角是正確的視角:
透視變換
第二種裁剪旋轉(zhuǎn)矩形的方法是通過透視變換直接將旋轉(zhuǎn)矩形的四個(gè)頂點(diǎn)映射到正矩形的四個(gè)頂點(diǎn)。
透視變換(Perspective Transformation
)是將圖片投影到一個(gè)新的視平面(Viewing Plane
)僧鲁,也稱作投影映射(Projective Mapping
)虐呻。
計(jì)算過程:
- 計(jì)算旋轉(zhuǎn)矩形。
- 基于矩形的四個(gè)頂點(diǎn)和想要摳出的正矩形的四個(gè)頂點(diǎn)得到一個(gè)變換矩陣寞秃。這里我想要得到的車牌是一個(gè)橫著的正矩形斟叼,因此需要判斷一下
width
和height
,將映射的平面定義為一個(gè)橫著的正矩形春寿。 - 通過透視變換朗涩,將四個(gè)點(diǎn)組成的平面轉(zhuǎn)換成另四個(gè)點(diǎn)組成的一個(gè)平面,以此摳出正矩形绑改。
def crop2(img, cnt):
img, rotated_box, box = drow_box(img, cnt)
width = int(rotated_box[1][0])
height = int(rotated_box[1][1])
print(width, height)
if width > height:
w = width
h = height
else:
w = height
h = width
src_pts = box.astype("float32")
dst_pts = np.array([[w - 1, h - 1],
[0, h - 1],
[0, 0],
[w - 1, 0]], dtype="float32")
M = cv2.getPerspectiveTransform(src_pts, dst_pts)
warped = cv2.warpPerspective(img, M, (w, h))
show([img, warped])
結(jié)論
以上兩種方法都可以用來(lái)?yè)溉⌒D(zhuǎn)矩形的內(nèi)容谢床。仿射變換方法需要預(yù)先對(duì)整張圖進(jìn)行旋轉(zhuǎn),通過觀察旋轉(zhuǎn)后的圖像可以發(fā)現(xiàn)厘线,有一部分圖像被旋轉(zhuǎn)出了圖像邊界识腿,如果你要摳取的目標(biāo)正好在圖像邊緣附近,那么很容易出界導(dǎo)致圖像摳取的缺失造壮。同時(shí)我們需要對(duì)寬渡讼、高和角度做出動(dòng)態(tài)的調(diào)整;透視變換的方法直接對(duì)摳取區(qū)域進(jìn)行了映射,這種方法可以省略旋轉(zhuǎn)的步驟成箫,并且不會(huì)出現(xiàn)摳取內(nèi)容的缺失展箱。同時(shí)我們只需要對(duì)4個(gè)頂點(diǎn)之間的映射關(guān)系做好定義即可,不需要考慮角度的問題蹬昌。相對(duì)的混驰,透視變換相對(duì)于仿射變換計(jì)算量更大一些,不過這在c++
的底層實(shí)現(xiàn)上帶來(lái)的時(shí)延差距小于ms
皂贩。