opencv传统分割算法总结(多边形拟合,水平投影,直线检测)

最近我发现,在很多特定问题上传统的分割方法挺方便的,比如分割打印字体文件,网站爬下来的表格图像,pdf中的特定格式文件等。在实战中,我总结了几点记录一下。主要采用opencv-python来应用这些算法。
大体来分,传统的分割算法可分为三类:
基于阈值的分割方法

基于区域的分割方法

基于边缘的分割方法以及基于特定理论的分割方法
从数学角度来看,图像分割是将数字图像划分成互不相交的区域的过程。图像分割的过程也是一个标记过程,即把属于同一区域的像索赋予相同的编号。

图像分割的目标是将图像中像素根据一定的规则分为若干个(N)个cluster集合,I每个集合包含一类像素。

根据算法分为监督学习算法和无监督学习算法,传统的图像分割算法多数都是无监督学习算法,基于深度学习的分割算法则多是有监督学习算法.

根据我在实际场景中的应用状况,分割出来的结果最好同时可以确定目标的位置,比如提取表格图像中的表格的时候,用霍夫直线检测比用多边形拟合矩形要好。因为直线检测可以定位每一条线从而定位到每一个表格中的格子,而多边形拟合想要达到同样的效果就需要设置很多阈值。(还有一点:由于直线检测对噪声很敏感,直接做多边形拟合很可能找不到,而直线检测就要灵活很多了。)

目前最常用的方法:1.预处理 2.直线检测 3.多边形拟合 4.水平投影
1.预处理方法:这步非常重要,对最终结果有极大影响。
由于色彩变化通常不稳定,一般采用转化成灰度图,然后基于灰度值变化去做分割。
1.1转化成灰度图有两种方法:

1.gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
2.im=cv2.imread("./677849385.jpg",0)#读图像的时候讲最后一个参数置0


1.2灰度图之后用canny算子做边缘检测
这里如果噪点较多,可以先用各种去噪的算法,比如高斯滤波(cv2.GaussianBlur(gray, (3, 3),0)),但有一个缺点,会将图像变模糊,不利于边缘检测。依情况选择使用。
同样为了减少噪声,还有一种选择是采用阈值过滤(cv2.threshold(gray,245,255,cv2.THRESH_BINARY)),这个函数很容易让人误解为高于阈值的就设为0,低于阈值的就设为255这样的二值化,其实做的比这复杂(参考下面的表)。参考:https://cloud.tencent.com/developer/article/1348925 所以如果是仅仅想达据阈值分开是需要自己写的,遍历图像即可.

回到canny算子,Canny(输入的灰度图, 最小阈值, 最大阈值),直觉上看这个函数就是把边缘提取出来,为后面的步骤做准备。原理上看就是寻找局部区域的极大值,因为这个原因找到的边缘可能是不连续的,一段一段的小线段,所以下一步可以采用腐蚀和膨胀的方法来链接边缘。

1.3腐蚀和膨胀

腐蚀和膨胀也是预处理比较重要的方法,二者本质上都是用一个卷积核在图像上滑动做卷积操作。直观上看膨胀是把边缘变粗。

  kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(3, 3))
  dilation = cv2.dilate(canny,kernel,iterations = 1)#膨胀一下,来连接边缘

我通常用这个办法来连接canny之后不连续的线段,但要根据情况而定,因为连接的同时可能会把不是同一条线段的地方也连在一起,导致不正确的粘连。这时候就需要用一下腐蚀操作。

kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(3, 3))
eroded = cv2.erode(RedThresh,kernel)        #腐蚀图像

2.接下来就是分割的方法,分割的方法有很多,我这里记录三种常用的,容易使用的方法。

2.1 多边形拟合

这种方法的意思是,在处理好的边缘检测之后的图像上找对应的图形,可以是长方形,圆形等各种图形。然后用找到的图形边界来筛选出的自己需要的大致区域。由于对噪声太敏感了,一般只用于确定大致位置,粗略提取,把更加细致的活交给直线检测。

有一个例子是身份证位置识别可以参考一下:https://blog.csdn.net/liu506039293/article/details/90046361

2.2直线拟合

上面的多边形拟合的方法有个问题:对噪声比较敏感。比如说有一个四边形,如果有一条边上出现了缺口,导致整个图形没有闭合,那么这个四边形很有可能识别不出来。这个时候去拟合多边形还不如直接拟合直线更有鲁棒性。有一个重要的技巧就是拟合横线的时侯把y坐标设置成最大值,拟合竖线的时侯把x坐标设置成最大值。opencv有两个函数完成这个事情,cv2.HoughLinescv2.HoughLinesP,我建议用后一个,前一个涉及到角度比较麻烦。

给出后一个的参数详解:

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

        这个方法中前面四个参数跟cv2.HoughLines方法中的用法相同,详细记录一下minLineLength和maxLineGap。

        *  lines返回值是以(x1,y1,x2,y2)4个元素的向量为元素的列表。(x1,y1)和(x2,y2)表示一条线段的起点和终点。

        *  minLineLength指最小的线段长度,小于该参数的直线被舍弃掉,认为不合格。

        *  maxLineGap指同一条线上的最大间断值。

 

调用范例:

                lines_0 = cv2.HoughLinesP(canny, 1, np.pi / 180, 5, 2, 3)
                for line in lines_0:
                    x1, y1, x2, y2 = line[0]
                    p1 = (x1, 0)
                    p2 = (x2, box_m.shape[0])
                    p3 = (0, y1)
                    p4 = (box_m.shape[1], y2)
                    if abs(int((x2 - x1))) < 3:#这样是检测竖线
                        linesum_1.append((p1, p2))
                       
                    if abs(int(y1 - y2)) < 3 and abs(int((x2 - x1))) > 50:#这样检测横线
                        linesum_0.append((p3, p4))

补充一份代码:将检测出来的直线用不同颜色画出来

                for i in range(len(point)-1):
                    cv2.line(im, tuple(point[i][0]), tuple(point[i+1][0]), (0,255,0), 1)
                cv2.line(im, tuple(point[0][0]), tuple(point[3][0]), (0,255,0), 1)
                cv2.imshow("a",im)

2.3水平分割(重点)

这个方法是统计的方法,我感觉是最好用的方法。方法为统计每一个横或者纵坐标上的灰色像素个数,得到统计像素图,根据统计值来寻找分割的特征。这种方法在很多复杂场景下效果很不错。但是统计的过程中如果图像像素质量较高,遍历图像统计灰色像素的时候就会很慢,我一般会采用gpu来加速处理,一般都能满足实时性要求。具体可以参看:https://mp.csdn.net/postedit/100826217

举个例子:

比如下面有图,我想分开图像和文字,但是图像中没有任何表格骨架来让我提取。这时候,我们统计一下灰度值,通过图像的灰度值一定比文字的灰度值要大,就可以很好的将二者区分开来了。(右图中绿色的为分割线。)

示例代码:

统计灰度直方图部分的代码(gpu加速之后):

from numba import jit
@jit
def shuiping_gpu_1(thresh):
    (h,w)=thresh.shape #返回高和宽
    a = [0 for z in range(0, h)] 

    for j in range(0,h):  
        for i in range(0,w):  
            if  thresh[j,i]==0: 
                a[j]+=1 
                thresh[j,i]=255
    return thresh,a
@jit
def shuzhi_gpu_1(thresh):
    (h,w)=thresh.shape #返回高和宽
    a = [0 for z in range(0,w)] 
    for i in range(0,w):  
        for j in range(0,h):  
            if  thresh[j,i]==0: 
                a[i]+=1 
                thresh[j,i]=255
    return thresh,a

调用:

(h,w)=img.shape
img = np.asarray(img)
img_shuiping,a=shuiping_gpu_1(img)
for j  in range(1,h-2):
    for i in range(0,a[j]):   
            img_shuiping[j,i]=0
cv2.imshow("o",img_shuiping)
cv2.waitKey(0)

总结:

分割问题是图像处理的基础问题,目标检测,图像语义识别算法均需要在分割的基础上做。

传统方法与深度学习的方法有着千丝万缕的关系,比如传统方法中想尽办法进行边缘提取,统计直方图特征,用ORB,SURF找特征点,本质上跟深度学习用cnns提取特征是一样的目的。

具体应用来说基于深度学习的分割方法更适用于分割应用场景复杂(光照复杂,分割物体本身需要较强的泛化能力,场景杂乱)的情况,这是优点。而缺点也很明显

1.需要标注数据,操作复杂

2.最终结果有不确定性(最好不要对神经网络的泛化能力过高期待)

传统方法正好截然相反,灵活性很欠缺,但只要用对了地方,相比深度学习有成本低,结果可预见,可以精细化调整等极大地优点。

  • 4
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
OpenCV(Open Source Computer Vision Library)是一个开源的计算机视觉库,它提供了许多图像处理和计算机视觉算法。图像分割是计算机视觉中的一个重要任务,它可以将图像分成多个区域或对象。OpenCV提供了许多图像分割算法,包括阈值分割、区域生长、聚类、水平集方法等。 以下是几个常见的OpenCV图像分割算法的介绍: 1. 阈值分割(Thresholding):阈值分割是一种简单的图像分割方法,它通过将图像中的像素值与一个阈值进行比较,将像素分为不同的类别。OpenCV提供了多种阈值分割算法,如全局阈值和自适应阈值等。 2. 区域生长(Region Growing):区域生长是一种基于像素的图像分割方法,它通过将具有相似属性的像素组合成一个区域,并将其他像素标记为背景。OpenCV提供了区域生长算法的实现,可以根据不同的应用场景选择不同的生长算法。 3. 聚类(Clustering):聚类是一种无监督的图像分割方法,它通过将相似的像素组合成群集,并将其他像素标记为背景。OpenCV提供了多种聚类算法,如K-means、DBSCAN等。 4. 水平集方法(Level Set Method):水平集方法是近年来发展起来的一种先进的图像分割方法,它通过将图像中的边界或轮廓进行跟踪和演化,将图像分割成不同的区域。OpenCV提供了水平集方法的实现,可以根据不同的应用场景选择不同的水平算法。 在使用OpenCV进行图像分割时,通常需要先对图像进行预处理,如滤波、去噪、缩放等,然后再选择合适的算法进行分割OpenCV还提供了许多工具和函数,用于处理图像数据和执行各种计算机视觉任务。使用OpenCV进行图像分割可以大大提高效率和准确性,适用于各种计算机视觉应用场景。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值