OpenCV中的图像处理

您所在的位置:网站首页 opencv霍夫圆检测原理 OpenCV中的图像处理

OpenCV中的图像处理

2023-04-13 21:28| 来源: 网络整理| 查看: 265

当前位置: 辣唇网 > 网站 > OpenCV中的图像处理 —— 霍夫线 / 圈变换 + 图像分割(分水岭算法) + 交互式前景提取(GrabCut算法) OpenCV中的图像处理 —— 霍夫线 / 圈变换 + 图像分割(分水岭算法) + 交互式前景提取(GrabCut算法) 作者:lachun 时间:23-04-12 阅读数:3人阅读 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的图像中间有一条水平线。取直线的第一点且我们知道它的坐标(xy)值。现在在线性方程式中将值θ= 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)值的数组ρ以像素为单位θ以弧度为单位。这个函数包括4个参数第一个即二进制原图因此在使用霍夫曼变换之前我们会先使用阈值或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]。使用的函数是cv.HoughLinesP()。它有两个新的属性1. - minLineLength - 最小行长小于此长度的线段将被拒绝2. - maxLineGap - 线段之间允许将它们视为一条线的最大间隙

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使用更加技巧性的方法即使用边缘的梯度信息的Hough梯度方法我们在这里使用的函数是cv.HoughCircles()

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

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

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()函数即形态学膨胀功能函数

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

现在我们可以创建标记了使用cv.connectedComponents()就很不错它用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 RotherVladimir Kolmogorov和Andrew Blake设计在他们的论文“GrabCut”中使用迭代图割的交互式前景提取

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

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

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

在这里插入图片描述

4.2 使用OpenCV进行GrabCut算法

OpenCV提供了函数cv.grabCut()这个函数的参数同样有些多我们接着摊开聊聊其中包括7个参数第一个参数**- img -即源图像第二个参数- mask -是掩码图像在其中我们会指定哪些区域为背景第三个参数- rect -是它的矩形坐标格式为x y w h其中包括前景对象第四第五个参数 - bdgModel, fgdModel - 是算法内部使用的数组我们只需要创建两个大小为1,65的np.float64类型零数组第六个参数- 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中文官方文档 如果文章对您有所帮助记得一键三连支持一下哦+⭐️+

上一篇:【迎战蓝桥】 算法·每日一题(今日详解)-- day6

下一篇:点云配准(三) 传统点云配准算法概述



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3