图片卷积

图像滤波是尽量保留图像细节特征的条件下对目标图像的噪声进行抑制,是图像预处理中不可缺少的操作,其处理效果的好坏将直接影响到后续图像处理和分析的有效性和可靠性。

线性滤波是图像处理最基本的方法,它允许我们对图像进行处理,产生很多不同的效果。首先,我们需要一个二维的滤波器矩阵(卷积核)和一个要处理的二维图像。然后,对于图像的每一个像素点,计算它的邻域像素和滤波器矩阵的对应元素的乘积,然后加起来,作为该像素位置的值。这样就完成了滤波过程。

03-opencv-下 - 图1

03-opencv-下 - 图2

对图像和滤波矩阵进行逐个元素相乘再求和的操作就相当于将一个二维的函数移动到另一个二维函数的所有位置,这个操作就叫卷积

卷积需要4个嵌套循环,所以它并不快,除非我们使用很小的卷积核。这里一般使用3x3或者5x5。而且,对于滤波器/卷积核,也有一定的规则要求:

  1. 滤波器的大小应该是奇数,这样它才有一个中心,例如3x3,5x5或者7x7。有中心了,也有了半径的称呼,例如5x5大小的核的半径就是2
  2. 滤波器矩阵所有的元素之和应该要等于1,这是为了保证滤波前后图像的亮度保持不变。当然了,这不是硬性要求了。
  3. 如果滤波器矩阵所有元素之和大于1,那么滤波后的图像就会比原图像更亮,反之,如果小于1,那么得到的图像就会变暗。如果和为0,图像不会变黑,但也会非常暗。
  4. 对于滤波后的结构,可能会出现负数或者大于255的数值。对这种情况,我们将他们直接截断到0和255之间即可。对于负数,也可以取绝对值。

均值滤波

将卷积核内的所有灰度值加起来,然后计算出平均值,用这个平均值填充卷积核正中间的值,这样做可以降低图像的噪声,同时也会导致图像变得模糊

03-opencv-下 - 图3

03-opencv-下 - 图4

示例代码:

  1. import cv2 as cv
  2. img = cv.imread("./assets/itheima.jpg", cv.IMREAD_COLOR)
  3. cv.imshow("src",img)
  4. dst = cv.blur(img, (3,3))
  5. cv.imshow("dst",dst)
  6. cv.waitKey(0)
  7. cv.destroyAllWindows()

高斯模糊

采用均值滤波降噪会导致图像模糊的非常厉害,有没有一种方式既能保留像素点真实值又能降低图片噪声呢?那就是加权平均的方式. 离中心点越近权值越高,越远权值越低.

但是权重的大小设置非常麻烦,那么有没有一种方式能够自动生成呢? 这个就是需要用到高斯函数

高斯函数呈现出的特征就是中间高,两边低的钟形

高斯模糊通常被用来减少图像噪声以及降低细节层次。

03-opencv-下 - 图5

03-opencv-下 - 图6

示例代码:

  1. import cv2 as cv
  2. # 回调函数
  3. def updateSigma(val):
  4. # 高斯模糊 参数1:图像 参数2:卷积核大小, 参数3:标准差越大,去除高斯噪声能力越强,图像越模糊
  5. gaussian_blur = cv.GaussianBlur(img, (5,5), val)
  6. cv.imshow("gaussian",gaussian_blur)
  7. img = cv.imread("assets/itheima.jpg", cv.IMREAD_GRAYSCALE)
  8. cv.imshow("src",img)
  9. # 创建一个窗口
  10. cv.namedWindow("gaussian",cv.WINDOW_AUTOSIZE)
  11. # 创建一个窗口进度条: 参数1:名称 参数2:窗口名称 参数3: 起始值 参数4: 最大值, 参数5:回调函数
  12. cv.createTrackbar("sigma","gaussian",0,255,updateSigma)
  13. updateSigma(0)
  14. cv.waitKey(0)
  15. cv.destroyAllWindows()

中值滤波

对邻近的像素点进行灰度排序,然后取中间值,它能有效去除图像中的椒盐噪声

操作原理:
卷积域内的像素值从小到大排序
取中间值作为卷积输出

03-opencv-下 - 图7

示例代码

  1. import cv2 as cv
  2. img = cv.imread("./assets/itheima_salt.jpg", cv.IMREAD_COLOR)
  3. cv.imshow("src",img)
  4. dst = cv.medianBlur(img, 3)
  5. cv.imshow("dst",dst)
  6. cv.waitKey(0)
  7. cv.destroyAllWindows()

Sobel算子

Sobel算子是像素图像边缘检测中最重要的算子之一,在机器学习、数字媒体、计算机视觉等信息科技领域起着举足轻重的作用。在技术上,它是一个离散的一阶差分算子,用来计算图像亮度函数的一阶梯度之近似值。在图像的任何一点使用此算子,将会产生该点对应的梯度矢量

  • 水平梯度

03-opencv-下 - 图8

  • 垂直梯度

03-opencv-下 - 图9

  • 合成

03-opencv-下 - 图10

为了提高计算机效率我们通常会使用: G = |Gx|+|Gy|

这里我们使用sobel卷积算子来查看脑干图像

图片顺序: 原图—-> x 方向sobel ——> y 方向sobel ——> xy合在一起

03-opencv-下 - 图11

示例代码

  1. import cv2 as cv
  2. img = cv.imread("./assets/brain.jpg",cv.IMREAD_GRAYSCALE)
  3. cv.imshow("src",img)
  4. # sobel算子 参数1:图像, 参数2:图像的深度 -1表示和原图相同, 参数3: x方向求导的阶数 参数4: y方向求导的阶数
  5. x_sobel = cv.Sobel(img, cv.CV_32F, 1, 0)
  6. # 将图像转成8位int
  7. x_sobel = cv.convertScaleAbs(x_sobel)
  8. cv.imshow("x sobel",x_sobel)
  9. # sobel算子
  10. y_sobel = cv.Sobel(img, cv.CV_16S, 0, 1)
  11. # 将图像转成8位int
  12. y_sobel = cv.convertScaleAbs(y_sobel)
  13. cv.imshow("y_sobel",y_sobel)
  14. # 将x,y方向的内容叠加起来
  15. x_y_sobel = cv.addWeighted(x_sobel, 0.5, y_sobel, 0.5,0)
  16. cv.imshow("x,y sobel",x_y_sobel)
  17. cv.waitKey(0)
  18. cv.destroyAllWindows()

由于使用Sobel算子计算的时候有一些偏差, 所以opencv提供了sobel的升级版Scharr函数,计算比sobel更加精细.

下面是使用Scharr计算出来的边缘图像

03-opencv-下 - 图12

示例代码

  1. import cv2 as cv
  2. img = cv.imread("./assets/brain.jpg",cv.IMREAD_GRAYSCALE)
  3. cv.imshow("src",img)
  4. # sobel算子
  5. x_scharr = cv.Scharr(img, cv.CV_32F, 1, 0)
  6. # 将图像转成8位int
  7. x_scharr = cv.convertScaleAbs(x_scharr)
  8. cv.imshow("x scharr",x_scharr)
  9. # # sobel算子
  10. y_scharr = cv.Scharr(img, cv.CV_16S, 0, 1)
  11. # 将图像转成8位int
  12. y_scharr = cv.convertScaleAbs(y_scharr)
  13. cv.imshow("y scharr",y_scharr)
  14. # 将x,y方向的内容叠加起来
  15. xy_scharr = cv.addWeighted(x_scharr, 0.5, y_scharr, 0.5,0)
  16. cv.imshow("x,y scharr",xy_scharr)
  17. cv.waitKey(0)
  18. cv.destroyAllWindows()

拉普拉斯算子

通过拉普拉斯变换后增强了图像中灰度突变处的对比度,使图像中小的细节部分得到增强,使图像的细节比原始图像更加清晰。

  • 普通

03-opencv-下 - 图13

  • 增强型

03-opencv-下 - 图14

03-opencv-下 - 图15

03-opencv-下 - 图16

  1. import cv2 as cv
  2. img = cv.imread("./assets/grbq.jpg",cv.IMREAD_GRAYSCALE)
  3. cv.imshow("src",img)
  4. # 使用拉普拉斯算子
  5. dst = cv.Laplacian(img,cv.CV_32F)
  6. # 取绝对值,将数据转到uint8类型
  7. dst = cv.convertScaleAbs(dst)
  8. cv.imshow("dst",dst)
  9. cv.waitKey(0)
  10. cv.destroyAllWindows();

canny边缘检测算法

Canny算法由John F.Canny于1986年开发,是很常用的边缘检测算法。

它是一种多阶段算法,内部过程共4个阶段:

  1. 噪声抑制(通过Gaussianblur高斯模糊降噪):使用5x5高斯滤波器去除图像中的噪声
  2. 查找边缘的强度及方向(通过Sobel滤波器)
  3. 应用非最大信号抑制(Non-maximum Suppression): 完成图像的全扫描以去除可能不构成边缘的任何不需要的像素
  4. 高低阈值分离出二值图像(Hysteresis Thresholding)
    • 高低阈值比例为T2:T1 = 3:1 / 2:1
    • T2为高阈值,T1为低阈值

03-opencv-下 - 图17

03-opencv-下 - 图18

示例代码

  1. import cv2 as cv
  2. import numpy as np
  3. import random
  4. # 将图片数据读取进来
  5. img = cv.imread("img/itheima.jpg",cv.IMREAD_COLOR)
  6. cv.imshow("img",img)
  7. # 1. 将图片转成灰度图片
  8. grayImg = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
  9. # 2. canny算法
  10. dstImg = cv.Canny(grayImg,50,180)
  11. # 显示效果图
  12. cv.imshow('dstimg',dstImg)
  13. cv.waitKey(0)

双边滤波

双边滤波其综合了高斯滤波器和α-截尾均值滤波器的特点,同时考虑了空间域与值域的差别,而Gaussian Filter和α均值滤波分别只考虑了空间域和值域差别。高斯滤波器只考虑像素间的欧式距离,其使用的模板系数随着和窗口中心的距离增大而减小;α-截尾均值滤波器则只考虑了像素灰度值之间的差值,去掉α%的最小值和最大值后再计算均值。

在opencv中,双边滤波的api已经提供好啦!

  1. cv.bilateralFilter(输入图像, d, sigmaColor, sigmaSpace)
  2. src: 输入图像
  3. d: 表示在过滤过程中每个像素邻域的直径范围。如果这个值是非正数,则函数会从sigmaSpace计算该值。
  4. sigmaColor: 颜色空间过滤器的sigma值,这个参数的值越大,表明该像素邻域内有越宽广的颜色会被混合到一起,产生较大的半相等颜色区域。
  5. sigmaSpace: 坐标空间中滤波器的sigma值,如果该值较大,则意味着越远的像素将相互影响,从而使更大的区域中足够相似的颜色获取相同的颜色.

双边滤波器可以很好的保存图像边缘细节而滤除掉低频分量的噪音,但是双边滤波器的效率不是太高,花费的时间相较于其他滤波器而言也比较长。

03-opencv-下 - 图19

  1. import cv2 as cv
  2. # 将图片数据读取进来
  3. img = cv.imread("img/timg.jpg",cv.IMREAD_COLOR)
  4. cv.imshow('img',img)
  5. # 双边滤波
  6. dstImg = cv.bilateralFilter(img, 10, 50, 50)
  7. # 显示改变之后的图像
  8. cv.imshow('newimg',dstImg)
  9. cv.waitKey(0)

补充知识点

锐化滤波

03-opencv-下 - 图20

图像的锐化和边缘检测很像,首先找到边缘,然后把边缘加到原来的图像上面,这样就强化了图像的边缘,使图像看起来更加锐利了

03-opencv-下 - 图21

实际上是计算当前点和周围点的差别,然后将这个差别加到原来的位置上。另外,中间点的权值要比所有的权值和大于1,意味着这个像素要保持原来的值。

我们还可以按照公式来创建锐化滤波核:

03-opencv-下 - 图22

示例代码

  1. import cv2 as cv
  2. import numpy as np
  3. img = cv.imread("./assets/hehua.jpg",cv.IMREAD_COLOR)
  4. cv.imshow("src",img)
  5. kernel = np.array([
  6. [-1,-1,-1],
  7. [-1,9,-1],
  8. [-1,-1,-1]])
  9. dst = cv.filter2D(img,-1,kernel)
  10. cv.imshow("sharpness filter",dst)
  11. cv.waitKey(0)
  12. cv.destroyAllWindows()

霍夫变换

霍夫直线变换

霍夫直线变换(Hough Line Transform)用来做直线检测

霍夫直线变换官网文档

为了加升大家对霍夫直线的理解,我在左图左上角大了一个点,然后在右图中绘制出来经过这点可能的所有直线

03-opencv-下 - 图23

绘制经过某点的所有直线的示例代码如下,这个代码可以直接拷贝运行

  1. import cv2 as cv
  2. import matplotlib.pyplot as plt
  3. import numpy as np
  4. def draw_line():
  5. # 绘制一张黑图
  6. img = np.zeros((500, 500, 1), np.uint8)
  7. # 绘制一个点
  8. cv.line(img, (10, 10), (10, 10), (255), 1)
  9. cv.imshow("line",img)
  10. return img
  11. def hough_lines(img):
  12. rho = 1;
  13. theta = np.pi/180
  14. threshold=0
  15. lines = cv.HoughLines(img,rho, theta, threshold)
  16. dst_img = img.copy()
  17. for line in lines[:,0]:
  18. rho,theta = line
  19. a = np.cos(theta)
  20. b = np.sin(theta)
  21. x = a*rho
  22. y=b*rho
  23. x1 = int(np.round(x + 1000*(-b)))
  24. y1 = int(np.round(y + 1000*a))
  25. x2 = int(np.round(x - 1000*(-b)))
  26. y2 = int(np.round(y - 1000*a))
  27. cv.line(dst_img,(x1,y1),(x2,y2),(255,0,0),1)
  28. cv.imshow("li",dst_img)
  29. img = draw_line()
  30. hough_lines(img)
  31. cv.waitKey(0)
  32. cv.destroyAllWindows()

练习:寻找棋盘中的直线

03-opencv-下 - 图24

示例代码如下:

  1. import cv2 as cv
  2. import matplotlib.pyplot as plt
  3. import numpy as np
  4. # 1. 将图片以灰度的方式读取进来
  5. img = cv.imread("assets/weiqi.jpg", cv.IMREAD_COLOR)
  6. cv.imshow("src",img)
  7. gray_img = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
  8. # cv.imshow("gray",gray_img)
  9. #
  10. flag,thresh_img = cv.threshold(gray_img,100,255,cv.THRESH_BINARY_INV)
  11. cv.imshow("thresh_img",thresh_img)
  12. # 3. 霍夫变换
  13. # 线段以像素为单位的距离精度,double类型的,推荐用1.0
  14. rho = 1
  15. # 线段以弧度为单位的角度精度,推荐用numpy.pi/180
  16. theta = np.pi/180
  17. # 累加平面的阈值参数,int类型,超过设定阈值才被检测出线段,值越大,基本上意味着检出的线段越长,检出的线段个数越少。
  18. threshold=10
  19. # 线段以像素为单位的最小长度
  20. min_line_length=25
  21. # 同一方向上两条线段判定为一条线段的最大允许间隔(断裂),超过了设定值,则把两条线段当成一条线段,值越大,允许线段上的断裂越大,越有可能检出潜在的直线段
  22. max_line_gap = 3
  23. lines = cv.HoughLinesP(thresh_img,rho,theta,threshold,minLineLength=min_line_length,maxLineGap=max_line_gap)
  24. dst_img = img.copy()
  25. for line in lines:
  26. x1,y1,x2,y2 = line[0]
  27. cv.line(dst_img,(x1,y1),(x2,y2),(0,0,255),2)
  28. cv.imshow("dst img",dst_img)
  29. cv.waitKey(0)
  30. cv.destroyAllWindows()

霍夫圆

一个圆可以由以下公式表示

03-opencv-下 - 图25%5E2%20%2B%20(y%20-%20y_0)%5E2%3Dr%5E2%0A#card=math&code=%28x%20-%20x_0%29%5E2%20%2B%20%28y%20-%20y_0%29%5E2%3Dr%5E2%0A&id=JmUjl)

,其中

03-opencv-下 - 图26%0A#card=math&code=%28x_0%2C%20y_0%29%0A&id=t0js5)

是圆心,r是半径。圆环需要3个参数来确定,所以进行圆环检测的累加器必须是三维的,这样效率就会很低,因此OpenCV使用了霍夫梯度法这个巧妙的方法,来使用边界的梯度信息,从而提升计算的效率。

使用步骤:

  1. 霍夫圆检测对噪声敏感,先对对象做中值滤波
  2. 检测边缘,先把可能的圆边缘过滤出来
  3. 根据边缘得到最大概率的圆心,进而得到最佳半径

03-opencv-下 - 图27

OpenCV的Logo检测结果:

03-opencv-下 - 图28

  • 参数及代码
  1. def hough_circle(img):
  2. img_copy = img.copy()
  3. # 中值滤波降噪
  4. img_copy = cv2.GaussianBlur(img_copy, (3,3), 0)
  5. img_copy = cv2.medianBlur(img_copy, 5)
  6. gray = cv2.cvtColor(img_copy, cv2.COLOR_BGR2GRAY)
  7. cv2.imshow("gray", gray)
  8. """
  9. @param 8-bit 单通道图片
  10. @param method 检测方法, 当前只有cv2.HOUGH_GRADIENT
  11. @param dp 累加器分辨率和图像分辨率的反比例, 例如:
  12. 如果 dp=1 累加器和输入图像分辨率相同.
  13. 如果 dp=2 累加器宽高各为输入图像宽高的一半相同.
  14. @param minDist 检测到圆的圆心之间的最小距离。
  15. 如果参数太小,除了真实的一个之外,可能错误地检测到多个相邻的圆圈。
  16. 如果参数太大,可能会遗漏一些圆
  17. @param param1 参数1。它是两个传递给Canny边缘检测器的较高阈值(较低的阈值是此值的一半)
  18. @param param2 参数2, 它是检测阶段圆心的累加器阈值。
  19. 它越小,会检测到更多的假圆圈。较大累加器值对应的圆圈将首先返回。
  20. @param minRadius 最小圆半径.
  21. @param maxRadius 最大圆半径.
  22. 如果<=0, 会使用图片最大像素值
  23. 如果< 0, 直接返回圆心, 不计算半径
  24. """
  25. # ../images/coins.jpg
  26. circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT,
  27. dp = 1,
  28. minDist = 50,
  29. param1=160,
  30. param2=50,
  31. minRadius=0,
  32. maxRadius=100)
  33. circles = np.uint16(np.around(circles))
  34. for i in circles[0, :]:
  35. # draw the outer circle
  36. cv2.circle(img_copy, (i[0], i[1]), i[2], (0, 255, 0), 2)
  37. # draw the center of the circle
  38. cv2.circle(img_copy, (i[0], i[1]), 2, (0, 0, 255), 3)
  39. cv2.imshow("detected circles", img_copy)

寻找棋盘中的棋子

03-opencv-下 - 图29

  1. import cv2 as cv
  2. import numpy as np
  3. img = cv.imread("assets/weiqi.jpg", cv.IMREAD_COLOR)
  4. cv.imshow("src",img)
  5. # 将图片转成灰色图片
  6. gray_img = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
  7. # 霍夫圆形检测
  8. def hough_circle(gray_img):
  9. # 定义检测图像中圆的方法。目前唯一实现的方法是cv2.HOUGH_GRADIENT
  10. method = cv.HOUGH_GRADIENT
  11. # 累加器分辨率与图像分辨率的反比。例如,如果dp = 1,则累加器具有与输入图像相同的分辨率。如果dp = 2,则累加器的宽度和高度都是一半。
  12. dp = 1
  13. # 检测到的圆的圆心之间最小距离。如果minDist太小,则可能导致检测到多个相邻的圆。如果minDist太大,则可能导致很多圆检测不到。
  14. minDist = 20
  15. # param1 Canny算法阈值上线
  16. # param2 cv2.HOUGH_GRADIENT方法的累加器阈值。阈值越小,检测到的圈子越多。
  17. # minRadius : 最小的半径,如果不确定,则不指定
  18. # maxRadius : 最大的半径,若不确定,则不指定
  19. circles = cv.HoughCircles(gray_img,method,dp,minDist=minDist,param1=70,param2=30,minRadius=0,maxRadius=20)
  20. for circle in circles[0,:]:
  21. # 圆心坐标,半径
  22. x,y,r = circle
  23. # 绘制圆心
  24. cv.circle(img,(x,y),2,(0,255,0),1)
  25. # 绘制圆形
  26. cv.circle(img,(x,y),r,(0,0,255),2)
  27. cv.imshow("result",img)
  28. # 调用函数,寻找霍夫圆
  29. hough_circle(gray_img)
  30. cv.waitKey(0)
  31. cv.destroyAllWindows()

边缘与轮廓

  • 基于图像边缘提取或二值化的基础寻找对象轮廓
  • 边缘提取的阈值会最终影响轮廓发现的结果
  • 主要API有以下两个
    1. findContours发现轮廓
    2. drawContours绘制轮廓

查找轮廓

  1. 轮廓列表,继承关系 = cv.findContours(图像,轮廓检索模式,检索的方法)
  2. # hierarchy[i][3],分别表示第i个轮廓的后一个轮廓、前一个轮廓、父轮廓、内嵌轮廓的索引编号
  • 轮廓检索模式 | RETR_EXTERNAL | 只检测最外层轮廓 | | —- | —- | | RETR_LIST | 提取所有轮廓,并放置在list中,检测的轮廓不建立等级关系 | | RETR_CCOMP | 提取所有轮廓,并将轮廓组织成双层结构(two-level hierarchy),顶层为连通域的外围边界,次层位内层边界 | | RETR_TREE | 提取所有轮廓并重新建立网状轮廓结构 |
  • 轮廓检索算法 | CHAIN_APPROX_NONE | 获取每个轮廓的每个像素,相邻的两个点的像素位置差不超过1 | | —- | —- | | CHAIN_APPROX_SIMPLE | 压缩水平方向,垂直方向,对角线方向的元素,只保留该方向的重点坐标,如果一个矩形轮廓只需4个点来保存轮廓信息 | | CHAIN_APPROX_TC89_L1 | Teh-Chinl链逼近算法 | | CHAIN_APPROX_TC89_KCOS | Teh-Chinl链逼近算法 |

绘制轮廓

  1. cv.drawContours(图像,轮廓列表,轮廓索引-1则绘制所有,轮廓颜色,轮廓的宽度)

绘制外切圆

  1. ((x,y),radius) = cv.minEnclosingCircle(contour)

实现步骤:

  1. 读取图片
  2. 将图片转成一张灰色图片
  3. 对图片进行二值化处理
  4. 使用findContours查找轮廓
  5. 对轮廓进行处理
  1. import cv2 as cv
  2. def read_rgb_img(img_name):
  3. rgb_img = cv.imread(img_name,cv.IMREAD_COLOR)
  4. cv.imshow("rgb img",rgb_img)
  5. return rgb_img
  6. def convert_rgb2gray(img):
  7. gray_img = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
  8. cv.imshow("gray img", gray_img)
  9. return gray_img
  10. def convert_gray2binary(img):
  11. binary_img = cv.adaptiveThreshold(img,
  12. 255,
  13. cv.ADAPTIVE_THRESH_GAUSSIAN_C,
  14. cv.THRESH_BINARY,5,2)
  15. # _,binary_img = cv.threshold(img,50,255,cv.THRESH_BINARY_INV)
  16. cv.imshow("binary img", binary_img)
  17. return binary_img
  18. def getContours(img):
  19. _,contours,hierarchy = cv.findContours(img,cv.RETR_TREE,cv.CHAIN_APPROX_SIMPLE)
  20. print(contours,hierarchy)
  21. return contours
  22. def draw_contours(img,contours):
  23. index = -1 # 所有的轮廓
  24. thickness = 2 # 轮廓的宽度
  25. color = (255,125,125) # 轮廓的颜色
  26. cv.drawContours(img,contours,index,color,thickness)
  27. cv.imshow('draw contours',img)
  28. if __name__ == '__main__':
  29. img_name = "assets/shape0.jpg"
  30. rgb_img = read_rgb_img(img_name)
  31. gray_img = convert_rgb2gray(rgb_img)
  32. binary_imgage = convert_gray2binary(gray_img)
  33. contours = getContours(binary_imgage)
  34. draw_contours(rgb_img,contours)
  35. cv.waitKey(0)
  36. cv.destroyAllWindows()

网球案例

03-opencv-下 - 图30

实现步骤:

  1. 读取图片
  2. 过滤出球的颜色
  3. 使用轮廓检测
  4. 找到球的中心点
  5. 展示信息
  1. import cv2 as cv
  2. import numpy as np
  3. def read_rgb_img(img_name):
  4. rgb_img = cv.imread(img_name,cv.IMREAD_COLOR)
  5. cv.imshow("rgb img",rgb_img)
  6. return rgb_img
  7. def convert_rgb2gray(img):
  8. gray_img = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
  9. # 采用高斯滤波去掉噪点
  10. gray_img = cv.GaussianBlur(gray_img,(5,5),0)
  11. cv.imshow("gray img", gray_img)
  12. return gray_img
  13. def convert_gray2binary(img):
  14. binary_img = cv.adaptiveThreshold(img,255,cv.ADAPTIVE_THRESH_GAUSSIAN_C,cv.THRESH_BINARY_INV,5,2)
  15. cv.imshow("binary img", binary_img)
  16. return binary_img
  17. def filter_tenis(img,lower_color,upper_color):
  18. hsv_img = cv.cvtColor(img,cv.COLOR_BGR2HSV)
  19. # 查找颜色
  20. mask_img = cv.inRange(hsv_img, lower_color, upper_color)
  21. cv.imshow("mask img",mask_img)
  22. return mask_img
  23. def getContours(img):
  24. _,contours,hierarchy = cv.findContours(img,cv.RETR_EXTERNAL,cv.CHAIN_APPROX_SIMPLE)
  25. print(contours,hierarchy)
  26. return contours
  27. def process_tenis_contours(rgb_img,contours):
  28. black_img = np.zeros([rgb_img.shape[0],rgb_img.shape[1],3],np.uint8)
  29. for c in contours:
  30. # 计算面积
  31. area = cv.contourArea(c)
  32. # 该函数计算曲线长度或闭合轮廓周长。
  33. perimeter = cv.arcLength(c,True)
  34. # 获取最小的外切圆
  35. ((x,y),radius) = cv.minEnclosingCircle(c)
  36. # 绘制轮廓
  37. cv.drawContours(rgb_img,[c],-1,(150,250,150),2)
  38. cv.drawContours(black_img,[c],-1,(150,250,150),2)
  39. # 获取轮廓中心点
  40. # cx,cy = get_contour_center(c)
  41. # print(cx,cy)
  42. x = int(x)
  43. y = int(y)
  44. cv.circle(rgb_img,(x,y),int(radius),(0,0,255),2)
  45. cv.circle(black_img,(x,y),int(radius),(0,0,255),2)
  46. print("Area:{},primeter:{}".format(area,perimeter))
  47. print("number of contours:{}".format(len(contours)))
  48. cv.imshow("rgb img contours",rgb_img)
  49. cv.imshow("black img contours",black_img)
  50. if __name__ == '__main__':
  51. img_name = "assets/tenis1.jpg"
  52. # 定义范围
  53. lower_color = (30, 120, 130)
  54. upper_color = (60, 255, 255)
  55. rgb_img = read_rgb_img(img_name)
  56. binary_imgage = filter_tenis(rgb_img,lower_color,upper_color)
  57. contours = getContours(binary_imgage)
  58. process_tenis_contours(rgb_img,contours)
  59. cv.waitKey(0)
  60. cv.destroyAllWindows()

形态学变换

膨胀与腐蚀

形态学变化是基于图像形状的一些简单操作。操作对象一般是二值图像,需要两个输入,一个是我们的原图,另一个是3x3的结构元素(内核),决定了膨胀操作的本质。常见的操作是图像的膨胀和腐蚀。以及他们的进阶操作注入Opening、Closing、Gradient等等。(参考

结构元素的形状

MORPH_RECT 矩形
MORPH_ELLIPSE 椭圆形
MORPH_CROSS 十字型

膨胀Dilation

跟卷积操作非常类似.有图像A和3x3的结构元素,结构元素在A上进行滑动.计算结构元素在A上覆盖的最大像素值来替换当前结构元素对应的正中间的元素

膨胀的作用:

  1. 对象边缘增加一个像素
  2. 使对象边缘平滑
  3. 减少了对象与对象之间的距离

03-opencv-下 - 图31

示例代码

  1. /**
  2. * 膨胀: 用内核中最大的值填充当前像素点的值
  3. */
  4. import cv2 as cv
  5. src = cv.imread("../img/morph-closing.jpg",cv.IMREAD_GRAYSCALE)
  6. cv.imshow("src",src)
  7. kernel = cv.getStructuringElement(cv.MORPH_RECT,(5,5))
  8. dst = cv.dilate(src,kernel)
  9. cv.imshow("dst",dst)
  10. cv.waitKey()

腐蚀Erosion

03-opencv-下 - 图32

腐蚀和膨胀过程类似,唯一不同的是以覆盖的最小值替换当前的像素值

侵蚀的作用:

  1. 对象边缘减少一个像素
  2. 对象边缘平滑
  3. 弱化了对象与对象之间连接

示例代码

  1. /**
  2. * 腐蚀: 用内核覆盖的最小像素值替换当前像素值
  3. */
  4. import cv2 as cv
  5. src = cv.imread("../img/morph-j.jpg",cv.IMREAD_GRAYSCALE)
  6. cv.imshow("src",src)
  7. kernel = cv.getStructuringElement(cv.MORPH_RECT,(5,5))
  8. dst = cv.erode(src,kernel)
  9. cv.imshow("dst",dst)
  10. cv.waitKey()

开操作Opening

03-opencv-下 - 图33

先腐蚀,后膨胀,主要应用在二值图像或灰度图像

先腐蚀: 让当前窗口中最小的颜色值替换当前颜色值

后膨胀: 让当前窗口中最大的颜色值替换当前颜色值

作用:

  1. 一般用于消除小的干扰或噪点(验证码图的干扰线)
  2. 可以通过此操作配合结构元素,提取图像的横线和竖线

示例代码

  1. /**
  2. * 开操作: 先腐蚀,再膨胀
  3. */
  4. import cv2 as cv
  5. src = cv.imread("../img/morph-opening.jpg",cv.IMREAD_GRAYSCALE)
  6. cv.imshow("src",src)
  7. kernel = cv.getStructuringElement(cv.MORPH_RECT,(5,5))
  8. dst = cv.morphologyEx(src,cv.MORPH_OPEN,kernel)
  9. cv.imshow("dst",dst)
  10. cv.waitKey()

去除验证码上面的干扰线

03-opencv-下 - 图34

闭操作Closing

先膨胀,后侵蚀

一般用于填充内部缝隙

03-opencv-下 - 图35

示例代码

  1. /**
  2. * 先膨胀再腐蚀
  3. */
  4. import cv2 as cv
  5. src = cv.imread("../img/morph-closing.jpg",cv.IMREAD_GRAYSCALE)
  6. cv.imshow("src",src)
  7. kernel = cv.getStructuringElement(cv.MORPH_RECT,(5,5))
  8. dst = cv.morphologyEx(src,cv.MORPH_CLOSE,kernel)
  9. cv.imshow("dst",dst)
  10. cv.waitKey()

小结

形态学变换中还有一些其它的变换,大家可以自行尝试查看执行效果

  1. 开运算
  2. 先对图像腐蚀,再膨胀
  3. dst=open(src,element)=dilate(erode(src,element))
  4. 闭运算
  5. 先膨胀再腐蚀
  6. dst=close(src,element)=erode(dilate(src,element))
  7. 形态梯度
  8. 膨胀图和腐蚀图之差
  9. dst=morph_grad(src,element)=dilate(src,element)−erode(src,element)
  10. 顶冒
  11. 原图和开运算图之差
  12. dst=tophat(src,element)=srcopen(src,element)
  13. 黑冒
  14. 闭运算结果和原图之差
  15. dst=blackhat(src,element)=close(src,element)−src

距离变换

Opencv中distanceTransform方法用于计算图像中每一个非零点距离自己最近的零点的距离,方法输出的信息为距离而不是颜色值,图像上越亮的点,代表了离零点的距离越远。
可以用来细化轮廓,或者寻找物体的质心!
方法参数说明:

  1. cv.distanceTransform(二值图,距离类型,卷积核大小)

distanceType类型介绍

DIST_USER User defined distance.
DIST_L1 distance = |x1-x2| + |y1-y2|
DIST_L2 the simple euclidean distance
DIST_C distance = max(|x1-x2|,|y1-y2|)
DIST_L12 L1-L2 metric: distance = 2(sqrt(1+x*x/2) - 1))
DIST_FAIR distance = c^2(|x|/c-log(1+|x|/c)), c = 1.3998
DIST_WELSCH distance = c^2/2(1-exp(-(x/c)^2)), c = 2.9846
DIST_HUBER distance = |x|<c ? x^2/2 : c(|x|-c/2), c=1.345

03-opencv-下 - 图36

车道线检测

本节我们使用opencv来完成一个车道线检测的案例,下图为案例最终处理的结果

完成这样的案例我们需要经历哪些步骤呢 ? 我们先来思考一下解决问题的思路.当前情况下,摄像头拍出了很多的东西,例如路边的杂草远方的山.但是在我们自动驾驶的过程中,我们并不需要这么多东西,所以我们要考虑提取感兴趣的区域.有了感兴趣的区域之后,我们接下来就需要来识别道路.大家可能会想道路可能会有弯道,但是在小范围内,它还是直线,所以我们可以使用前面我们学过的霍夫直线来进行检测

这张摄像头拍摄到的照片

03-opencv-下 - 图37

这张是我们使用canny边缘检测算法得到的图片

03-opencv-下 - 图38

然后使用多边形截去不感兴趣的区域

03-opencv-下 - 图39

通过以上三个步骤之后,下面我们就可以使用霍夫直线的方式,提取到车辆直线啦!

示例代码

  1. import cv2 as cv
  2. import numpy as np
  3. import matplotlib.pyplot as plt
  4. def canny(image):
  5. gray = cv.cvtColor(image,cv.COLOR_BGR2GRAY);
  6. blur = cv.GaussianBlur(gray,(5,5),0)
  7. canny= cv.Canny(blur,50,100)
  8. return canny
  9. def roi(image):
  10. height = image.shape[0]
  11. polygons = np.array([[(200,height),(1100,height),(550,250)]])
  12. mask = np.zeros_like(image);
  13. cv.fillPoly(mask,polygons,255)
  14. masked_image = cv.bitwise_and(image,mask)
  15. return masked_image
  16. def display_lines(image,lines):
  17. line_image = np.zeros_like(image);
  18. for line in lines:
  19. x1,y1,x2,y2 = line.reshape(4)
  20. cv.line(line_image,(x1,y1),(x2,y2),(255,255,0),10);
  21. return line_image
  22. def make_coordinates(image,parameters):
  23. slope = parameters[0]
  24. intercept = parameters[1]
  25. y1 = image.shape[0]
  26. y2 = int(y1*0.6)
  27. x1 = int((y1 - intercept)/slope)
  28. x2 = int((y2 - intercept)/slope)
  29. return np.array([x1,y1,x2,y2])
  30. def average_slop_intercept(image,lines):
  31. left_fit = []
  32. right_fit = []
  33. print(lines)
  34. for line in lines:
  35. x1, y1, x2, y2 = line.reshape(4)
  36. print(x1,y1,x2,y2)
  37. parameters = np.polyfit((x1,x2),(y1,y2),1)
  38. print(parameters)
  39. slope = parameters[0]
  40. intercept = parameters[1]
  41. if slope > 0 :
  42. left_fit.append((slope,intercept))
  43. else:
  44. right_fit.append((slope,intercept))
  45. #yong
  46. result_lines = []
  47. if len(left_fit):
  48. left_fit_average = np.average(left_fit, axis=0)
  49. left_line = make_coordinates(image,left_fit_average)
  50. result_lines.append(left_line)
  51. if len(right_fit)>0:
  52. right_fit_average = np.average(right_fit, axis=0)
  53. right_line = make_coordinates(image,right_fit_average)
  54. result_lines.append(right_line)
  55. return result_lines
  56. cv.namedWindow("result",cv.WINDOW_NORMAL)
  57. capture = cv.VideoCapture()
  58. capture.open("test2.mp4")
  59. if capture.isOpened():
  60. flag = True
  61. while flag:
  62. flag,image = capture.read();
  63. if not flag: break
  64. # image = cv.imread("test_image.jpg")
  65. lane_image = image # np.copy(image);
  66. print(lane_image.shape)
  67. canny_image = canny(lane_image)
  68. cropped_image = roi(canny_image)
  69. lines = cv.HoughLinesP(cropped_image,2,np.pi/180,100,minLineLength=40,maxLineGap=5)
  70. average_lines = average_slop_intercept(image,lines)
  71. lines_image = display_lines(lane_image,average_lines)
  72. combo_image = cv.addWeighted(lane_image,0.8,lines_image,1,1)
  73. cv.imshow("result",combo_image)
  74. cv.waitKey(10)
  75. cv.waitKey(0)
  76. cv.destroyAllWindows()

注意:可能会出现Could not load the Qt platform plugin “xcb”错误,

安装pip install opencv-python-headless,

如果不能解决问题,则需要安装sudo apt install --reinstall libxcb-xinerama0