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