这里面相对比较核心的是cv2.boundingRect和cv2.minAreaRect,后者用的非常多,上述所有方法的输入都是点集,对于minAreaRect,输入的是findContours找到的点集,然后获取一个完整的边界矩形,这个边界矩形通常会作为检测的结果,在文本检测中是常用的。
我这里先给一个任务,然后来看一下以上这些方法是如何作用的?任务本身还简单,就是将下面这张图做一些切分,分成上下横版和竖版两张图。
import os
import cv2
import numpy as np
from PIL import Image
def preprocess(gray):
# 1. Sobel算子,x方向求梯度
sobel = cv2.Sobel(gray, cv2.CV_8U, 1, 0, ksize=3)
# 2. 二值化
# ret, binary = cv2.threshold(sobel, 0, 255, cv2.THRESH_OTSU + cv2.THRESH_BINARY)
# ret, binary = cv2.threshold(gray, 150, 255, cv2.THRESH_TRUNC) # 截断 大于150的是改为150 小于150的保留
ret, binary = cv2.threshold(gray, 210, 255, cv2.THRESH_BINARY) # 自定义阈值为150,大于150的是白色 小于的是黑色
return binary
def findRegion(img):
region, area, widths, heights = [], [], [], []
# 1. 查找轮廓
# contours, hierarchy = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours, hierarchy = cv2.findContours(img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# print("number of contours:%d" % len(contours))
# cv2.drawContours(img, contours, -1, (0, 255, 255), 2)
# cv2.imwrite("img.png", img)
for i in range(len(contours)):
cnt = contours[i]
rect = cv2.minAreaRect(cnt)
box = cv2.boxPoints(rect)
box = np.int0(box)
# 计算高和宽
height = abs(box[0][1] - box[2][1])
width = abs(box[0][0] - box[2][0])
widths.append(width)
heights.append(height)
# 筛选那些太细的矩形,留下扁的
if width < 50 or height < 50:
continue
region.append(box)
area.append(cv2.contourArea(cnt))
return region, area
def detect(img_path, save_path):
img = Image.open(img_path)
# img = cv2.imread(img)
img = cv2.cvtColor(np.asarray(img), cv2.COLOR_RGB2BGR)
# 1. 转化成灰度图
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 2. 形态学变换的预处理,得到可以查找矩形的图片
dilation = preprocess(gray)
# 3. 查找和筛选文字区域
region, area = findRegion(dilation)
if len(region) == 0 or len(area) == 0:
return
max_area = area[np.argmax(area)]
# 4. 用绿线画出这些找到的轮廓
i = 0
for box, area_box in zip(region, area):
# cv2.drawContours(img, [box], 0, (0, 255, 0), 2)
# cv2.namedWindow("img", cv2.WINDOW_NORMAL)
# cv2.imshow("img", img)
#
# cv2.imwrite("contours.png", img)
#
# cv2.waitKey(0)
# cv2.destroyAllWindows()
if area_box < max_area / 4:
continue
elif area_box == max_area:
continue
xmin = box[:, 0].min()
ymin = box[:, 1].min()
xmax = box[:, 0].max()
ymax = box[:, 1].max()
img_crop = img[int(ymin):int(ymax), int(xmin):int(xmax)]
image = Image.fromarray(cv2.cvtColor(img_crop, cv2.COLOR_BGR2RGB))
image.show()
name = img_path.split('\\')[-1]
print("name:", name)
i += 1
image.save(save_path + '/' + str(i).zfill(6) + '_' + name)
# cv2.imwrite(save_path + '/' + str(i).zfill(6) + '_' + name, img_origin, [int(cv2.IMWRITE_JPEG_QUALITY), 100])
if __name__ == '__main__':
detect("banner38.jpg","E:/digital_asset_classification/cls")
逻辑上也很简单,先用阈值法对灰度图做一次阈值处理,再用findContours去找边界点,找到轮廓之后会利用minAreaRect返回外接轮廓,不过是返回是四个点的,通过boxPoints转坐标之后,进行判定是否符合要求,利用满足要求的点计算contourArea面积,最终根据满足要求的外接矩形进行裁剪。
1.cv2.counterArea:计算轮廓的面积。
area = cv2.contourArea(cnt)
2.轮廓周长。
也称为弧长,可以使用函数cv2.arcLenngth()计算得到。这个函数的第二参数可以用来指定对象的形状是闭合(True),还是打开的(一条曲线)。
perimeter = cv2.arcLength(cnt,True)
3.轮廓近似
将轮廓形状近似到另外一种有更少点组成的轮廓形状,新轮廓的点的数目有我们设定的准确度来决定。假设我们要在一幅图像中查找一个矩形,但是由于图像的种种原因,我们不能得到一个完美的矩形,而是一个不规则形状,现在就可以使用这个函数来近似这个形状了。这个函数的第二个参数叫epsilon,它是从原始轮廓到近似轮廓的最大距离,它是一个准确率参数,选择一个好的epsilon对于得到满意结果非常重要。
epsilon = 0.1*cv2.arcLength(cnt,True)
approx = cv2.approxPolyDP(cnt,epsilon,True)
4.凸包
凸包与轮廓近似相似,但不同,虽然有些情况下他们给出的结果是一样的。函数cv2.convexHull()可以用来检测一个曲线是否具有凸性缺陷,并能纠正缺陷。一般来说,凸性曲线总是凸出来的,至少是平的,如果有地方凹进去了就被叫做凸性缺陷。例如下图中的手,红色曲线显示了手的凸包,凸性缺陷被双箭头标出来了。
hull = cv2.convexHull(points[,hull[,clockwise[,returnPoints]]])
要获得上图中的凸包,如下
hull = cv2.convexHull(cnt)
5.边界矩形,这应该是用的最为广泛的函数。
有两类边界矩形。
直边界矩形 一个直矩形(就是没有旋转的矩形)。它不会考虑对象是否旋转,所以边界矩形的面积不是最小的,可以使用cv2.boundingRect()得到。
(x,y)为矩形左上角坐标,(w,h)是矩形的宽和高
x,y,w,h = cv2.boundingRect(cnt)
img = cv2.rectangle(img,(x,y),(x+w,y+h),(0,255,0),2)
旋转的边界矩形 这个边界矩形是面积最小的,因为它考虑了对象的旋转。用到的函数为cv2.minAreaRect()。返回的是一个Box2D结构,其中包含矩形左上角角点的坐标(x,y),矩形的宽和高(w,h)以及旋转角度。但是要绘制这个矩形需要矩形的4个角点,可以通过函数cv2.boxPoints()获得。
x,y,w,h = cv2.boundingRect(cnt)
img = cv2.retangle(img,(x,y),(x+w,y+h),(0,255,0),2)
把这两种边界矩形显示在下图中,其中绿色为直矩形,红的为旋转矩形。