欢迎来到代码驿站!

Python代码

当前位置:首页 > 软件编程 > Python代码

Python OpenCV实现图形检测示例详解

时间:2022-10-27 10:45:23|栏目:Python代码|点击:

图形检测在计算机视觉开发中是一项非常重要的操作,算法通过对图像的检测,分析出图像中可能存在哪些形状。除此之外,除了让计算机识别轮廓之外,轮廓也需要让人看到,这就需要再分别读这些轮廓的形状进行描绘。

1. 轮廓识别与描绘

cv2.findContours() & cv2.drawContours() 方法

在Python中OpenCV提供了findContours() 方法来判断图像的轮廓,drawContours()方法来绘制轮廓。

1.1 cv2.findComtours()方法

cv2.findComtours()方法的语法如下

cv2.contours, hierarchy = findContours(image, mode, method)

其中

  • image 即原图像
  • mode 轮廓检索模式,具体参数被总结在了下表中
  • method 使用的方法,具体参数也被总结在了下表中

contours是一个列表,列表的每一个元素都是由某个轮廓的像素的坐标组成的数组。

hierarchy是轮廓与轮廓之间的层次关系。

mode取值表

mode值 描述
cv2.RETR_EXTERNAL 只检测外轮廓
cv2.RETR_LIST 检测所有轮廓,但不建立层次关系
cv2.RETR_CCOMP 检测所有轮廓,并建立两级层次关系
cv2.RETR_TREE 建立所有轮廓,并建立树状结构的层次关系

method取值表

cv2.PATH_APPROX_ 储存轮廓上所有点
cv2.PATH_APPROX_NONE 只保存水平、垂直或对角线轮廓的端点
cv2.PATH_APPROX_SIMPLE Ten-Chinl
cv2.PATH_APPROX_TC89_L1 Ten-Chinl近似算法的一种
cv2.PATH_APPROX_TC89_KCOS Ten-Chinl近似算法的一种

1.2 cv2.drawContours() 方法

cv2.drawContours(image, contours, contourIdx, color, thickness=None, lineType=None, hierarchy=None, maxLevel=None, offset=None)

  • image 目标图像
  • contours findComtours()方法得到的轮廓列表
  • contourldx 轮廓中列表中,绘制轮廓的对象的索引,如果为-1则表示绘制所有
  • color 绘制线条时的颜色,使用BGR格式描述
  • thickness 线条粗细程度,-1表示实心
  • lineType 绘制轮廓时线条的类型(可选参数)
  • hierarchy findComtours()方法得到的层次关系(可选参数)
  • maxLevel 绘制轮廓的层次深度,最深绘制在maxLevel层。(可选参数)
  • offset 偏移量 (可选参数)

drawContours() 方法返回的是一个图像(数组)。

1.3 代码示例

以队此小鸟图操作为例(test1.jpg):

import cv2
img = cv2.imread("test1.jpg")  
# 彩色图像转为变成单通道灰度图像
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)  
# 二值化处理
t, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)  
# 检测图像中出现的所有轮廓,记录轮廓的每一个点
contours, hierarchy = cv2.findContours(binary, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)
# 绘制所有轮廓,宽度为5,颜色为红色
cv2.drawContours(img, contours, -1, (0, 0, 255), 5)
cv2.imshow("img", img)
cv2.waitKey()
cv2.destroyAllWindows()

程序执行结果展示如下,图中所有能够识别出的轮廓被描出:

2. 轮廓拟合

轮廓拟合,即,将凹凸不平的轮廓用平整的几何图形体现出来。这里展示使用矩形和圆形拟合两种方法。

2.1 矩形包围框拟合 - cv2.boundingRect()

在Python中OpenCV提供了cv2.boundingRect()来计算轮廓的最小矩形边界的坐标 ,其语法如下

retval = cv2.boundingRect(array)

其中参数array为轮廓数组。即,cv2.findComtours()方法的执行结果中的contours中的元素。

返回值retval是一个包含着四个整数值的元组,四个值依次是左上角顶点的横坐标,左上角顶点的纵坐标,矩形的宽,矩形的高。

常常也可以写成x,y,w,h = retval = cv2.boundingRect(array)

还以这张小鸟图片(test1.jpg)为例,在上一个部分的示例中,我们找出并绘制出了图中所有的轮廓,经过调试,发现被识别出的轮廓共有94个。

我们要从数组列表中,选择出表示小鸟轮廓的位置的数组。

从上图中可以看出,小鸟的轮廓是所有轮廓中最大的。即该数组的 shape[0] 最大。即我们只用找出 shape[0]最大的即可。

import cv2
img = cv2.imread("test1.jpg") 
# 从彩色图像变成单通道灰度图像
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)  
# 将灰度图像进行二值化阈值处理
t, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
# 获取二值化图像中的轮廓极轮廓层次数据
contours, hierarchy = cv2.findContours(binary, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
# 找出小鸟的轮廓
n1 = 1
n2 = 0
index = 0
for arr in contours:
    if len(arr) > n1:
        n1 = len(arr)
        index = n2
    n2 += 1
print(index)

x, y, w, h = cv2.boundingRect(contours[index])
# 绘制红色矩形
cv2.rectangle(img, (x, y), (x + w, y + h), (0, 0, 255), 2)  
cv2.imshow("img", img) 
cv2.waitKey()
cv2.destroyAllWindows() 

绘制出矩形包围框效果如下:

2.2圆形包围框拟合 - cv2.minEnclosingCircle()

在Python中OpenCV提供了cv2.minEnclosingCircle()来计算轮廓的最小圆形边界的圆心和半径 ,其语法如下

center,radius = minEnclosingCircle(points)

其中

  • points的轮廓数组
  • center最小圆形包围框的圆心的横纵坐标。是元组类型。
  • radius是最小圆形包围款更多半径,浮点类型。

同样的算法,只是这次调用cv2.minEnclosingCircle()方法,其他不变:

import cv2
img = cv2.imread("test1.jpg")  # 读取原图
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)  # 从彩色图像变成单通道灰度图像
# 将灰度图像进行二值化阈值处理
t, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
# 获取二值化图像中的轮廓极轮廓层次数据
contours, hierarchy = cv2.findContours(binary, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)

n1 = 1
n2 = 0
index = 0
for arr in contours:
    if len(arr) > n1:
        n1 = len(arr)
        index = n2
    n2 += 1


center, radius = cv2.minEnclosingCircle(contours[index])
# 圆心点横坐标转为近似整数
x = int(round(center[0]))
# 圆心点纵坐标转为近似整数  
y = int(round(center[1]))  
cv2.circle(img, (x, y), int(radius), (0, 0, 255), 2)
cv2.imshow("img", img)
cv2.waitKey()
cv2.destroyAllWindows()

绘制出圆形包围框效果如下(因为尺寸问题,只画出了一部分):

3. 凸包 绘制

使用矩形框和圆形框对图形的贴合程度往往都会较差。为了提高这个贴合程度,我们可以使用“凸包”。

所谓凸包,就是最逼近轮廓的多边形。

在Python中OpenCV提供了 cv2.bonvexHull()方法来计算凸包

cv2.bonvexHull()方法语法如下:

hull = convexHull(points, clockwise=None, returnPoints=None)

其中

  • points 是轮廓数组
  • clockwise 是布尔类型的参数,默认为True,表示凸包中的点按顺时针排序,为False时则按逆时针 排序。
  • returnPoints 是布尔类型的参数,默认为True时返回点坐标。如果为False则返回点索引。

返回值hull是凸包的点阵数组

依然以“test1.jpg为例”,为图中的小鸟绘制凸包:

import cv2

img = cv2.imread("test1.jpg")
# 转为灰度图像
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 二值化阈值处理
ret, binary = cv2.threshold(gray, 127, 225, cv2.THRESH_BINARY)
# 检测图像中出现的所有轮廓
contours, hierarchy = cv2.findContours(binary, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)

n1 = 1
n2 = 0
index = 0
for arr in contours:
    if len(arr) > n1:
        n1 = len(arr)
        index = n2
    n2 += 1

hull = cv2.convexHull(contours[index])
cv2.polylines(img, [hull], True, (0, 0, 255), 2)
cv2.imshow("img", img)
cv2.waitKey()
cv2.destroyAllWindows()

4. Canny边缘检测 - cv2.Canny()

4.1 cv2.Canny() 用法简介

Canny边缘检测算法是John F.Canny在1986年开发的一个多级边缘检测算法。

Canny边缘检测算法通过像素的梯度变化寻找图像的边缘,最终可以绘制出十分精细的二值边缘图像

edges = cv2.Canny(image, threshold1, threshold2, apertureSize=None, L2gradient=None)

其中

  • image 即原图像
  • threshold1 ??第一个阈值,一般为最小阈值
  • threshold2 ??第二个阈值,一般为最大阈值
  • apertureSize ?Sobel算子的孔径大小
  • L2gradient 计算图像梯度的标识。默认为False。为True时采用更精准的算法进行计算。

关于这两个阈值怎么用,这涉及到了算法的底层逻辑,还请自行探索。这里一种可以接受的解释是:低于阈值1的像素点,会被认为不构成边缘,而高于阈值2的像素点,会被认为构成边缘。

最后返回值edges是一个二值的灰度图像。

4.2 代码示例

下边对test1.jpg以三组不同的阈值来做Canny边缘检测,根据处理结果感受算法效果:

- 当阈值为 10-50 时

import cv2
img = cv2.imread("test1.jpg")
r1 = cv2.Canny(img, 10, 50)
cv2.imshow("r1", r1)
cv2.waitKey()
cv2.destroyAllWindows()

- 当阈值为100-200时

import cv2
img = cv2.imread("test1.jpg")
r2 = cv2.Canny(img, 100, 200)
cv2.imshow("r2", r2)
cv2.waitKey()
cv2.destroyAllWindows()

- 当阈值为400-600时

import cv2
img = cv2.imread("test1.jpg")
r3 = cv2.Canny(img, 400, 600)

cv2.imshow("r3", r3)
cv2.waitKey()
cv2.destroyAllWindows()

5. 霍夫变换

5.1 概述

霍夫变换是一种特征检测,通过霍夫变换可以检测出图像中存在的特殊的形状。比如,直线,圆等。

霍夫变换检测直线时,算法有两个,

一个是cv2.HoughLines() 方法,用于检测无限延长的直线;

另一个是cv2.HoughLinesP() 方法,用于检测线段。

霍夫变换检测圆,使用的是**cv2.HoughCircles()**方法。

使用这三个方法前,都要先对图像进行降噪处理(使用滤波器),以去除干扰。

5.2 cv2.HoughLines() 检测直线

cv2.HoughLines()语法如下:

lines = cv.HoughLines( image, rho, theta, threshold[,srn][,stn])

其中

  • image 即原图像
  • rho 指的是搜索直线使用的半径步长,其值为1时表示检测所有。(即极坐标中的ρ)
  • theta 指的是搜索直线的角度,值为π/180°时,表示检测所有角度。(即极坐标中的θ)
  • threshold 指的是阈值,点的数量(也称投票数)。因为直线的长度取决于直线上的点的数量。所以如果达不到这个长度,就不会被
  • 判定为直线。同理,当该阈值越小,检测出的直线也就越多。
  • srn 对于多尺度霍夫变换,srn表示对rho的 距离分辨率 的除数
  • stn 对于多尺度霍夫变换,stn表示对theta的 距离分辨率 的除数
  • min_theat 对于标准和多尺度Hough变换,检查线条的最小角度。必须介于0和最大θ之间。
  • max_theat 对于标准和多尺度Hough变换,检查线条的最大角度。必须介于min_theta和CV_PI之间。

返回值lines,是一个数组,shape为(n, 1, 2),n表示检测出的所有线段数目,每个线段用极坐标(ρ, θ)表示。

以此跨海大桥图(test2.jpg)为例,对其使用cv2.HoughLines()方法,并绘制直线:

import cv2
import numpy as np

img = cv2.imread("test2.jpg")
o = img.copy()
# 使用中值滤波进行降噪
o = cv2.medianBlur(o, 5)
# 从彩色图像变成单通道灰度图像
gray = cv2.cvtColor(o, cv2.COLOR_BGR2GRAY)
# 绘制边缘图像
binary = cv2.Canny(o, 50, 150)

# 检测直线 不限步长,不限角度,至少50个点确定一条线
lines = cv2.HoughLines(binary, 1, np.pi / 180, 50)
# print(lines)
# print(lines.shape)

for line in lines:
    rho, theta = line[0]
    a = np.cos(theta)
    b = np.sin(theta)
    x0 = a * rho
    y0 = b * rho
    x1 = int(x0 + 1000 * (-b))
    y1 = int(y0 + 1000 * (a))
    x2 = int(x0 - 1000 * (-b))
    y2 = int(y0 - 1000 * (a))

    cv2.line(img, (x1, y1), (x2, y2), (0, 0, 255), 2)
cv2.imshow("img", img)
cv2.waitKey()
cv2.destroyAllWindows()

识别效果如下,可以自行调节参数进行改良,并在这个过程中具体感受每个参数的作用。

当选择至少50个点确定一条线时,一共检测出168根直线:

当选择至少100个点确定一条线时,一共检测出35根直线:

当选择至少300个点确定一条直线时,符合要求的直线有3根:

当选择至少380个点确定一条直线时,这样的直线还剩一根:

相比检测直线,检测线段的cv2.HoughLinesP()相对要更常用些。

5.3 cv2.HoughLinesP() 检测线段

lines = cv2.HoughLinesP(image, rho, theta, threshold, minLineLength=None, maxLineGap=None)

检测线段

  • image 原图
  • rho 检测直线使用的半径步长,值为1时表示所有可能的半径步长
  • theta 搜索直线的角度
  • threshold 阈值,该值越小,检测出的直线越多。
  • minLineLength表示线段的最小长度,小于该长度的线段不会被记录在结果中。值越大线段越少。
  • maxLineGap 表示允许将同一行的点连接起来的最大距离。 值越大线段越多。

返回值lines,是一个数组,shape为(n, 1, 4),n表示检测出的所有线段数目,4指的是每个线段的两端端点的笛卡尔坐标(x, y) 坐标的四个点。

其中minLineLength(最小线段长度)和maxLineGap(最小线段距离)两个参数,都是越大,识别的线段越少。

import cv2
import numpy as np

img = cv2.imread("test2.jpg")
o = img.copy()
# 使用中值滤波进行降噪
o = cv2.medianBlur(o, 5)
# 从彩色图像变成单通道灰度图像
gray = cv2.cvtColor(o, cv2.COLOR_BGR2GRAY)
# 绘制边缘图像
binary = cv2.Canny(o, 50, 150)

# 检测线段,不限步长,不限角度,至少100个点确定一条线。最大将距离为200的线段连城一条线。
lines = cv2.HoughLinesP(binary, 1, np.pi / 180, 50, minLineLength=100, maxLineGap=200)
print(lines.shape)
for line in lines:
    x1, y1, x2, y2 = line[0]
    cv2.line(img, (x1, y1), (x2, y2), (0, 0, 255), 2)
cv2.imshow("canny", binary)
cv2.imshow("img", img)
cv2.waitKey()
cv2.destroyAllWindows()

二值化边缘图案效果:

描绘线段效果(minLineLength=100)至少100个点确定一条线段(上边代码),最大将距离为200的线段连接起来
共描绘了23条线:

描绘线段效果(minLineLength=500)至少500个点确定一条线段,最大将距离为200的线段连接起来
共描绘了6条线段:

描绘线段效果(minLineLength=720)至少720个点确定一条线段,最大将距离为200的线段连接起来
共描绘了一条线:

5.4 cv2.检测圆 - HoughCircles()

circles = HoughCircles(image, method, dp, minDist, circles=None, param1=None, param2=None, minRadius=None, maxRadius=None)

其中

  • image 原图像(降噪、灰度处理后的)
  • method 检测方法。
  • dp 累加器分辨率与原图分辨率之比的倒数。值为1时累加器与原图像有着相同的分辨率。通常选择1作为参数。(值为2时则累加器的分辨率是原图像的一半)
  • minDist 圆心之间的最小距离
  • param1 参数1,表示Canny边缘检测的最大阈值。是可选参数。
  • param2 参数2,表示检测结果投票数。即至少多少个点确定一个圆。值越大,识别的圆越少,约精准。是可选参数。
  • minRadius 圆环的最小半径(可选参数)
  • maxRadius 圆环的最大半径(可选参数)

返回值circles是一个数组,数组内是所有检测出的圆环,shape为(n,1,3)。其中3表示圆心的x坐标,圆心的y坐标和半径长度三个指标。

了解完语法,

接下来我们来检测下图(test3.jpg)中的客家土楼中的圆形。

import cv2
import numpy as np

img = cv2.imread("test3.jpg")
# 使用中值滤波进行降噪
o = cv2.medianBlur(img, 11) 
# 从彩色图像变成单通道灰度图像
gray = cv2.cvtColor(o, cv2.COLOR_BGR2GRAY)  
# 展示灰度图像
cv2.imshow('gray', gray)
# 检测圆环,圆心最小间距为50,Canny最大阈值为40,投票数超过63。最小半径为10,最大半径为50
circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, 1, 50, param1=40, param2=63, minRadius=10, maxRadius=100)
# 将数组元素四舍五入成整数
circles = np.uint(np.around(circles))
# 遍历圆环结果
for c in circles[0]:
    # 圆心横坐标、纵坐标和圆半径
    x, y, r = c
    # 绘制圆环
    cv2.circle(img, (x, y), r, (0, 0, 255), 3)
    # 绘制圆心
    cv2.circle(img, (x, y), 2, (0, 0, 255), 3)
cv2.imshow("img", img)
cv2.waitKey()
cv2.destroyAllWindows()

降过噪的灰度图像如下:

识别结果呈现如下,如图成功识别出了图中所有土楼的圆形:

上一篇:python函数传参意义示例详解

栏    目:Python代码

下一篇:使用Python写个小监控

本文标题:Python OpenCV实现图形检测示例详解

本文地址:http://www.codeinn.net/misctech/217414.html

推荐教程

广告投放 | 联系我们 | 版权申明

重要申明:本站所有的文章、图片、评论等,均由网友发表或上传并维护或收集自网络,属个人行为,与本站立场无关。

如果侵犯了您的权利,请与我们联系,我们将在24小时内进行处理、任何非本站因素导致的法律后果,本站均不负任何责任。

联系QQ:914707363 | 邮箱:codeinn#126.com(#换成@)

Copyright © 2020 代码驿站 版权所有