通过Opencv进行边缘检测可以说是十分常见了,接下来让我们聊一聊如何通过python opencv一步一步实现边缘检测

重要函数讲解

图片读取函数:
pic = cv2.imread(file_path, flag=None)

参数:

  • file_path:读取的图片的路径。这里要注意如果图像不能读取(由于文件丢失、权限不当、格式不支持或无效),函数返回一个NULL。文件的格式取决于具体的图像的格式,而不是图像的后缀名。
  • flag:图像的格式。这里使用的是 cv2.IMREAD_UNCHANGED代表的是读取的是源图像。也可以使用cv2.IMREAD_GRAYSCALE读取的是灰度图。

返回值:

  • pic:加载后返回的图像。解码后的图像将按B G R 顺序存储通道。使用 IMREAD_GRAYSCALE 时,将使用编解码器的内部灰度转换。

注意:结果可能与 cvtColor() 的输出不同。编解码器附带 OpenCV 图像(libjpeg、默认使用 libpng、libtiff 和 libjasper)。因此,OpenCV 始终可以读取 JPEG、PNG、TIFF。而Linux需要安装随操作系统映像提供的编解码器libjpeg-dev


摄像头读取函数:
cap = cv2.VideoCapture(CAMERA_ID)

参数

  • CAMERA_ID:代表的是摄像头的ID号,一般情况下0代表的是内置的摄像头。若要读取的是视频则为视频的具体的路径,如./test.avi。

返回值:

  • 初始化好的摄像头设备

摄像头读取视频帧函数:
ret, frame = cap.read()

  • cap.read()按帧读取视频

返回值

  • ret是布尔值,如果读取帧是正确的则返回True,如果文件读取到结尾,它的返回值就为False。
  • frame就是每一帧的图像,是个RGB图像的三维矩阵。如果未抓取任何帧,则图像将为空。不允许修改或发布图片,只是使用的是副本图像进行相关的操作。

高斯滤波函数:
gs_frame=cv2.GaussianBlur(src, ksize, sigmaX, dst=None, sigmaY=None, borderType=None)
说明:将源图像与指定的高斯核进行卷积
参数

  • src:输入图像。图像可以有任意数量的通道,这些通道被独立处理,但深度应为 CV_8U、CV_16U、CV_16S、CV_32F 或 CV_64F。
  • ksize:ksize.width 和 ksize.height 可以不同,但​​它们都必须是正数和奇数。或者,它们可以是零,然后根据 sigma 计算它们。
  • sigmaX:X 方向的高斯核标准偏差
  • dst:输出与 src 大小和类型相同的结果图像
  • sigmaY:Y 方向的高斯核标准偏差;如果 sigmaY 为零,则设置为等于 sigmaX,如果两个 sigma 都为零,则根据 ksize.width 和 ksize.height 计算它们.
  • borderType:像素外推方法。使用默认值即可。

inRange()函数实现二值化
OpenCV中的inRange()函数可实现二值化功能(这点类似threshold()函数
inRange_hsv=cv2.inRange((InputArray src, InputArray lowerb,InputArray upperb, OutputArray dst)

参数

  • src:输入要处理的图像,可以为单通道或多通道。
  • lowerb:包含下边界的数组或标量。
  • upperb:包含上边界数组或标量
  • dst:输出图像,与输入图像src 尺寸相同且为CV_8U 类型。也可以作为返回值处理

返回值

  • inRange_hsv:输出图像,与输入图像src 尺寸相同且为CV_8U 类型。也可以作为返回值处理

形态学腐蚀函数
img_erosion=cv2.erode(src, kernel, dst=None, anchor=None, iterations=None, borderType=None, borderValue=None)
说明:通过使用特定的结构元素侵蚀图像。该函数使用确定的指定结构元素侵蚀源图像,取最小值的像素邻域的形状.侵蚀可以应用几次(迭代),对于多通道图像,每个通道都是独立处理的。
参数

  • src:输入图像.通道的数量可以是任意的,但深度应该是CV_8U、CV_16U、CV_16S、CV_32F 或 CV_64F。
  • kernel:用于腐蚀的内核结构元素。一般为奇数(3,3)、(5,5)的卷积核等等
  • dst:输出与 src 大小和类型相同的图像
  • anchor:元素内锚点的锚点位置;默认值 (-1, -1) 表示锚点位于元素中心。
  • iterations:迭代应用侵蚀的次数
  • borderType:像素外推方法
  • borderValue:边界的值为恒定的值

返回值

  • img_erosion:腐蚀后的图像,与 src 大小和类型相同的图像

形态膨胀学函数
说明:通过使用特定的结构元素来扩大图像。该函数使用指定的结构元素来扩展源图像,该结构元素确定取最大值的像素邻域的形状。
dilate_img=cv2.dilate(src, kernel, dst=None, anchor=None, iterations=None, borderType=None, borderValue=None)

参数

  • src:输入图像.通道的数量可以是任意的,但深度应该是CV_8U、CV_16U、CV_16S、CV_32F 或 CV_64F。
  • kernel:用于膨胀的内核结构元素。一般为奇数(3,3)、(5,5)的卷积核等等
  • dst:输出与 src 大小和类型相同的图像
  • anchor:元素内锚点的锚点位置;默认值 (-1, -1) 表示锚点位于元素中心。
  • iterations:迭代应用膨胀的次数
  • borderType:像素外推方法
  • borderValue:边界的值为恒定的值

返回值

  • dilate_img:膨胀后的图像,与 src 大小和类型相同的图像

开、闭操作
morphologyEx_frame=cv2.morphologyEx(src, op, kernel, dst=None, anchor=None, iterations=None, borderType=None, borderValue=None)
说明:使用腐蚀和膨胀来执行高级形态变换,开操作就是先腐蚀后膨胀

参数

  • src:输入图像.通道的数量可以是任意的,但深度应该是CV_8U、CV_16U、CV_16S、CV_32F 或 CV_64F。
  • op:形态学操作的类型. cv2.MORPH_OPEN、cv2.MORPH_CLOSE分别为开操作和闭操作
  • kernel:用于腐蚀或者膨胀的内核结构元素。一般为奇数(3,3)、(5,5)的卷积核等等
  • dst:输出与 src 大小和类型相同的图像
  • anchor:元素内锚点的锚点位置;默认值 (-1, -1) 表示锚点位于元素中心。
  • iterations:迭代应用侵蚀和膨胀的次数
  • borderType:像素外推方法
  • borderValue:边界的值为恒定的值

返回值

  • morphology_frame:开或者闭操作的图像,与 src 大小和类型相同的图像

sobel算子
sobel算子简介:sobel算子认为,邻域的像素对当前像素产生的影响不是等价的,所以距离不同的像素具有不同的权值,对算子结果产生的影响也不同。一般来说,距离越远,产生的影响越小。

sobel算子原理: 对传进来的图像像素做卷积,卷积的实质是在求梯度值,或者说给了一个加权平均,其中权值就是所谓的卷积核;然后对生成的新像素灰度值做阈值运算,以此来确定边缘信息。x方向的梯度会加强图像水平方向的特征,而y方向的梯度会加强图像竖直方向的特征

sobel=cv2.Sobel(src, ddepth, dx, dy, dst=None, ksize=None, scale=None, delta=None, borderType=None)
说明:Sobel 算子结合了高斯平滑和微分,所以结果或多或少耐噪音。大多数情况下,函数被调用( xorder = 1, yorder = 0, ksize = 3)或 ( xorder = 0, yorder = 1, ksize = 3) 计算第一个 x 或 y 图像导数。

参数

  • src:输入图像
  • ddepth:输入图像的深度。-1表示采用的是与原图像相同的深度。目标图像的深度必须大于等于原图像的深度。如果是8 位输入图像它会导致截断导数。
  • dx:导数x的顺序
  • dy:导数y的顺序
  • dst:输出图像。和输入图像具有相同的深度
  • ksize:扩展 Sobel 内核的大小;它必须是 1、3、5 或 7
  • scale:计算导数值的可选比例因子。默认情况下,没有缩放
  • delta:可选的增量,将会加到最终的dst中,同样,默认情况下没有额外的值加到dst中
  • borderType:像素外推方法

权重取绝对值函数

dst=cv2.convertScaleAbs(src, dst=None, alpha=None, beta=None)
说明:由于sobel算子算出的值会产生负值,否则负数会一律当做0处理,所以要使用取绝对值函数。在输入数组的每个元素上,函数 convertScaleAbs依次执行三个操作:缩放、取绝对值,转换为无符号 8 位类型。在多通道数组的情况下,该函数处理每个通道独立。当输出不是 8 位时,操作可以是通过调用 Mat::convertTo 方法(或使用矩阵表达式),然后计算结果的绝对值。

参数

  • src:输入数组
  • dst:输出数组
  • alpha:可选比例因子
  • beta:可选增量添加到缩放值

加权函数
add_weight_img=cv2.addWeighted(src1, alpha, src2, beta, gamma, dst=None, dtype=None)

说明:计算两个数组的加权和: dst = src1alpha + src2beta + gamma

参数

  • src1:第一个输入数组
  • alpha:第一个数组元素的 alpha 权重
  • src2:具有相同大小和通道号的第二个输入数组
  • beta:第二个数组元素的 beta 权重
  • gamma:标量添加到每个总和
  • dst:输出数组,其大小和通道数与输入数组相同
  • dtype:输出数组的可选深度;当两个输入数组具有相同的深度时,dtype可以设置为 -1,相当于 src1.depth()

scharr算子
scharr_img=cv2.Scharr(src, ddepth, dx, dy, dst=None, scale=None, delta=None, borderType=None)

说明:虽然Sobel算子可以有效的提取图像边缘,但是对图像中较弱的边缘提取效果较差。因此为了能够有效的提取出较弱的边缘,需要将像素值间的差距增大。Scharr算子是对Sobel算子差异性的增强,因此两者之间的在检测图像边缘的原理和使用方式上相同。Scharr算子的边缘检测滤波的尺寸为3×3,因此也有称其为Scharr滤波器。可以通过将滤波器中的权重系数放大来增大像素值间的差异.

参数

  • src:输入图像
  • ddepth:输入图像的深度。-1表示采用的是与原图像相同的深度。目标图像的深度必须大于等于原图像的深度。如果是8 位输入图像它会导致截断导数。
  • dx:导数x的顺序
  • dy:导数y的顺序
  • dst:输出图像。和输入图像具有相同的深度
  • ksize:扩展 Sobel 内核的大小;它必须是 1、3、5 或 7
  • scale:计算导数值的可选比例因子。默认情况下,没有缩放
  • delta:可选的增量,将会加到最终的dst中,同样,默认情况下没有额外的值加到dst中
  • borderType:像素外推

Canny边沿检测算法
canny_img=cv2.Canny(image, threshold1, threshold2, edges=None, apertureSize=None, L2gradient=None)
说明:在输入图像中找到边缘,并使用精明算法。阈值 1 和阈值 2 之间的最小值用于边缘链接。这最大值用于查找强边缘的初始段。

参数

  • image:8 位输入图像
  • threshold1:第一个阈值
  • threshold2:第二个阈值
  • edges:输出边缘图;单通道 8 位图像,与 image 大小相同。
  • apertureSize:sobel算子的孔径大小
  • L2gradient:计算图像梯度幅度。可以使用默认的L2范数

寻找轮廓函数
contours, hierarchy=cv2.findContours(image, mode, method, contours=None, hierarchy=None, offset=None):
说明:从二进制图像中检索轮廓。

参数

  • image:一般为sobel算子或者scharr算子得到的二值化图像。8 位单通道图像。非零像素被视为 1。零像素保持为 0,因此图像被视为 binary
  • mode:

    • cv2.RETR_EXTERNAL 只检测外轮廓
    • cv2.RETR_LIST 检测的轮廓不建立等级关系,都是同级,不存在父轮廓或内嵌轮廓,
    • cv2.RETR_CCOMP 建立两个等级的轮廓,上面一层为外边界,里面一层为内孔的边界信息
    • cv2.RETR_TREE 建立一个等级树结构的轮廓
  • method

    • cv2.RETR_EXTERNAL 只检测外轮廓
    • cv2.RETR_LIST 检测的轮廓不建立等级关系,都是同级,不存在父轮廓或内嵌轮廓,
    • cv2.RETR_CCOMP 建立两个等级的轮廓,上面一层为外边界,里面一层为内孔的边界信息
    • cv2.RETR_TREE 建立一个等级树结构的轮廓
  • contours:检测的轮廓数组,每一个轮廓用一个point类型的vector表示

  • hierarchy:和轮廓个数相同,每个轮廓contours[ i ]对应4个hierarchy元素hierarchy[ i ][0 ] ~hierarchy[ i ][ 3],分别表示后一个轮廓、前一个轮廓、父轮廓、内嵌轮廓的索引编号,如果没有对应项,该值设置为负数。

  • offset:每个轮廓点移动的可选偏移量

返回值

  • contours:检测的轮廓数组,每一个轮廓用一个point类型的vector表示
  • hierarchy:和轮廓个数相同,每个轮廓contours[ i ]对应4个hierarchy元素hierarchy[ i ][0 ] ~hierarchy[ i ][ 3],分别表示后一个轮廓、前一个轮廓、父轮廓、内嵌轮廓的索引编号,如果没有对应项,该值设置为负数。

画轮廓函数
cv2.drawContours(image, contours, contourIdx, color, thickness=None, lineType=None, hierarchy=None, maxLevel=None, offset=None)

说明:绘制轮廓轮廓或填充轮廓

参数

  • image:目标图像
  • contours:是得到的一系列点的集合
  • contourIdx:指示要绘制的轮廓的参数。如果为负,则绘制所有轮廓。
  • color:轮廓的颜色
  • thickness:绘制轮廓线的粗细。如果它是负数,轮廓内部被绘制
  • lineType:线的连通性
  • hierarchy:可选层次信息结构,是findContours所的到的基于Contours的层级信息
  • maxLevel: 绘制轮廓的最大等级。如果等级为0,绘制单独的轮廓。如果为1,绘制轮廓及在其后的相同的级别下轮廓。如果值为2,所有的轮廓。如果等级为2,绘制所有同级轮廓及所有低一级轮廓,诸此种种。如果值为负数,函数不绘制同级轮廓,但会升序绘制直到级别为abs(max_level)-1的子轮廓
  • offset:照给出的偏移量移动每一个轮廓点坐标.当轮廓是从某些感兴趣区域(ROI)中提取的然后需要在运算中考虑ROI偏移量时,将会用到这个参数。
  • 源代码:

源代码:

import cv2
import numpy as np

# 选择图片检测或者选择视频检测
is_picture = True
file_path = '../Picture/Badminton.jpeg'

# 识别OpencCV书的阈值:'red': {'Lower': np.array([0, 53, 66]), 'Upper': np.array([74, 76, 187])


# 这个是颜色的HSV的范围值(分别代表的是H,S,V),可以根据需求进行删改
color_dist = {'red': {'Lower': np.array([0, 53, 66]), 'Upper': np.array([74, 76, 187])},
              'blue': {'Lower': np.array([100, 80, 46]), 'Upper': np.array([124, 255, 255])},
              'green': {'Lower': np.array([77, 54, 47]), 'Upper': np.array([166, 255, 255])},
              'yellow': {'Lower': np.array([26, 43, 46]), 'Upper': np.array([34, 255, 255])},
              }


class DetectProcess(object):
    def __init__(self):
        super(DetectProcess, self).__init__()

    # 形态学腐蚀操作:cv2.erode
    def erode(self, img):
        # 创建腐蚀使用的内核:3x3和5x5
        kernel = np.ones((3, 3), np.uint8)
        # 执行腐蚀操作 iterations表示执行腐蚀的次数
        img_erosion = cv2.erode(img, kernel, iterations=1)
        return img_erosion

    # 形态学膨胀:cv2.dilate
    def dilate(self, img):
        kernel = np.ones((5, 5), np.uint8)
        dilation = cv2.dilate(img, kernel, iterations=1)
        return dilation

    # 开运算:cv2.morphologyEx() :先腐蚀再膨胀,有助于消除噪音.
    def morphologyExOpening(self, img):
        kernel = np.ones((5, 5), np.uint8)
        opening = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)
        return opening

    # 闭运算:用于消除前景对象内的小孔或对象上的小黑点
    def morphologyExClosing(self, img):
        kernel = np.ones((5, 5), np.uint8)
        closing = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)
        return closing

    # sobel算子:返回的图像通道和原图相同,即彩色图像处理后仍为3通道
    # sobel得到的图像是有许多小点的的
    def sobel(self, img):
        # 核函数的取值范围:1,3,5,7,9,核函数过大效果不好
        Ksize = 3

        sobelx = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=Ksize)
        sobely = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=Ksize)

        # sobel-x方向
        sobel_X = cv2.convertScaleAbs(sobelx)
        # sobel-y方向
        sobel_Y = cv2.convertScaleAbs(sobely)
        # sobel-xy方向
        sobel_XY = cv2.addWeighted(sobel_X, 0.5, sobel_Y, 0.5, 0)
        return sobel_XY

    # Scharr算子:返回的图像通道和原图相同,即彩色图像处理后仍为3通道
    def scharr(self, img):
        scharr_x = cv2.Scharr(img, cv2.CV_8U, 1, 0)
        scharr_y = cv2.Scharr(img, cv2.CV_8U, 0, 1)
        scharrX = cv2.convertScaleAbs(scharr_x)
        scharrY = cv2.convertScaleAbs(scharr_y)
        scharr_XY = cv2.addWeighted(scharrX, 0.5, scharrY, 0.5, 0)
        return scharr_XY

    # Canny:返回的为单通道图像
    # 得到的是一条淡颜色的线
    def canny(self, img):
        # 核函数的取值为3,5,7
        # 高低阈值maxVal和minVal的取值范围1-255
        # L2g为“精准”标识符,参数为True和False
        Ksize = 3
        minVal = 20
        maxVal = 40
        L2g = True
        Canny = cv2.Canny(img, minVal, maxVal, apertureSize=Ksize, L2gradient=False)
        return Canny


# 对轮廓的面积进行筛选
def areaFilter(contours):
    """
    对面积进行筛选,若大于900的面积我们才认为是有效的边缘,这个可以根据自己的需求进行调整
    :param contours: 轮廓的集合
    :return: 识别到的有效的轮廓集合
    """
    areas = []
    for i in range(len(contours)):
        if cv2.contourArea(contours[i]) <= 900:
            continue
        else:
            areas.append(contours[i])
    return areas
    # areas.sort()
    # print("area is:", areas)


if __name__ == '__main__':
    detect = DetectProcess()
    # 调用摄像头
    cap = cv2.VideoCapture(0)

    while True:
        if is_picture:
            frame = cv2.imread(file_path)

        else:
            # 读取视频帧,ret标志读取的结果,frame为读取到的视频帧图像
            ret, frame = cap.read()
        # 高斯滤波
        gs_frame = cv2.GaussianBlur(frame, (5, 5), 0)

        # 转化成HSV图像
        hsv = cv2.cvtColor(gs_frame, cv2.COLOR_BGR2HSV)

        # 规定红色区域的HSV.
        # OpenCV中的inRange()函数可实现二值化功能(这点类似threshold()函数
        # cv2.inRange((InputArray src, InputArray lowerb,InputArray upperb, OutputArray dst);
        # 参数1:输入要处理的图像,可以为单通道或多通道。
        # 参数2:包含下边界的数组或标量。
        # 参数3:包含上边界数组或标量。
        # 参数4:输出图像,与输入图像src 尺寸相同且为CV_8U 类型。也可以作为返回值处理
        inRange_hsv = cv2.inRange(hsv, color_dist['green']['Lower'], color_dist['green']['Upper'])
        cv2.imshow('inRange_hsv', inRange_hsv)

        # 形态学腐蚀操作
        erode_frame = detect.erode(inRange_hsv)
        cv2.imshow('erode', erode_frame)
        # 形态学膨胀操作
        dilate_frame = detect.dilate(erode_frame)
        cv2.imshow('dilate', dilate_frame)

        # 开运算
        morphologyExOpening_frame = detect.morphologyExOpening(inRange_hsv)
        cv2.imshow('morphologyExOpening', morphologyExOpening_frame)

        # 闭运算
        morphologyExClosing_frame = detect.morphologyExClosing(morphologyExOpening_frame)
        cv2.imshow('morphologyExClosing', morphologyExClosing_frame)

        # Sobel_frame
        Sobel_frame = detect.sobel(morphologyExClosing_frame)
        cv2.imshow('Sobel_frame', Sobel_frame)

        # 寻找外部的点:建立RETR_EXTERNAL来统计最大外围的点。
        contours, hierarchy = cv2.findContours(Sobel_frame.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        # cnt = contours[1:50] 这个表示的是提取的图像的范围
        cv2.drawContours(frame, areaFilter(contours), -1, (0, 0, 255), 2)
        cv2.imshow('result', frame)
        if cv2.waitKey(1) & 0xFF == 27:
            break
    cv2.destroyAllWindows()

效果