OpenCV---009(目标检测--轮廓检测)

轮廓检测

图像轮廓 指对图像中的对象的边界 是图像目标的外部特征

轮廓的发现和绘制

图像的轮廓不仅仅能够提供物体的边缘 还可以提供物体边缘间的层次关系 及 拓扑关系
我们可以将图像轮廓看为 带有 结构关系的边缘检测 这种结构关系可以表面图像中的连通域或者某些区域的关系
定义轮廓由外到内 级别越来越低

为了表示轮廓间的关系 
一般使用4个参数来表示不同的个层级间关系、  
分别为 同层的下一个轮廓索引 同层的上一个轮廓索引  下一层的第一个子轮廓索引 上一层的父轮廓索引
不存在的用-1表示
比如最外层的轮廓 [-1 -1 1 -1]

opencv提供了可以在二值图像中检测图像的所有轮廓并生成不同轮廓关系的函数
    contours,hierarchy = cv.findContours(img,mode,method,offset)
    mode 轮廓检测模式的标志
    0: 只检测最外层轮廓 
    1: 提取所有轮廓 但是不建立等级关系
    2: 提取所有轮廓 并组织为双层结构 顶层为外围 次层内层边界
    3: 提取所有的轮廓 并建立网状轮廓结构



    method 轮廓逼近方法的标志
    1: 获得每个轮廓的每个像素 相邻两点的距离不超过1
    2: 压缩水平 垂直方向 只保留该方向的终点坐标 如矩形仅保留四个点


    contouts 轮廓的像素坐标
        用于存放检测到的轮廓  以ndarry的方式存放在list中 每个ndarry存放的是坐标

    hierarchy 轮廓结构关系描述向量
        放在一个 1 * N * 4 的对象中
    offset 每个轮廓点移动的可选偏移量 主要用在从ROI图像找到轮廓并基于整个图像分析轮廓的场景
        

提取轮廓后为了能直观的得到轮廓 所以 opencv提供了函数
    img = cv.drawContours(img, contours,contourIdx,color,thickness,lineType)
    contourIdx 绘制轮廓数目
    注意: 这里返回的img 是 img本身

点击查看代码
    import cv2 as cv
    import sys
    
    
    def func(x):
        pass
    
    
    if __name__ == '__main__':
        cv.namedWindow('res')
        cv.createTrackbar('thre', 'res', 50, 255, func)
        img = cv.imread('img_7.png')
        if img is None:
            sys.exit()
            
        img_old = img.copy()
        img = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
        img = cv.GaussianBlur(img, (9, 9), 10, 20)
        cv.imshow('img', img)
        while True:
            img_change = img.copy()
            thr = cv.getTrackbarPos('thre', 'res')
            _, img_change = cv.threshold(img_change, thr, 255, cv.THRESH_BINARY_INV)
            img_change = cv.morphologyEx(img_change, 2, (3, 3))
    
            cv.imshow('res', img_change)
            contours, hierarchy = cv.findContours(img_change, 3, 2)
            img_draw = cv.drawContours(img_old, contours, -1, (255, 0, 0), 1, 8)
            print(contours)
            print(hierarchy)
            cv.imshow('draw', img_draw)
            if cv.waitKey(1) == ord('q'):
                break
    
        cv.destroyAllWindows()


image

image

轮廓检测也可以检测边缘的轮廓

轮廓面积

轮廓面积是重要的统计特性之一 通过轮廓面积大小 可以进一步分析每个轮廓隐含的信息
如通过 轮廓面积区分物体大小 识别不同的物体 轮廓面积是指每个轮廓中的所有像素围成的区域面积
单位为像素

opencv提供了计算面积函数
retval = cv.contourAread(contours,oriented)
    contours 是一个轮廓   上面得到的是一个list  用索引取值后在带入!!
    oriented: 面积是否有方向性
    轮廓顶点顺时针给出 和 逆时针给出 时 计算的面积值互为相反数
    默认为 False

轮廓长度

轮廓长度也是轮廓重要的统计特征之一 虽然1轮廓长度无法直接反应轮廓的大小 和 形状 但是可以与轮廓面积集合
得到关于轮廓区域的更多信息

retval = cv.arcLength(curve,closed)
curve: 轮廓
closed: 轮廓 是否闭合
如计算一个三角形的轮廓 若闭合 就是 计算周长 不闭合 则计算 两两点间的长度

轮廓外接多边形

由于噪声和光照的影响 轮廓会出现不规则的形状 不规则的轮廓形状不利于对图像内容进行分析。
所以需要将物体的轮廓拟合为规则的几何图像

求取轮廓最大外接矩阵的函数
    retval = cv.boundingRect(arry)
    arry 表示灰度图或者点集
    retval: 一个元组 里面有四个参数
    分别是 左上角 x y 宽 高

最小外界矩阵:
    retval = cv.minAreaRect(points)
    retval: (x,y) (w,h) angle 

点击查看代码
    import cv2 as cv
    import sys
    
    
    def func(x):
        pass
    
    
    def get_area(contour):
        area = cv.contourArea(contour)
        return area
    
    
    def get_length(contour):
        length = cv.arcLength(contour, True)
        return length
    
    
    def get_box(contour):
        retval = cv.boundingRect(contour)
        return retval
    
    
    def draw_box(retval, img):
        x, y, w, h = retval
        img = cv.rectangle(img, (x, y), (x + w, y + h), (255, 0, 0), 2)
        return img
    
    
    if __name__ == '__main__':
        cv.namedWindow('res')
        cv.createTrackbar('thre', 'res', 50, 255, func)
        img = cv.imread('img_7.png')
        if img is None:
            sys.exit()
    
        img_old = img.copy()
        img = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
        img = cv.GaussianBlur(img, (9, 9), 10, 20)
        cv.imshow('img', img)
        while True:
            img_change = img.copy()
            thr = cv.getTrackbarPos('thre', 'res')
            _, img_change = cv.threshold(img_change, thr, 255, cv.THRESH_BINARY_INV)
            img_change = cv.morphologyEx(img_change, 2, (3, 3))
    
            cv.imshow('res', img_change)
            img_change = cv.Canny(img_change, 50, 100, apertureSize=3)
    
            cv.imshow('canny', img_change)
            contours, hierarchy = cv.findContours(img_change, 3, 2)
    
            img_draw = img_old.copy()
    
            for i in contours:
                recval = get_box(i)
                img_draw = draw_box(recval, img_draw)
    
            cv.imshow('draw',img_draw)
            # img_draw = cv.drawContours(img_old.copy(), contours, -1, (255, 0, 0), 1, 8)
            # cv.imshow('draw', img_draw)
    
            if cv.waitKey(1) == ord('q'):
                break
    
        cv.destroyAllWindows()




效果如下:
image

有时候矩形会产生较大的误差 所以用多边形围成的面积会更接近真实的轮廓面积

轮廓拟合 将 一组轮廓拟合为一个多边形的顶点
approxCurve = cv.approxPolyDP(curve,epsilon,closed)
approxCurve: 以多边形顶点的形式给出
curve: 输入轮廓
epsilon: 精度
closed: 是否闭合

效果如下
image

可以根据多边形的边数来判断大体形状

轮廓到点的距离

点到轮廓的距离对于计算机在图像中的位置 两轮廓的距离 以及 确定图像上某一点是否在轮廓内部有重要的作用

retval = cv.pointPolygonTest(contour,pt,measureDist)
contour: 轮廓 
pt: 需计算的像素点
measureDist: 统计的距离是否有方向性

该函数可以计算像素到轮廓的最短距离 并且以float的形式返回
第一个参数 保存在 ndarry中
第三个参数 当为False时,仅判断像素与轮廓间的位置关系
-1 在轮廓外部   1  在轮廓内部    0 在轮廓边缘
若为True: 则有方向性 正数在轮廓外部 负数在内部

凸包检测

有时候图像过于复杂 即使使用多边形进行拟合处理 后 还是过于复杂 形状较复杂的图像可以使用凸包检测来近似表示
凸包是图形学中常见的概念 将二维平面上点集最外层的点连接起来构成的凸多边形称为凸包。虽然凸包检测
也是对轮廓的多边形逼近 但是逼近结果一定是凸多边形

opencv给出函数:

hull = cv.convexHull(points,hull,clockwise,returnPoints)
points: 输入轮廓坐标
hull: 输出凸包的顶点
    返回一个list 或者ndarry
clockwise: 方向标志
returnPoints: 为True时 输出的是凸包顶点坐标,放在ndarry中 为False时 输出的是索引 放在list
当范围list时 用索引在轮廓点集内提取出来顶点 但是 不能直接用 cv.drawContours画出
因为他接受的是一个list list内每一个元素都单独是一个轮廓 所以会画出很多的点

效果如下:
image


点击查看代码
import cv2 as cv
import sys
import numpy as np


def func(x):
    pass


def get_area(contour):
    area = cv.contourArea(contour)
    return area


def get_length(contour):
    length = cv.arcLength(contour, True)
    return length


def get_box(contour):
    retval = cv.boundingRect(contour)
    return retval


def draw_box(retval, img):
    x, y, w, h = retval
    img = cv.rectangle(img, (x, y), (x + w, y + h), (255, 0, 0), 2)
    return img


def draw_shape(contour, img):
    appr = cv.approxPolyDP(contour, 1, True)
    # print(sum(appr))
    center = np.int0((sum(appr)[0] // len(appr)))
    center = (center[0], center[1])

    img = cv.drawContours(img, [appr], -1, (0, 255, 0), 2, 8)
    img = cv.circle(img, center, 3, (0, 0, 255), -1)
    return img


def out_bag(contour, img):
    hull = cv.convexHull(contour, returnPoints=True)
    img = cv.drawContours(img, [hull], -1, (255, 0, 0), 2, 8)
    return img


if __name__ == '__main__':
    cv.namedWindow('res')
    cv.createTrackbar('thre', 'res', 50, 255, func)
    img = cv.imread('img_7.png')
    if img is None:
        sys.exit()

    img_old = img.copy()
    img = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
    img = cv.GaussianBlur(img, (9, 9), 10, 20)
    cv.imshow('img', img)
    point = (100, 100)
    while True:
        img_change = img.copy()
        thr = cv.getTrackbarPos('thre', 'res')
        _, img_change = cv.threshold(img_change, thr, 255, cv.THRESH_BINARY_INV)
        img_change = cv.morphologyEx(img_change, 2, (3, 3))

        cv.imshow('res', img_change)
        img_change = cv.Canny(img_change, 50, 100, apertureSize=3)

        cv.imshow('canny', img_change)
        contours, hierarchy = cv.findContours(img_change, 3, 2)

        img_draw = img_old.copy()

        # for i in contours:
        #     img_draw = draw_shape(i, img_draw)

        for i in contours:
            img_draw = out_bag(i, img_draw)
        for i in range(len(contours)):
            dis = cv.pointPolygonTest(contours[i], point, measureDist=True)
            if dis > 0:
                print('点距离第{}个轮廓{},在轮廓外'.format(i, dis))
            if dis < 0:
                print('点距离第{}个轮廓{},在轮廓内'.format(i, dis))
            if dis == 0:
                print('点距离第{}个轮廓{},在轮廓上'.format(i, dis))

        # for i in contours:
        #     recval = get_box(i)
        #     img_draw = draw_box(recval, img_draw)

        cv.imshow('draw', img_draw)
        # img_draw = cv.drawContours(img_old.copy(), contours, -1, (255, 0, 0), 1, 8)
        # cv.imshow('draw', img_draw)

        if cv.waitKey(1) == ord('q'):
            break

    cv.destroyAllWindows()

posted @ 2022-03-20 17:07  cc学习之路  阅读(821)  评论(0)    收藏  举报