资讯详情

OpenCV中的图像处理 —— 霍夫线 / 圈变换 + 图像分割(分水岭算法) + 交互式前景提取(GrabCut算法)

OpenCV图像处理 —— 霍夫线 / 圈变换 图像分割(分水岭算法) 提取交互式前景(GrabCut算法)

??上一节我们介绍了OpenCV我们来谈谈霍夫线/圈变换的原理和应用,用分水岭算法实现图像分割和使用GrabCut算法实现交互式前景提取

??大家好,这里是ErrorError!,某高校大二本科生读的一枚♂同学们,希望以后能在机器视觉领域有所成就,很荣幸能在机器视觉领域有所成就CSDN认识很多志同道合、各方面都有造诣的朋友,让我们一起加油吧~??

??上节内容:OpenCV图像处理 —— 傅里叶变换 模板匹配

目录???

    • OpenCV图像处理 —— 霍夫线 / 圈变换 图像分割(分水岭算法) 提取交互式前景(GrabCut算法)
    • 1. 霍夫线变换
      • 1.1 HoughLines工作原理
      • 1.2 OpenCV霍夫曼在中间的变化
      • 1.3 霍夫线的概率变化
    • 2. 霍夫圈变换
    • 3. 图像分割和分水岭算法
      • 3.1 分水岭算法
      • 3.2 实现图像分割
    • 4. 使用GrabCut算法实现交互式前景提取
      • 4.1 GrabCut算法
      • 4.2 使用OpenCV进行GrabCut算法

1. 霍夫线变换

1.1 HoughLines工作原理

在了解了上一节的模板匹配之后,你发现我们有一点目标测试的原型吗?霍夫线的变换也是一个深入的关键点。如果形状可以以数学形式表示,霍夫变换是一种流行的测试任何形状的技术,即使形状损坏或变形,我们也会看到它

??:通常,一条线可以表示y=mx cy=mx c或以参数的形式表示ρ=xcosθ ysinθ,其中ρ从原点到线的垂直距离,θ逆时针测量垂直线和水平轴形成的角度(方向随我们如何表示坐标系而变化)。因此,如果线路通过原点以下,它将是正的ρ如果线在原点上方,角度小于180,将角度取小于180,而不是大于180,ρ取负值,任何垂直线0度,水平线90度

??:任何一行都可以使用(ρ,θ)这两个术语说。因此,首先创建2D数组或累加器(以保存两个参数的值),并将其初始设置为0。让行表示ρ,列表示θ,阵列的大小取决于所需的精度。假设角度精度为1度,则需要180列。对于ρ,图像的对角线长度可能是最大距离。因此,以像素精度为准,行数可以是图像的对角线长度

??:假设有一个100*100图像中间有一条水平线。取直线的第一点,我们知道它的坐标(x,y)值。现在在线性方程中,值θ= 0,1,2,… 放入180,然后检查ρ。对于每对(ρ,θ),对应于累加器(ρ,θ)单元格将值增加1。

现在,与上述操作相同,对行的第二点进行递增(ρ,θ)对应单元格中的值,此操作使单元格(50,90)=2。事实上,我们是对的(ρ,θ)投票值。我们继续执行线路上的每一点。在每一点上,单元格(50、90)都会增加或投票,而其他单元格可能或不投票。这样,单元格(50、90)的投票数最高。因此,如果我们在累加器中搜索最大票数,我们将获得(50、90)值,这意味着图像中的一条线与原点之间的距离为50,角度为90度

1.2 OpenCV霍夫曼在中间的变化

OpenCV在函数中包装上述所有霍夫曼变换过程cv.HoughLines(),它回到了一个math:(rho,theta)值的数组,ρ以像素为单位,θ以弧度为单位。这个函数包括四个参数,第一个是二进制原图,所以我们会先使用阈值或Canny边缘检测,第二和第三个参数是ρ和θ第四个参数是阈值,这意味着最低投票,

import cv2 as cv import numpy as np  img = cv.imread(cv.samples.findFile(r'E:\image\test19.png')) gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY) edges = cv.Canny(gray, 50, 150, apertureSize=3) lines = cv.HoughLines(edges, 1, np.pi / 180, 100) 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))
    cv.line(img, (x1, y1), (x2, y2), (0, 0, 255), 2)
cv.imshow('houghlines.jpg', img)
cv.waitKey(0)

在这里插入图片描述

1.3 概率霍夫线变换

在霍夫线变换中即使对于带有两个参数的行,也需要大量计算,概率霍夫变换是我们看到的霍夫变换的优化,它没有考虑所有要点。取而代之的是,它仅采用随机的点子集,足以进行线检测,只是我们必须

OpenCV的实现基于Matas,J.和Galambos,C.和Kittler, J.V.使用渐进概率霍夫变换对行进行的稳健检测[145]。使用的函数是()。它有两个新的属性:1. - - 最小行长,小于此长度的线段将被拒绝;2. - - 线段之间允许将它们视为一条线的最大间隙

import cv2 as cv
import numpy as np

img = cv.imread(cv.samples.findFile(r'E:\image\test19.png'))
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
edges = cv.Canny(gray, 50, 150, apertureSize=3)
lines = cv.HoughLinesP(edges, 1, np.pi / 180, 100, minLineLength=100, maxLineGap=10)
for line in lines:
    x1, y1, x2, y2 = line[0]
    cv.line(img, (x1, y1), (x2, y2), (0, 255, 0), 2)
cv.imshow('houghlines.jpg', img)
cv.waitKey(0)

2. 霍夫圈变换

上面说完了霍夫线变换,现在挨到霍夫圈变换了,也就是说霍夫线变换面向的是图像中的线,而圈变换面向的就是圆咯

圆在数学上表示为(x−xcenter)^ 2+(y−ycenter)^2= r^2,其中(xcenter,ycenter)(xcenter,ycenter)是圆的中心,rr是圆的半径,从等式中,我们可以看到我们有3个参数,因此我们需要3D累加器进行霍夫变换,这将非常低效。因此,OpenCV使用更加技巧性的方法,即使用边缘的梯度信息的,我们在这里使用的函数是()

这个函数参数有点儿多,我们有必要说说,它的原型是cv2.HoughCircles(image, method, dp, minDist, circles, param1, param2, minRadius, maxRadius),参数为原图像(灰度图),参数是检测方法,参数为检测内侧圆心的累加器图像的分辨率于输入图像之比的倒数,如dp=1,累加器和输入图像具有相同的分辨率,如果dp=2,累计器便有输入图像一半那么大的宽度和高度,数表示两个圆之间圆心的最小距离

param1与param2有默认值100,它们是method设置的检测方法的对应的参数,对当前唯一的方法霍夫梯度法cv2.HOUGH_GRADIENT,表示传递给canny边缘检测算子的高阈值,而低阈值为高阈值的一半,表示在检测阶段圆心的累加器阈值,它越小,就越可以检测到更多根本不存在的圆,而它越大的话,能通过检测的圆就更加接近完美的圆形了

minRadius和maxRadius有默认值0,分别表示圆半径的最小值和最大值

import numpy as np
import cv2 as cv

img = cv.imread(r'E:\image\test20.png', 0)
img = cv.medianBlur(img, 5)
cimg = cv.cvtColor(img, cv.COLOR_GRAY2BGR)
circles = cv.HoughCircles(img, cv.HOUGH_GRADIENT, 1, 100, param1=100, param2=30, minRadius=100, maxRadius=200)
circles = np.uint16(np.around(circles))
for i in circles[0, :]:
    # 绘制外圆
    cv.circle(cimg, (i[0], i[1]), i[2], (0, 255, 0), 2)
    # 绘制圆心
    cv.circle(cimg, (i[0], i[1]), 2, (0, 0, 255), 3)
cv.imshow('detected circles', cimg)
cv.waitKey(0)

3. 图像分割与分水岭算法

3.1 分水岭算法

🚀:任何灰度图像都可以看作是一个,其中高强度表示山峰,低强度表示山谷,我们用不同颜色的**水(标签)**填充每个孤立的山谷(局部最小值)。随着水位的上升,根据附近的山峰(坡度),来自不同山谷的水明显会开始合并,颜色也不同。为了避免这种情况,我们要在水融合的地方建造屏障。继续填满水,建造障碍,直到所有的山峰都在水下。然后我们创建的屏障将返回你的分割结果

但是这种方法会由于图像中的噪声或其他不规则性而产生过度分割的结果。因此OpenCV实现了一个基于标记的分水岭算法,我们可以指定哪些是要合并的山谷点,哪些不是。这是一个交互式的图像分割。我们所做的是给我们知道的对象赋予不同的标签。用一种颜色(或强度)标记我们确定为前景或对象的区域,用另一种颜色标记我们确定为背景或非对象的区域,最后用0标记我们不确定的区域。这是我们的标记。然后应用分水岭算法。然后我们的标记将使用我们给出的标签进行更新,对象的边界值将为-1

3.2 图像分割的实现

有一张布满硬币的白纸,部分硬币之间相互接触,我们将这张图作为源图像,我们先从寻找硬币的近似估计开始,因此我们要使用(Otsu的二值化)

import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread(r'E:\image\test21.png')
gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
ret, thresh = cv.threshold(gray,0,255,cv.THRESH_BINARY_INV+cv.THRESH_OTSU)

然后由于分水岭算法对噪声非常敏感,所有我们要去除图像中的所有白点,为此我们可以使用形态学,如果要去除硬币对象中的小孔,我们可以使用形态学,在进行完这些操作后,我们可以十分确信靠近对象中心的区域是前景,离对象中心很远的就是背景,现在我们唯一不确定的就是硬币的

接下来我们需要提取我们可确认为硬币的区域(因为侵蚀会去除边界像素),如果硬币之间不接触那么我们之前的操作完全没问题,但是事实是他们接触了,因此我们更好的选择是找到距离变换并应用适当的阈值。此时我们需要确定的区域,形态学扩张可以满足我们的需求

剩下的区域是我们不知道的区域,无论是硬币还是背景。分水岭算法应该找到它。这些区域通常位于前景和背景相遇(甚至两个不同的硬币相遇)的硬币边界附近。可以通过从sure_bg区域中减去sure_fg区域来获得

# 噪声去除
kernel = np.ones((3,3),np.uint8)
opening = cv.morphologyEx(thresh,cv.MORPH_OPEN,kernel, iterations = 2)
# 确定背景区域
sure_bg = cv.dilate(opening,kernel,iterations=3)
# 寻找前景区域
dist_transform = cv.distanceTransform(opening,cv.DIST_L2,5)
ret, sure_fg = cv.threshold(dist_transform,0.7*dist_transform.max(),255,0)
# 找到未知区域
sure_fg = np.uint8(sure_fg)
unknown = cv.subtract(sure_bg,sure_fg)

✏️代码解析:第3行的cv.morphologyEx()函数是高级形态学转换函数,其功能取决于第二个参数的选取,具体内容请移步 OpenCV-Python——第13章:图像的形态学操作(腐蚀,膨胀,开运算,闭运算…)

第5行cv.dilate()函数即形态学膨胀功能函数

现在我们已经得到了硬币区域并且将它们分割了,在某些情况下,我们只对前景分割感兴趣,而对是否接触或分离接触并不感兴趣,所以这个时候我们不用使用距离变换,只需要侵蚀就可以满足我们的需求了(侵蚀是一种提取确定前景区域的重要方法)

现在我们可以创建标记了,使用()就很不错,它用0标记图像的背景,然后其他对象用从1开始的整数标记,标记完成后使用分水岭方法cv.watershed()完成图像分割

# 类别标记
ret, markers = cv.connectedComponents(sure_fg)
# 为所有的标记加1,保证背景是0而不是1
markers = markers+1
# 现在让所有的未知区域为0
markers[unknown==255] = 0
markers = cv.watershed(img,markers) 
img[markers == -1] = [255,0,0]

标记完成后使用分水岭方法cv.watershed()完成图像分割

plt.subplot(241), plt.imshow(cv2.cvtColor(src, cv2.COLOR_BGR2RGB)),
plt.title('Original'), plt.axis('off')
plt.subplot(242), plt.imshow(thresh, cmap='gray'),
plt.title('Threshold'), plt.axis('off')
plt.subplot(243), plt.imshow(sure_bg, cmap='gray'),
plt.title('Dilate'), plt.axis('off')
plt.subplot(244), plt.imshow(dist_transform, cmap='gray'),
plt.title('Dist Transform'), plt.axis('off')
plt.subplot(245), plt.imshow(sure_fg, cmap='gray'),
plt.title('Threshold'), plt.axis('off')
plt.subplot(246), plt.imshow(unknown, cmap='gray'),
plt.title('Unknow'), plt.axis('off')
plt.subplot(247), plt.imshow(np.abs(markers), cmap='jet'),
plt.title('Markers'), plt.axis('off')
plt.subplot(248), plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)),
plt.title('Result'), plt.axis('off')
plt.show()

代码资源参考自:OpenCV-Python——第22章:分水岭算法实现图像分割

4. 使用GrabCut算法实现交互式前景提取

4.1 GrabCut算法

🚀:GrabCut算法由英国微软研究院的Carsten Rother,Vladimir Kolmogorov和Andrew Blake设计,在他们的论文“GrabCut”中:使用迭代图割的交互式前景提取

🚀用户在前景区域周围绘制一个矩形(前景区域应完全位于矩形内部),然后迭代分割,以获得最佳结果。做完了但在某些情况下,分割可能不会很好,例如,可能已将某些前景区域标记为背景,反之亦然。在这种情况下,需要用户进行精修。只需在图像错误分割区域上画些笔画,笔画会对算法说: “嘿,该区域应该是前景,你将其标记为背景,在下一次迭代中对其进行校正”或与背景相反,然后在下一次迭代中,我们将获得更好的结果

🚀:用户输入矩形后,此矩形外部的所有内容都将作为背景(这是在矩形应包含所有对象之前提到的原因),而矩形内的所有内容都是未知的。任何指定前景和背景的用户输入都被视为,这意味着它们在此过程中不会更改。计算机根据我们提供的数据进行初始标记,它标记前景和背景像素(或对其进行硬标记)

  • 现在使用**高斯混合模型(GMM)**对前景和背景进行建模。 根据我们提供的数据,GMM可以学习并创建新的像素分布。也就是说,未知像素根据颜色统计上与其他硬标记像素的关系而被标记为可能的前景或可能的背景(就像聚类一样)
  • 根据此像素分布构建图形,图中的节点为像素。添加了另外两个节点,即“源”节点和“接收器”节点。每个前景像素都连接到源节点,每个背景像素都连接到接收器节点
  • 通过像素是前景/背景的概率来定义将像素连接到源节点/末端节点的边缘的权重。像素之间的权重由边缘信息或像素相似度定义。如果像素颜色差异很大,则它们之间的边缘将变低
  • 然后使用mincut算法对图进行分割。它将图切成具有最小成本函数的两个分离的源节点和宿节点。成本函数是被切割边缘的所有权重的总和。剪切后,连接到“源”节点的所有像素都变为前景,而连接到“接收器”节点的像素都变为背景
  • 继续该过程,直到分类收敛为止

4.2 使用OpenCV进行GrabCut算法

OpenCV提供了函数cv.grabCut(),这个函数的参数同样有些多,我们接着摊开聊聊,其中包括7个参数,第一个参数**- img -- mask -- rect -- iterCount -- model -**应该是cv.GC_INIT_WITH_RECT或cv.GC_INIT_WITH_MASK或两者结合,决定我们要绘制矩形还是最终的修饰笔触,废话不多说上实例

import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt

img = cv.imread(r'E:\image\test22.png')
mask = np.zeros(img.shape[:2], np.uint8)
bgdModel = np.zeros((1, 65), np.float64)
fgdModel = np.zeros((1, 65), np.float64)
rect = (50, 50, 450, 290)
cv.grabCut(img, mask, rect, bgdModel, fgdModel, 5, cv.GC_INIT_WITH_RECT)
mask2 = np.where((mask == 2) | (mask == 0), 0, 1).astype('uint8')
img = img * mask2[:, :, np.newaxis]
plt.imshow(img), plt.colorbar(), plt.show()

有时候我们分割手部骨骼,发现少了一根手指头,而且个别手指还没显示完全,我们这时就需要用画笔精修了,在paint应用程序中打开输入图像,并在图像中添加了另一层,使用画笔中的画笔工具,在新图层上用白色标记了错过的前景,而用白色标记了不需要的背景(例如logo,地面等),然后用灰色填充剩余的背景,然后将该mask图像加载到OpenCV中,编辑我们在新添加的mask图像中具有相应值的原始mask图像

# newmask是我手动标记过的mask图像
newmask = cv.imread('newmask.png',0)
# 标记为白色(确保前景)的地方,更改mask = 1
# 标记为黑色(确保背景)的地方,更改mask = 0
mask[newmask == 0] = 0
mask[newmask == 255] = 1
mask, bgdModel, fgdModel = cv.grabCut(img,mask,None,bgdModel,fgdModel,5,cv.GC_INIT_WITH_MASK)
mask = np.where((mask==2)|(mask==0),0,1).astype('uint8')
img = img*mask[:,:,np.newaxis]
plt.imshow(img),plt.colorbar(),plt.show()

(注:文章内容参考OpenCV4.1中文官方文档) 如果文章对您有所帮助,记得一键三连支持一下哦👍+⭐️+📝

标签: 180度两芯卡罩式过孔连接器

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台