OpenCV---008(目标检测---形状检测)


目标检测

图像中物体的形状信息是比较明显和重要的信息 我们可以通过对形状的识别来实现对物体的检测
检测形状可以确定目标的位置 通过对目标大小位置的处理 我们可以进一步得到图像中重要的信息
本文 主要有 直线 圆形等图像的检测 以及如何检测图像中的区域轮廓 拟合轮廓形状 统计面积

形状检测

物体的形状检测多基于特殊形状的特性 opencv中提供了检测图像边缘中是否存在 直线 和 圆形的算法

直线检测

霍夫变换是图像处理中检测是否存在直线的重要算法 霍夫变换可以通过将图像中的像素从图像空间
变换到参数空间 使具有相同特性的曲线或者直线映射到参数空间形成峰值 从而将检测任意形状的问题
转化为统计峰值的问题

例如检测两个像素所在的直线 需要构建检测形状的数学解析式
    y = ax+b

    y0 = ax0 + b

    b = -x0a - y0        

    代表在一点有无数条直线
    而 y0 x0 定值 则 a和b当变量形成一条直线
    霍夫变换就是将空间图形经过一点的全部直线映射为参数空间的一条直线
    直线上每个点都对应直角坐标是的一条直线

    两个像素点 的连线 映射到参数空间
    是两条直线的交点 
    
    霍夫变换可以得到两个重要的结论:
        图像空间中的每条直线在参数空间都对应一个点
        在图像空间的直线上的任何一个点在参数空间对应的直线都较于一点


    所以利用霍夫变换 在参数空间找到交点 就可以得到直线
    但是注意 在直角坐标系中 垂直的直线不能被检测出来
    因为他们在参数空间的线是平行的


为了解决垂直直线的问题 一般采用极坐标来表示空间图形中的直线
    r = xcos + ycos
    他的参数空间 变为了 正弦曲线 
    
因为上述的直线检测是寻找点的问题 由于曲线是连线的 而实际情况下是离散的
所以我们要将参数空间离散化 离散后的每一个格子代表一个直线
然后寻找符合的格子

综上: 霍夫变换检测直线:
    1. 将参数空间离散化
    2. 通过映射关系求取图像中每个非零像素点在参数空间通过的方格
    3. 统计参数空间每个方格出现次数  选取最大的作为表示直线的方格
    4. 将根据直线的方格得到图像中的直线

在参数空间累加的点 映射到直角坐标系 不一定是连续的点

Opencv提供了两种用于检测图像中直线的函数
    1. 
        lines = cv.HoughLines(img,rho,theta,threshold)
    
        以极坐标形式将图像中 直线参数返回
        img: 必须是二值图,对灰度图的计算 需要用边缘检测得到二值图
        rho theta: 设置离散的精度
        一般为1 和 np.pi/180

        threshold: 参数空间中的经过次数 大于阈值的将被当为直线

        
        lines: 只能输出极坐标形式的参数 若需要绘制直线 需要得到直线上的两点坐标

点击查看代码
    import cv2
    import cv2 as cv
    import numpy as np
    import sys
    import math
    
    
    def func(x):
        pass
    
    
    def lines_draw(img_1, lines):
        img_2 = img_1.copy()
        for i in range(0, len(lines)):
            r, th = lines[i][0][0], lines[i][0][1]
            a = np.cos(th)
            b = np.sin(th)
            x = a * r
            y = b * r
            x1 = int(x + 1000 * (-b))
            y1 = int(y + 1000 * a)
            x2 = int(x - 1000 * (-b))
            y2 = int(y - 1000 * a)
            cv.line(img_2, (x1, y1), (x2, y2), (255, 255, 255), 2)
        return img_2
    
    
    cv.namedWindow('lines')
    cv.createTrackbar('thr', 'lines', 200, 500, func)
    
    if __name__ == '__main__':
        img = cv.imread('./img.jpg')
        if img is None:
            print('图读取失败')
            sys.exit()
    
        img = cv.cvtColor(img, cv2.COLOR_BGR2GRAY)
        cv.imshow('old', img)
        img = cv.GaussianBlur(img, (5, 5), 10, 20)
        cv.imshow('Gas', img)
        img = cv.Canny(img, 70, 140, apertureSize=3)
        img = cv.convertScaleAbs(img)
        cv.imshow('pad', img)
        while True:
            thrs = cv.getTrackbarPos('thr', 'lines')
            print(thrs)
            lines = cv.HoughLines(img, 1, np.pi / 180, thrs)
    
            img_new = lines_draw(img, lines)
            cv.imshow('lines', img_new)
    
            if cv.waitKey(1000) == ord('q'):
                break
    
        cv.destroyAllWindows()

缺点:该函数无法获得具体的线的位置和长度 只能直到是否有符合的线的存在

image

较大阈值
image

较小阈值


2. lines = cv.HoughLinesP(img,rho,theta,threshold, minLinelength,maxLineGap)
    lines 存储的是满足条件的直线或者线段两个端点的坐标
    x1,y1 = lines[i][0][0], lines[i][0][1]
    x2,y2 = lines[i][0][1], lines[i][0][3]
    上面已经说了 累加不一定是连续的 所以 累加的直线可能比连续的点长

    minLinelength : 最短长度 如果直线长度小于这个值 及时是直线也不会被输出
    maxLineGap: 最短距离 若两个点间的间隙小于这个值 直接连接 
    
lines = cv.HoughLinesP(img, 1, np.pi / 180, thrs, minLineLength=thrs + 50, maxLineGap=5)
对于想要提取斜线的情况 我们应该加大maxLineGap
    
!:
    minLinelength = threashold
    maxLineGap = 0
    表示全部是连续的点组成的直线或者线段

    而设置了这两个参数 就代表不需要是绝对的连续点组成的直线 增加了检测的正确率

效果如下:

image

排除山的影响:
    通过测试可以直知道 远处的山和地平线影响了我们对车道的检测
    所以 可以通过赋值的方法 来将上半部分直接用黑布盖起来,避免影响
    为什么不用感兴趣区域: 因为x和y的坐标会发生改变 导致不能正确的画出直线

    img3 = img.copy()
    img3[:200, :] = 0

效果如下:
image

直线判断:
    前面两个函数都可以检测图像中是否存在直线
    而实际工程中 我们有时候 得到的是一些点 而非是一张完整的图像
    opencv提供了能在含有坐标的点中 判断 这些点是否是一个直线

    _lines = cv.HoughLinesPointSet(_point,lines_max,threshold,min_rho,max_rho,tho_step,min_theta,max_theta,theta_step)
    _point: 点集
    lines_max: 最多拿多少直线
    threshold: 累加器阈值
    min_rho: 直线上两点最短距离
    max_rho: 直线上两点最长距离
    rho_stop: 离散
    min_theta: 直线过原点的垂线与x轴最小夹角
    max_theta: 直线过原点的垂线与x轴最大夹角
    theta_step: 离散


直线拟合:
    有时候我们知道一些点在一条线上 我们想要将所有点拟合为一条线
    opencv提供了最小二乘函数:
        line = cv.fitLine(points,distType,param,reps,aeps)
        
        distTyppe: 使用的距离类型
        param: 某些距离类型的数值参数
        reps: 坐标原点与拟合直线间的距离精度
        aeps: 拟合直线的角度精度

圆形检测

详细参照直线检测 原理相似 利用霍夫变换

circles = cv.HuoghCircles(img,method,dp,minDist,param1,param2,minRadius,maxRadius)
dp: 离散化
method: 当前版本 不能进行选择 仅有 cv.HOUGH_GRADIENT 方法
dp: 离散化 为1 与原图一样 为2 都为原图一半
circles : 1* N * 3 的数组 [0][0][0]:第0个圆的x [0][0][1]第0个圆的y [0][0][2]r
minDist: 太小会导致 错误的多个相邻圆 太大会导致 遗漏一些圆 两圆心最小距离 

该函数 直接给灰度图即可 会自动进行边缘检测 
param1: 传给Canny边缘检测的较大阈值
param2: 检测圆形的累加器阈值
minRadius: 最小半径
maxRadius: 最大半径

点击查看代码
    import cv2
    import cv2 as cv
    import numpy as np
    import sys
    import math
    
    
    def func(x):
        pass
    
    
    def lines_draw(img_1, cir):
        img_2 = img_1.copy()
        for i in range(0, len(cir)):
            r = int(cir[0][i][2])
            x = int(cir[0][i][0])
            y = int(cir[0][i][1])
    
            cv.circle(img_2, (x, y), r, (255, 0, 0), 5)
            cv.circle(img_2, (x, y), 2, (255, 0, 0), 5)
        return img_2
    
    
    if __name__ == '__main__':
        img = cv.imread('./img_5.png')
        if img is None:
            print('图读取失败')
            sys.exit()
    
        img_old = img.copy()
        img = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
        img = cv.GaussianBlur(img, (5, 5), 10, 20)
    
        img_2 = cv.Canny(img, 50, 150)
        cv.imshow('1',img_2)
    
        cirs = cv.HoughCircles(img, cv.HOUGH_GRADIENT, 2, 10, param1=150, param2=300)
        print(cirs)
        if cirs is not None:
            img_new = lines_draw(img_old, cirs)
            cv.imshow('new', img_new)
        cv.waitKey(0)
    
        cv.destroyAllWindows()


效果如下:
image


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM