在我们进行图像处理的时候,有可能需要对图像进行细化,提取出图像的骨架信息,进行更加有效的分析。
图像细化(Image Thinning),一般指二值图像的骨架化(Image Skeletonization) 的一种操作运算。
所谓的细化就是经过一层层的剥离,从原来的图中去掉一些点,但仍要保持原来的形状,直到得到图像的骨架。骨架,可以理解为图象的中轴。
好的细化算法一定要满足:
- 收敛性;
- 保证细化后细线的连通性;
- 保持原图的基本形状;
- 减少笔画相交处的畸变;
- 细化结果是原图像的中心线;
- 细化的快速性和迭代次数少;
这里,我们对"Zhang并行快速细化算法"进行了实现(注意,该算法为并行算法,而我们在实现过程中并没有并行化处理,所以,效率并没有达到最好)。
1. #include <opencv2/opencv.hpp>
2. #include <iostream>
3. #include <vector>
4. #include <limits>
5. using namespace cv;
6. using namespace std;
7. /**
8. * @brief 对输入图像进行细化
9. * @param[in] src为输入图像,用cvThreshold函数处理过的8位灰度图像格式,元素中只有0与1,1代表有元素,0代表为空白
10. * @param[out] dst为对src细化后的输出图像,格式与src格式相同,调用前需要分配空间,元素中只有0与1,1代表有元素,0代表为空白
11. * @param[in] maxIterations限制迭代次数,如果不进行限制,默认为-1,代表不限制迭代次数,直到获得最终结果
12. */
13. void thinImage(IplImage* src,IplImage* dst,int maxIterations = -1 )
14. {
15. CvSize size = cvGetSize(src);
16. cvCopy(src,dst);//将src中的内容拷贝到dst中
17. int count = 0; //记录迭代次数
18. while (true)
19. {
20. count++;
21. if(maxIterations!=-1 && count > maxIterations) //限制次数并且迭代次数到达
22. break;
23. //std::cout << count << ' ';输出迭代次数
24. vector<pair<int,int> > mFlag; //用于标记需要删除的点
25. //对点标记
26. for (int i=0; i<size.height; ++i)
27. {
28. for (int j=0; j<size.width; ++j)
29. {
30. //如果满足四个条件,进行标记
31. // p9 p2 p3
32. // p8 p1 p4
33. // p7 p6 p5
34. int p1 = CV_IMAGE_ELEM(dst,uchar,i,j);
35. int p2 = (i==0)?0:CV_IMAGE_ELEM(dst,uchar,i-1,j);
36. int p3 = (i==0 || j==size.width-1)?0:CV_IMAGE_ELEM(dst,uchar,i-1,j+1);
37. int p4 = (j==size.width-1)?0:CV_IMAGE_ELEM(dst,uchar,i,j+1);
38. int p5 = (i==size.height-1 || j==size.width-1)?0:CV_IMAGE_ELEM(dst,uchar,i+1,j+1);
39. int p6 = (i==size.height-1)?0:CV_IMAGE_ELEM(dst,uchar,i+1,j);
40. int p7 = (i==size.height-1 || j==0)?0:CV_IMAGE_ELEM(dst,uchar,i+1,j-1);
41. int p8 = (j==0)?0:CV_IMAGE_ELEM(dst,uchar,i,j-1);
42. int p9 = (i==0 || j==0)?0:CV_IMAGE_ELEM(dst,uchar,i-1,j-1);
43.
44. if ((p2+p3+p4+p5+p6+p7+p8+p9)>=2 && (p2+p3+p4+p5+p6+p7+p8+p9)<=6)
45. {
46. int ap=0;
47. if (p2==0 && p3==1) ++ap;
48. if (p3==0 && p4==1) ++ap;
49. if (p4==0 && p5==1) ++ap;
50. if (p5==0 && p6==1) ++ap;
51. if (p6==0 && p7==1) ++ap;
52. if (p7==0 && p8==1) ++ap;
53. if (p8==0 && p9==1) ++ap;
54. if (p9==0 && p2==1) ++ap;
55.
56. if (ap==1)
57. {
58. if (p2*p4*p6==0)
59. {
60. if (p4*p6*p8==0)
61. {
62. //标记
63. mFlag.push_back(make_pair(i,j));
64. }
65. }
66. }
67. }
68. }
69. }
70.
71. //将标记的点删除
72. for (vector<pair<int,int> >::iterator i=mFlag.begin(); i!=mFlag.end(); ++i)
73. {
74. CV_IMAGE_ELEM(dst,uchar,i->first,i->second) = 0;
75. }
76.
77. //直到没有点满足,算法结束
78. if (mFlag.size()==0)
79. {
80. break;
81. }
82. else
83. {
84. mFlag.clear();//将mFlag清空
85. }
86.
87. //对点标记
88. for (int i=0; i<size.height; ++i)
89. {
90. for (int j=0; j<size.width; ++j)
91. {
92. //如果满足四个条件,进行标记
93. // p9 p2 p3
94. // p8 p1 p4
95. // p7 p6 p5
96. int p1 = CV_IMAGE_ELEM(dst,uchar,i,j);
97. if(p1!=1) continue;
98. int p2 = (i==0)?0:CV_IMAGE_ELEM(dst,uchar,i-1,j);
99. int p3 = (i==0 || j==size.width-1)?0:CV_IMAGE_ELEM(dst,uchar,i-1,j+1);
100. int p4 = (j==size.width-1)?0:CV_IMAGE_ELEM(dst,uchar,i,j+1);
101. int p5 = (i==size.height-1 || j==size.width-1)?0:CV_IMAGE_ELEM(dst,uchar,i+1,j+1);
102. int p6 = (i==size.height-1)?0:CV_IMAGE_ELEM(dst,uchar,i+1,j);
103. int p7 = (i==size.height-1 || j==0)?0:CV_IMAGE_ELEM(dst,uchar,i+1,j-1);
104. int p8 = (j==0)?0:CV_IMAGE_ELEM(dst,uchar,i,j-1);
105. int p9 = (i==0 || j==0)?0:CV_IMAGE_ELEM(dst,uchar,i-1,j-1);
106.
107. if ((p2+p3+p4+p5+p6+p7+p8+p9)>=2 && (p2+p3+p4+p5+p6+p7+p8+p9)<=6)
108. {
109. int ap=0;
110. if (p2==0 && p3==1) ++ap;
111. if (p3==0 && p4==1) ++ap;
112. if (p4==0 && p5==1) ++ap;
113. if (p5==0 && p6==1) ++ap;
114. if (p6==0 && p7==1) ++ap;
115. if (p7==0 && p8==1) ++ap;
116. if (p8==0 && p9==1) ++ap;
117. if (p9==0 && p2==1) ++ap;
118.
119. if (ap==1)
120. {
121. if (p2*p4*p8==0)
122. {
123. if (p2*p6*p8==0)
124. {
125. //标记
126. mFlag.push_back(make_pair(i,j));
127. }
128. }
129. }
130. }
131. }
132. }
133. //删除
134. for (vector<pair<int,int> >::iterator i=mFlag.begin(); i!=mFlag.end(); ++i)
135. {
136. CV_IMAGE_ELEM(dst,uchar,i->first,i->second) = 0;
137. }
138.
139. //直到没有点满足,算法结束
140. if (mFlag.size()==0)
141. {
142. break;
143. }
144. else
145. {
146. mFlag.clear();//将mFlag清空
147. }
148. }
149. }
150.
151. int main(int argc, char*argv[])
152. {
153. //获取图像
154. if (argc!=2)
155. {
156. cout << "参数个数错误!"<<endl;
157. return -1;
158. }
159. IplImage *pSrc = cvLoadImage(argv[1],CV_LOAD_IMAGE_GRAYSCALE);
160. if (!pSrc)
161. {
162. cout << "读取文件失败!" << endl;
163. return -1;
164. }
165. IplImage *pTemp = cvCreateImage(cvGetSize(pSrc),pSrc->depth,pSrc->nChannels);
166. IplImage *pDst = cvCreateImage(cvGetSize(pSrc),pSrc->depth,pSrc->nChannels);
167.
168. //将原图像转换为二值图像
169. cvThreshold(pSrc,pTemp,128,1,CV_THRESH_BINARY);
170. //图像细化
171. thinImage(pTemp,pDst);
172.
173. for (int i=0; i<pDst->height; ++i)
174. {
175. for (int j=0; j<pDst->width; ++j)
176. {
177. if(CV_IMAGE_ELEM(pDst,uchar,i,j)==1)
178. CV_IMAGE_ELEM(pDst,uchar,i,j)= 255;
179. }
180. }
181.
182. namedWindow("src",CV_WINDOW_AUTOSIZE);
183. namedWindow("dst",CV_WINDOW_AUTOSIZE);
184. cvShowImage("src",pSrc);
185. cvShowImage("dst",pDst);
186.
187. waitKey(0);
188. }
1原图像
2.运行效果