cv2.contourArea、cv2.arcLength、cv2.approxPolyDP、cv2.convexHull、cv2.boundingRect、cv2.minAreaRect

         这里面相对比较核心的是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, , 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)

把这两种边界矩形显示在下图中,其中绿色为直矩形,红的为旋转矩形。

 

来源:Kun Li

物联沃分享整理
物联沃-IOTWORD物联网 » cv2.contourArea、cv2.arcLength、cv2.approxPolyDP、cv2.convexHull、cv2.boundingRect、cv2.minAreaRect

发表评论