图像细化,骨架提取
版权声明:本文为CSDN博主「晴堂」的原创文章
原文链接:https://blog.csdn.net/xukaiwen_2016/article/details/53135866
————————————————
所谓细化就是经过一层层的剥离,从原来的图中去掉一些点,但仍要保持原来的形状,直到得到图像的骨架。骨架,可以理解为物体的中轴,例如一个长方形的骨架是它的长方向上的中轴线;正方形的骨架是它的中心点;圆的骨架是它的圆心,直线的骨架是它自身,孤立点的骨架也是自身。得到了骨架,就相当于突出物体的主要结构和形状信息,去除了多余信息,根据这些信息可以实现图像上特征点的检测,如端点,交叉点和拐点。
下面先介绍经典的Zhang并行快速细化算法:
设p1点的八邻域为:
p9 p2 p3
p8 p1 p4
p7 p6 p5
(其中p1为白点也就是物体,如果以下四个条件同时满足,则删除p1,即令p1=0)
其中迭代分为两个子过程:
过程1 细化删除条件为:
(1)、2 <=N(p1) <= 6, N(x)为x的8邻域中黑点的数目
(2)、A(p1)=1,A(x)指的是将p2-p8之间按序前后分别成对值为0、1的个数(背景色:0)
(3)、p2*p4*p6=0
(4)、p4*p6*p8=0
如果同时满足以上四个条件则该点可以删除(赋值为0)。
过程2 细化删除条件为:
(1)、2 <=N(p1) <= 6, N(x)为x的8邻域中黑点的数目
(2)、A(p1)=1,A(x)指的是将p2-p8之间按序前后分别为0、1的对数(背景色:0)
(3)、p2*p4*p8=0
(4)、p2*p6*p8=0
如果同时满足以上四个条件则该点可以删除。这样反复迭代,直到获取细化图像为止。
过滤部分较为简单:
如果p2+p3+p8+p9>=1,则该点可以删除(赋值为0)。实现每两个白点之间不能紧靠在一起
检测部分比较复杂需要反复实验:
过程1 确定卷积邻域范围:
p25 p10 p11 p12 p13
p24 p9 p2 p3 p14
p23 p8 p1 p4 p15
p22 p7 p6 p5 p16
p21 p20 p19 p18 p17
(这里是使用5x5,实际上为了更好的检测需要至少6x6的卷积且为圆形)
过程2 统计卷积范围内白点个数:
如果白点个数较多,则说明p1为交叉点。
如果白点个数较少,则说明p1为端点。
过程3 对检测出的点进行合并:
如果两个点之间距离太*,取*均值。(下面代码没有实现该功能)
所有程序源代码:
1 #include <opencv2/opencv.hpp> 2 #include <opencv2/core/core.hpp> 3 #include <iostream> 4 #include <vector> 5 using namespace cv; 6 using namespace std; 7 8 /** 9 * @brief 对输入图像进行细化,骨骼化 10 * @param src为输入图像,用cvThreshold函数处理过的8位灰度图像格式,元素中只有0与1,1代表有元素,0代表为空白 11 * @param maxIterations限制迭代次数,如果不进行限制,默认为-1,代表不限制迭代次数,直到获得最终结果 12 * @return 为对src细化后的输出图像,格式与src格式相同,元素中只有0与1,1代表有元素,0代表为空白 13 */ 14 cv::Mat thinImage(const cv::Mat & src, const int maxIterations = -1) 15 { 16 assert(src.type() == CV_8UC1); 17 cv::Mat dst; 18 int width = src.cols; 19 int height = src.rows; 20 src.copyTo(dst); 21 int count = 0; //记录迭代次数 22 while (true) 23 { 24 count++; 25 if (maxIterations != -1 && count > maxIterations) //限制次数并且迭代次数到达 26 break; 27 std::vector<uchar *> mFlag; //用于标记需要删除的点 28 //对点标记 29 for (int i = 0; i < height; ++i) 30 { 31 uchar * p = dst.ptr<uchar>(i); 32 for (int j = 0; j < width; ++j) 33 { 34 //如果满足四个条件,进行标记 35 // p9 p2 p3 36 // p8 p1 p4 37 // p7 p6 p5 38 uchar p1 = p[j]; 39 if (p1 != 1) continue; 40 uchar p4 = (j == width - 1) ? 0 : *(p + j + 1); 41 uchar p8 = (j == 0) ? 0 : *(p + j - 1); 42 uchar p2 = (i == 0) ? 0 : *(p - dst.step + j); 43 uchar p3 = (i == 0 || j == width - 1) ? 0 : *(p - dst.step + j + 1); 44 uchar p9 = (i == 0 || j == 0) ? 0 : *(p - dst.step + j - 1); 45 uchar p6 = (i == height - 1) ? 0 : *(p + dst.step + j); 46 uchar p5 = (i == height - 1 || j == width - 1) ? 0 : *(p + dst.step + j + 1); 47 uchar p7 = (i == height - 1 || j == 0) ? 0 : *(p + dst.step + j - 1); 48 if ((p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9) >= 2 && (p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9) <= 6) 49 { 50 int ap = 0; 51 if (p2 == 0 && p3 == 1) ++ap; 52 if (p3 == 0 && p4 == 1) ++ap; 53 if (p4 == 0 && p5 == 1) ++ap; 54 if (p5 == 0 && p6 == 1) ++ap; 55 if (p6 == 0 && p7 == 1) ++ap; 56 if (p7 == 0 && p8 == 1) ++ap; 57 if (p8 == 0 && p9 == 1) ++ap; 58 if (p9 == 0 && p2 == 1) ++ap; 59 60 if (ap == 1 && p2 * p4 * p6 == 0 && p4 * p6 * p8 == 0) 61 { 62 //标记 63 mFlag.push_back(p + j); 64 } 65 } 66 } 67 } 68 69 //将标记的点删除 70 for (std::vector<uchar *>::iterator i = mFlag.begin(); i != mFlag.end(); ++i) 71 { 72 **i = 0; 73 } 74 75 //直到没有点满足,算法结束 76 if (mFlag.empty()) 77 { 78 break; 79 } 80 else 81 { 82 mFlag.clear();//将mFlag清空 83 } 84 85 //对点标记 86 for (int i = 0; i < height; ++i) 87 { 88 uchar * p = dst.ptr<uchar>(i); 89 for (int j = 0; j < width; ++j) 90 { 91 //如果满足四个条件,进行标记 92 // p9 p2 p3 93 // p8 p1 p4 94 // p7 p6 p5 95 uchar p1 = p[j]; 96 if (p1 != 1) continue; 97 uchar p4 = (j == width - 1) ? 0 : *(p + j + 1); 98 uchar p8 = (j == 0) ? 0 : *(p + j - 1); 99 uchar p2 = (i == 0) ? 0 : *(p - dst.step + j); 100 uchar p3 = (i == 0 || j == width - 1) ? 0 : *(p - dst.step + j + 1); 101 uchar p9 = (i == 0 || j == 0) ? 0 : *(p - dst.step + j - 1); 102 uchar p6 = (i == height - 1) ? 0 : *(p + dst.step + j); 103 uchar p5 = (i == height - 1 || j == width - 1) ? 0 : *(p + dst.step + j + 1); 104 uchar p7 = (i == height - 1 || j == 0) ? 0 : *(p + dst.step + j - 1); 105 106 if ((p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9) >= 2 && (p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9) <= 6) 107 { 108 int ap = 0; 109 if (p2 == 0 && p3 == 1) ++ap; 110 if (p3 == 0 && p4 == 1) ++ap; 111 if (p4 == 0 && p5 == 1) ++ap; 112 if (p5 == 0 && p6 == 1) ++ap; 113 if (p6 == 0 && p7 == 1) ++ap; 114 if (p7 == 0 && p8 == 1) ++ap; 115 if (p8 == 0 && p9 == 1) ++ap; 116 if (p9 == 0 && p2 == 1) ++ap; 117 118 if (ap == 1 && p2 * p4 * p8 == 0 && p2 * p6 * p8 == 0) 119 { 120 //标记 121 mFlag.push_back(p + j); 122 } 123 } 124 } 125 } 126 127 //将标记的点删除 128 for (std::vector<uchar *>::iterator i = mFlag.begin(); i != mFlag.end(); ++i) 129 { 130 **i = 0; 131 } 132 133 //直到没有点满足,算法结束 134 if (mFlag.empty()) 135 { 136 break; 137 } 138 else 139 { 140 mFlag.clear();//将mFlag清空 141 } 142 } 143 return dst; 144 } 145 146 /** 147 * @brief 对骨骼化图数据进行过滤,实现两个点之间至少隔一个空白像素 148 * @param thinSrc为输入的骨骼化图像,8位灰度图像格式,元素中只有0与1,1代表有元素,0代表为空白 149 */ 150 void filterOver(cv::Mat thinSrc) 151 { 152 assert(thinSrc.type() == CV_8UC1); 153 int width = thinSrc.cols; 154 int height = thinSrc.rows; 155 for (int i = 0; i < height; ++i) 156 { 157 uchar * p = thinSrc.ptr<uchar>(i); 158 for (int j = 0; j < width; ++j) 159 { 160 // 实现两个点之间至少隔一个像素 161 // p9 p2 p3 162 // p8 p1 p4 163 // p7 p6 p5 164 uchar p1 = p[j]; 165 if (p1 != 1) continue; 166 uchar p4 = (j == width - 1) ? 0 : *(p + j + 1); 167 uchar p8 = (j == 0) ? 0 : *(p + j - 1); 168 uchar p2 = (i == 0) ? 0 : *(p - thinSrc.step + j); 169 uchar p3 = (i == 0 || j == width - 1) ? 0 : *(p - thinSrc.step + j + 1); 170 uchar p9 = (i == 0 || j == 0) ? 0 : *(p - thinSrc.step + j - 1); 171 uchar p6 = (i == height - 1) ? 0 : *(p + thinSrc.step + j); 172 uchar p5 = (i == height - 1 || j == width - 1) ? 0 : *(p + thinSrc.step + j + 1); 173 uchar p7 = (i == height - 1 || j == 0) ? 0 : *(p + thinSrc.step + j - 1); 174 if (p2 + p3 + p8 + p9 >= 1) 175 { 176 p[j] = 0; 177 } 178 } 179 } 180 } 181 182 /** 183 * @brief 从过滤后的骨骼化图像中寻找端点和交叉点 184 * @param thinSrc为输入的过滤后骨骼化图像,8位灰度图像格式,元素中只有0与1,1代表有元素,0代表为空白 185 * @param raudis卷积半径,以当前像素点位圆心,在圆范围内判断点是否为端点或交叉点 186 * @param thresholdMax交叉点阈值,大于这个值为交叉点 187 * @param thresholdMin端点阈值,小于这个值为端点 188 * @return 为对src细化后的输出图像,格式与src格式相同,元素中只有0与1,1代表有元素,0代表为空白 189 */ 190 std::vector<cv::Point> getPoints(const cv::Mat &thinSrc, unsigned int raudis = 4, unsigned int thresholdMax = 6, unsigned int thresholdMin = 4) 191 { 192 assert(thinSrc.type() == CV_8UC1); 193 int width = thinSrc.cols; 194 int height = thinSrc.rows; 195 cv::Mat tmp; 196 thinSrc.copyTo(tmp); 197 std::vector<cv::Point> points; 198 for (int i = 0; i < height; ++i) 199 { 200 for (int j = 0; j < width; ++j) 201 { 202 if (*(tmp.data + tmp.step * i + j) == 0) 203 { 204 continue; 205 } 206 int count=0; 207 for (int k = i - raudis; k < i + raudis+1; k++) 208 { 209 for (int l = j - raudis; l < j + raudis+1; l++) 210 { 211 if (k < 0 || l < 0||k>height-1||l>width-1) 212 { 213 continue; 214 215 } 216 else if (*(tmp.data + tmp.step * k + l) == 1) 217 { 218 count++; 219 } 220 } 221 } 222 223 if (count > thresholdMax||count<thresholdMin) 224 { 225 Point point(j, i); 226 points.push_back(point); 227 } 228 } 229 } 230 return points; 231 } 232 233 234 int main(int argc, char*argv[]) 235 { 236 cv::Mat src; 237 //获取图像 238 if (argc != 2) 239 { 240 src = cv::imread("src.jpg", cv::IMREAD_GRAYSCALE); 241 } 242 else 243 { 244 src = cv::imread(argv[1], cv::IMREAD_GRAYSCALE); 245 } 246 if (src.empty()) 247 { 248 std::cout << "读取文件失败!" << std::endl; 249 return -1; 250 } 251 252 //将原图像转换为二值图像 253 cv::threshold(src, src, 128, 1, cv::THRESH_BINARY); 254 //图像细化,骨骼化 255 cv::Mat dst = thinImage(src); 256 //过滤细化后的图像 257 filterOver(dst); 258 //查找端点和交叉点 259 std::vector<cv::Point> points = getPoints(dst,6,9,6); 260 //二值图转化成灰度图,并绘制找到的点 261 dst = dst * 255; 262 src = src * 255; 263 vector<cv::Point>::iterator it = points.begin(); 264 for (;it != points.end(); it++) 265 { 266 circle(dst, *it,4,255, 1); 267 } 268 imwrite("dst.jpg", dst); 269 //显示图像 270 cv::namedWindow("src1", CV_WINDOW_AUTOSIZE); 271 cv::namedWindow("dst1", CV_WINDOW_AUTOSIZE); 272 cv::imshow("src1", src); 273 cv::imshow("dst1", dst); 274 cv::waitKey(0); 275 }
处理效果: