2016-11-10 14:05:40 lhanchao 阅读数 13991

最近在做一个东西,需要使用字符的分割,如下图所示

可以看到这里的每个字符互相之间是没有粘连的,那么如何把他们分割开来呢?一个很简单的想法就是通过连通域进行分割,每个字符是一个连通域,不同字符之间是不同的连通域,因为这里没有粘连,所以处理起来是非常方便的。

那么重点就来了,如何实现连通域的分割呢?

原来的打算是通过扫描图像,通过寻找不同字符的边缘定位不同的字符,但是我发现对于“O”这种字符,由于他有外边缘和内边缘之分,在实现的过程中还要不断的判断,非常麻烦,纠结了很久最后没有想出简单的方法规避这个问题,最终也没有动手去实现。

这里介绍一种简单易于实现的方法实现连通域分割,对于我这里的这幅图像是二值图像,我可以通过找到一个未访问过的前景像素点以后,就不断的遍历与之相邻的且未经遍历的前景像素点,直到无法找到这样的像素点,则一个连通域遍历结束。

下面的算法的流程:

1、扫描图像,直到当前像素为前景像素且未被访问过,将该像素点入栈CD。

(1)出栈,把当前像素中的邻域的未被访问的前景像素点压入栈;

(2)重复(1)中操作,直到栈空,至此找到一个连通域;

2、重复1中的操作,直到遍历完所有图像。

这种方法对图像就进行了一次遍历就可以找到所有的连通域,速度快且原理简单易于实现。下面给出实现的代码:

void getConnectedDomain(Mat& src, vector<Rect>& boundingbox)//boundingbox为最终结果,存放各个连通域的包围盒
{
	int img_row = src.rows;
	int img_col = src.cols;
	Mat flag = Mat::zeros(Size(img_col, img_row), CV_8UC1);//标志矩阵,为0则当前像素点未访问过
	for (int i = 0; i < img_row; i++)
	{
		for (int j = 0; j < img_col; j++)
		{
			if (src.ptr<uchar>(i)[j] == 0 && flag.ptr<uchar>(i)[j] == 0)
			{
				stack<Point2f> cd;
				cd.push(Point2f(j, i));
				flag.ptr<uchar>(i)[j] = 1;
				int minRow = i, minCol = j;//包围盒左、上边界
				int maxRow = i, maxCol = j;//包围盒右、下边界
				while (!cd.empty())
				{
					Point2f tmp = cd.top();
					if (minRow > tmp.y)//更新包围盒
						minRow = tmp.y;
					if (minCol > tmp.x)
						minCol = tmp.x;
					if (maxRow < tmp.y)
						maxRow = tmp.y;
					if (maxCol < tmp.x)
						maxCol = tmp.x;
					cd.pop();
					Point2f p[4];//邻域像素点,这里用的四邻域
					p[0] = Point2f(tmp.x - 1 > 0 ? tmp.x - 1 : 0, tmp.y);
					p[1] = Point2f(tmp.x + 1 < img_col - 1 ? tmp.x + 1 : img_row - 1, tmp.y);
					p[2] = Point2f(tmp.x, tmp.y - 1 > 0 ? tmp.y - 1 : 0);
					p[3] = Point2f(tmp.x, tmp.y + 1 < img_row - 1 ? tmp.y + 1 : img_row - 1);
					for (int m = 0; m < 4; m++)
					{
						int x = p[m].y;
						int y = p[m].x;
						if (src.ptr<uchar>(x)[y] == 0 && flag.ptr<uchar>(x)[y] == 0)//如果未访问,则入栈,并标记访问过该点
						{
							cd.push(p[m]);
							flag.ptr<uchar>(x)[y] = 1;
						}
					}
				}
				Rect rect(Point2f(minCol, minRow), Point2f(maxCol + 1, maxRow + 1));
				boundingbox.push_back(rect);
			}
		}
	}
}
最终结果如下图所示:



2020-02-14 21:59:53 haoxun05 阅读数 1733

这篇文章主要给大家介绍了关于python验证码识别教程之利用投影法、连通域法分割图片的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起看看吧
前言

今天这篇文章主要记录一下如何切分验证码,用到的主要库就是Pillow和Linux下的图像处理工具GIMP。首先假设一个固定位置和宽度、无粘连、无干扰的例子学习一下如何使用Pillow来切割图片。
使用GIMP打开图片后,按 加号 放大图片,然后点击View->Show Grid来显示网格线:在这里插入图片描述
其中,每个正方形边长为10像素,所以数字1切割坐标为左20、上20、右40、下70。以此类推可以知道剩下3个数字的切割位置。

代码如下:

from PIL import Image
p = Image.open("1.png")
# 注意位置顺序为左、上、右、下
cuts = [(20,20,40,70),(60,20,90,70),(100,10,130,60),(140,20,170,50)]
for i,n in enumerate(cuts,1):
 temp = p.crop(n) # 调用crop函数进行切割
 temp.save("cut%s.png" % i)

切割后得到4张图片:
在这里插入图片描述
那么,如果字符位置不固定怎么办呢?现在假设一种随机位置宽度、无粘连、无干扰线的情况。
第一种方法,也是最简单的方法叫做”投影法”。原理就是将二值化后的图片在竖直方向进行投影,根据投影后的极值来判断分割边界。这里我依然使用上面的验证码图片来进行演示:

def vertical(img):
 """传入二值化后的图片进行垂直投影"""
 pixdata = img.load()
 w,h = img.size
 ver_list = []
 # 开始投影
 for x in range(w):
 black = 0
 for y in range(h):
  if pixdata[x,y] == 0:
  black += 1
 ver_list.append(black)
 # 判断边界
 l,r = 0,0
 flag = False
 cuts = []
 for i,count in enumerate(ver_list):
 # 阈值这里为0
 if flag is False and count > 0:
  l = i
  flag = True
 if flag and count == 0:
  r = i-1
  flag = False
  cuts.append((l,r))
 return cuts
 
p = Image.open('1.png')
b_img = binarizing(p,200)
v = vertical(b_img)

通过vertical函数我们就得到了一个包含所有黑色像素在X轴上投影后左右边界的位置。由于验证码没有任何干扰,所以我的阈值设定为0。
输出如下:
[(21, 37), (62, 89), (100, 122), (146, 164)]
可以看到,投影法给出左右边界和我们手工查看得到很接近。对于上下边界,偷懒的可以直接使用0和图片的高度,也可以在水平方向进行投影,这里有兴趣的小伙伴可以自己尝试。
但是,对于字符间有粘连的情况,投影法就会出现拆分错误,比如上篇文章中的:在这里插入图片描述
修改阈值为5后,投影法给出的左右边界是:
[(5, 27), (33, 53), (59, 108)]
明显最后的6和9数字没有切割。
修改阈值为7,结果则是:

[(5, 27), (33, 53), (60, 79), (83, 108)]

所以对于简单粘连的情况,调整阈值也是可以解决的。
第二种方法,叫做CFS连通域分割法。原理就是假定每个字符都由一个单独的连通域组成,换言之就是无粘连,找到一个黑色像素并开始判断,直到所有相连的黑色像素都被遍历标记过后即可判断出这个字符的分割位置。算法如下:
将二值化后的图片进行从左到右、从上到下的遍历,如果遇到黑色像素并且这个像素没有没访问过,就将这个像素入栈并标记为已经访问。
如果栈不为空,则继续探测周围8个像素,并执行第2步;如果栈空,则代表探测完了一个字符块。
探测结束,这样就确定了若干字符。
代码如下:

import queue
 
def cfs(img):
 """传入二值化后的图片进行连通域分割"""
 pixdata = img.load()
 w,h = img.size
 visited = set()
 q = queue.Queue()
 offset = [(-1,-1),(0,-1),(1,-1),(-1,0),(1,0),(-1,1),(0,1),(1,1)]
 cuts = []
 for x in range(w):
  for y in range(h):
   x_axis = []
   #y_axis = []
   if pixdata[x,y] == 0 and (x,y) not in visited:
    q.put((x,y))
    visited.add((x,y))
   while not q.empty():
    x_p,y_p = q.get()
    for x_offset,y_offset in offset:
     x_c,y_c = x_p+x_offset,y_p+y_offset
     if (x_c,y_c) in visited:
      continue
     visited.add((x_c,y_c))
     try:
      if pixdata[x_c,y_c] == 0:
       q.put((x_c,y_c))
       x_axis.append(x_c)
       #y_axis.append(y_c)
     except:
      pass
   if x_axis:
    min_x,max_x = min(x_axis),max(x_axis)
    if max_x - min_x > 3:
     # 宽度小于3的认为是噪点,根据需要修改
     cuts.append((min_x,max_x))
 return cuts

调用后输出结果和使用投影法是一样的。另外我看网上还有一种叫做“泛洪填充(Flood Fill)”的方法,似乎和连通域是一样的。

推荐我们的python学习基地,看前辈们是如何学习的!从基础的python脚本、爬虫、django、数据挖掘等编程已经,还有整理零基础到项目实战的资料,送给每一位爱学习python的小伙伴!每天都有老前辈定时讲解Python技术,分享一些学习的方法和需要留意的小细节,点击加入我们的 python学习者聚集地
总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值

2017-08-24 10:49:17 muye_CSDN 阅读数 190

对医学图片的去噪处理的总结。

  • 1。基本上对于图片的处理,即处理图片的在图片的像数上的值大小,也即是转化成灰度图的值的大小,灰度值0-255之间,可以通过对灰度值的改变,以修改图片。
  • 2。基于连通域的算法,连通域里面有一个很重要的概念就是阈值,阈值的选择非常关键,主要是本项目每个图片里面的目标灰度值与噪声灰度值差别不大,并且有些地方带粘连,连通域求解之后会站在一起,无法获取目标的准确连通域,另外一个地方是每个图片的目标灰度值差别稍微有点大,所有针对不同的图片,需要不同的阈值才能获取较好的效果。
  • 3。求解了连通域了之后,因为存在2中所说的问题,有一定的算法可以降低误差,可以使用腐蚀膨胀,以及形态性操作,但是迭代次数高之后,效果任然不是很理想,只是提高了一定的实验准确度。
  • 4。确定那个连通域代表的是目标图像,因为连通域确定的是二值化的像数位置图,需要对原图进行裁剪,获取目标,有可能存在最大的连通域表征并不是目标,因此必须找出那个连通域才是目标的表征。
  • 5。根据连通域确定目标并裁剪,有两种方式获取一个去噪的目标图像,一种是直接根据连通域的表征图,取最小长方形,以长方形的四个点坐标直接裁剪原图像,这种取法求得的图形对原图像的变化最小,并没有增加额外的噪声,但是可能存在并没有把噪声排除,即有可能噪声在裁剪的长方形里面,另外的是根据0、1的连通域。直接画在一张灰度全是255的跟原图一样大像数的图上,两张图形求&,这种方案,虽然结果最精确,但是增加了连通域的边界锯齿影响,而且非常严重,经过试验,在取得连通域之后或者取连通域之前,经过高斯滤波或者双边滤波能较好的解决,但是这样会增加连通域对目标的影响,即阈值的选择非常关键,如果每张图片的目标灰度值差别有点大,不变的阈值结果会很差。

仅作参考,不足之处,多多指教。

2018-07-23 10:49:52 yangzm 阅读数 5401

python验证码识别2:投影法、连通域法分割图片

9月 20, 2017 发布在 Python

今天这篇文章主要记录一下如何切分验证码,用到的主要库就是Pillow和Linux下的图像处理工具GIMP。首先假设一个固定位置和宽度、无粘连、无干扰的例子学习一下如何使用Pillow来切割图片。

使用GIMP打开图片后,按 加号 放大图片,然后点击View->Show Grid来显示网格线:


8.png

其中,每个正方形边长为10像素,所以数字1切割坐标为左20、上20、右40、下70。以此类推可以知道剩下3个数字的切割位置。代码如下:

1
2
3
4
5
6
7
from PIL import Image
p = Image.open("1.png")
# 注意位置顺序为左、上、右、下
cuts = [(20,20,40,70),(60,20,90,70),(100,10,130,60),(140,20,170,50)]
for i,n in enumerate(cuts,1):
    temp = p.crop(n) # 调用crop函数进行切割
    temp.save("cut%s.png" % i)

切割后得到4张图片:


9.png

那么,如果字符位置不固定怎么办呢?现在假设一种随机位置宽度、无粘连、无干扰线的情况。

第一种方法,也是最简单的方法叫做”投影法”。原理就是将二值化后的图片在竖直方向进行投影,根据投影后的极值来判断分割边界。这里我依然使用上面的验证码图片来进行演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
def vertical(img):
    """传入二值化后的图片进行垂直投影"""
    pixdata = img.load()
    w,h = img.size
    ver_list = []
    # 开始投影
    for x in range(w):
        black = 0
        for y in range(h):
            if pixdata[x,y] == 0:
                black += 1
        ver_list.append(black)
    # 判断边界
    l,r = 0,0
    flag = False
    cuts = []
    for i,count in enumerate(ver_list):
        # 阈值这里为0
        if flag is False and count > 0:
            l = i
            flag = True
        if flag and count == 0:
            r = i-1
            flag = False
            cuts.append((l,r))
    return cuts

p = Image.open('1.png')
b_img = binarizing(p,200)
v = vertical(b_img)

通过vertical函数我们就得到了一个包含所有黑色像素在X轴上投影后左右边界的位置。由于验证码没有任何干扰,所以我的阈值设定为0。 关于binarizing函数可以参考上一篇文章

输出如下:

1
[(21, 37), (62, 89), (100, 122), (146, 164)]

可以看到,投影法给出左右边界和我们手工查看得到很接近。对于上下边界,偷懒的可以直接使用0和图片的高度,也可以在水平方向进行投影,这里有兴趣的小伙伴可以自己尝试。

但是,对于字符间有粘连的情况,投影法就会出现拆分错误,比如上篇文章中的:

7.png

修改阈值为5后,投影法给出的左右边界是:

1
[(5, 27), (33, 53), (59, 108)]

 

明显最后的6和9数字没有切割。

修改阈值为7,结果则是:

1
[(5, 27), (33, 53), (60, 79), (83, 108)]

 

所以对于简单粘连的情况,调整阈值也是可以解决的。

第二种方法,叫做CFS连通域分割法。原理就是假定每个字符都由一个单独的连通域组成,换言之就是无粘连,找到一个黑色像素并开始判断,直到所有相连的黑色像素都被遍历标记过后即可判断出这个字符的分割位置。算法如下:

  1. 将二值化后的图片进行从左到右、从上到下的遍历,如果遇到黑色像素并且这个像素没有没访问过,就将这个像素入栈并标记为已经访问。
  2. 如果栈不为空,则继续探测周围8个像素,并执行第2步;如果栈空,则代表探测完了一个字符块。
  3. 探测结束,这样就确定了若干字符。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import queue

def cfs(img):
    """传入二值化后的图片进行连通域分割"""
    pixdata = img.load()
    w,h = img.size
    visited = set()
    q = queue.Queue()
    offset = [(-1,-1),(0,-1),(1,-1),(-1,0),(1,0),(-1,1),(0,1),(1,1)]
    cuts = []
    for x in range(w):
        for y in range(h):
            x_axis = []
            #y_axis = []
            if pixdata[x,y] == 0 and (x,y) not in visited:
                q.put((x,y))
                visited.add((x,y))
            while not q.empty():
                x_p,y_p = q.get()
                for x_offset,y_offset in offset:
                    x_c,y_c = x_p+x_offset,y_p+y_offset
                    if (x_c,y_c) in visited:
                        continue
                    visited.add((x_c,y_c))
                    try:
                        if pixdata[x_c,y_c] == 0:
                            q.put((x_c,y_c))
                            x_axis.append(x_c)
                            #y_axis.append(y_c)
                    except:
                        pass
            if x_axis:
                min_x,max_x = min(x_axis),max(x_axis)
                if max_x - min_x >  3:
                    # 宽度小于3的认为是噪点,根据需要修改
                    cuts.append((min_x,max_x))
    return cuts

 

调用后输出结果和使用投影法是一样的。另外我看网上还有一种叫做“泛洪填充(Flood Fill)”的方法,似乎和连通域是一样的。

2013-12-15 21:04:36 Arthurlr 阅读数 1057
问题是这样的,有一幅经过二值化处理之后的图像,我们希望统计其中细胞的个数,和不同粘连情况的细胞个数,比如,下图中有1个细胞组成连通区域的,也有2个细胞组成连通区域的,也有更多个细胞组成连通区域的,我们希望分别统计不同的情况。
我想出的一种可行的方法是这样的:
  1. 通过图像形态学的处理erode,将一些邻接的细胞分离开来,并减少单个像素的噪声干扰
  2. 计算其中的连通域
  3. 计算每一个连通域的面积
  4. 根据面积计算其中的聚类,其中聚类算法采用kmeans,其中k,由用户设定
  5. 根据聚类的情况计算其中细胞数

里面的采用聚类的思想是根据不同类型的重叠细胞,其中的面积应该有相应的分布,比如,两个重叠的细胞面积往往会显著的小于三个重叠的细胞(但是肯定会有例外),我们基于以上的想法通过计算聚类的方法计算其中的细胞数。
import cv2
from numpy import *
from scipy.cluster.vq import *

img=cv2.imread('FigProb9.27.jpg',0)
kernel=cv2.getStructuringElement(cv2.MORPH_CROSS,(3,3))
img=cv2.erode(img,kernel,iterations=5)
contour,h=cv2.findContours(img,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_NONE)
cv2.drawContours(img,contour,-1,(255,0,0),1)
cv2.imshow('img',img)

ContourArea=[]
for cnt in contour:
    Area=cv2.contourArea(cnt)
    ContourArea.append(Area)
ContourArea=array(ContourArea)
ContourArea=ContourArea[ContourArea>20]
#print len(ContourArea)
# print ContourArea
# print min(ContourArea)
# print max(ContourArea)
# print average(ContourArea)
#print sort(ContourArea)
centroid,dis=kmeans(ContourArea,5)#calculate 5 cluster
label,dis=vq(ContourArea,sort(centroid))#calculate label in 5 cluster
clusterNum=[]#the amount cluster
classNum=[]#the total amount cell in this kind of cluster
for i in range(len(label)):
    clusterNum.append(len(label[label==i]))
    classNum.append(clusterNum[i]*(i+1))
     
print ("In picture,we can see Total amount of cell is %d.\n ")%(sum(classNum))
print("%d in one;%d in two;%d in three;%d in four;%d in five.")%(classNum[0],classNum[1],classNum[2],classNum[3],classNum[4])
cv2.waitKey()



手写汉字分割1

阅读数 644

没有更多推荐了,返回首页