赞
踩
具体的算法原理,参考的是文章A Fast Parallel Algorithm for Thinning Digital Patterns,这也是opencv中骨架提取算法的实现方式。
下面开始造轮子。
可以看到上面的为图像细化的过程,其中前两幅图像为两次子迭代过程, 第三幅图像是最终结果。
一定的规则
去除图像中左边和下边的多余的点一定的规则
去除图像中右边和上边的多余的点规则
文章中的规则为数学表示,而且比较绕,我同等变换为以下三个条件,如果一个像素需要被删除,那么必须满足以下全部条件:
1 | 1 | 1 |
---|---|---|
0 | P | 1 |
0 | 0 | 0 |
0 | 0 | 0 |
---|---|---|
1 | P | 0 |
1 | 1 | 1 |
1 | 2 | 3 |
---|---|---|
8 | P | 4 |
7 | 6 | 5 |
现在要设计一个数据结构来表示一个点位是否满足被删除。
很简单可以想到一个字节的长度正好是8个bit,可以完美表示像素P的八个邻居的状态(非零为1或者为零为0),按照bit最高位对应像素序号为1的邻居,最低位对应序号为8的邻居。
上面提到的条件1和2表示起来比较方便,重点是第3个条件的表示。
邻居像素序号 | 邻居像素为零的条件 | 符号表示 |
---|---|---|
2 | !(surround & mask_north) | A |
4 | !(surround & mask_east) | B |
6 | !(surround & mask_south) | C |
8 | !(surround & mask_west) | D |
综上所述,条件3可表示为:(B || C) || (A && D)。
我们使用他的逆否条件(方便编写程序)就可以表示为:!(!B && !C) && !(!A || !D)。
/** * this struct help to describe the pixel attributes * @param surround is a unsigned char which has eight bits and are corresponding to a pixel's eight neighbours * @param pattern_cnt is the number of pattern where the adjacent two neighbours are 0 and none-zero in clock-order * @param neighbour_cnt is the number of none-zero neighbour * @param delete_state whether this pixel has been deleted * @param iter_idx if this pixel has been deleted, the variable represent in which iteration this happened */ typedef struct _Descriptor_ { uchar surround = 0; int pattern_cnt = 0; int neighbour_cnt = 0; bool delete_state = false; int iter_idx = -1; }Descriptor;
白色为原图,灰色部分为提取出来的骨架。
函数实现
// Where you can find this article: A Fast Parallel Algorithm for Thinning Digital Patterns // <https://dl.acm.org/doi/10.1145/357994.358023>. /** * this struct help to describe the pixel attributes * @param surround is a unsigned char which has eight bits and are corresponding to a pixel's eight neighbours * @param pattern_cnt is the number of pattern where the adjacent two neighbours are 0 and none-zero in clock-order * @param neighbour_cnt is the number of none-zero neighbour * @param delete_state whether this pixel has been deleted * @param iter_idx if this pixel has been deleted, the variable represent in which iteration this happened */ typedef struct _Descriptor_ { uchar surround = 0; int pattern_cnt = 0; int neighbour_cnt = 0; bool delete_state = false; int iter_idx = -1; }Descriptor; /// @brief this function is used to find the skeleton of patterns in an image /// @param srcimage IO- input and output image void skeletonTry(cv::Mat& srcimage) { int dx[8] = { -1, 0, 1, 1, 1, 0, -1, -1 }; int dy[8] = { -1, -1, -1, 0, 1, 1, 1, 0 }; // mask: 1 is the highest bit, // 1 2 3 mask 0 north 0 // 8 P 4 ---------> west p east // 7 6 5 0 south 0 uchar mask_east = 1 << 4; uchar mask_south = 1 << 2; uchar mask_west = 1; uchar mask_north = 1 << 6; size_t cols = srcimage.cols; Descriptor* describ = new Descriptor[cols * (size_t)(srcimage.rows)]; int cnt_break = 2; int iter_cnt = 0; bool del_found = false; while (true) { del_found = false; for (int i = 1; i < srcimage.rows - 1; i++) { // test point uchar* data_a[3]; data_a[0] = srcimage.ptr<uchar>(i - 1); data_a[1] = srcimage.ptr<uchar>(i); data_a[2] = srcimage.ptr<uchar>(i + 1); for (int j = 1; j < cols - 1; j++) { // 1 2 3 order // 8 P 4 // 7 6 5 uchar p = *(data_a[1] + j); Descriptor* tmp_describ = describ + i * cols + j; if (0 != p && tmp_describ->delete_state != true) { // the pixel is none zero int surround_flag = 0; // auxiliary flag which is used to calculate pattern_cnt for (int z = 0; z < 7; z++) { Descriptor* surround_describ = describ + (i + dy[z]) * cols + j + dx[z]; if (0 < *(data_a[dy[z] + 1] + dx[z] + j) || surround_describ->iter_idx == iter_cnt) { // this neighbour count, surround_flag will be setted as 1 tmp_describ->surround += 1; // the neighbour composition tmp_describ->neighbour_cnt++; // counting the neighbour if (1 != surround_flag) { // 01 pattern found(initial is 0, so if the successor is 1 the counter will self-add by 1) tmp_describ->pattern_cnt++; if (tmp_describ->pattern_cnt > cnt_break) { break; } } surround_flag = 1; } else { surround_flag = 0; } tmp_describ->surround = tmp_describ->surround << 1; } Descriptor* surround_describ = describ + (i + dy[7]) * cols + j + dx[7]; if ((0 < *(data_a[dy[7] + 1] + dx[7] + j) || surround_describ->iter_idx == iter_cnt) && tmp_describ->pattern_cnt <= cnt_break) { tmp_describ->surround += 1; tmp_describ->neighbour_cnt++; if (1 != surround_flag) { // the second condition is used to avoid repetitive count of the first place tmp_describ->pattern_cnt++; } if (tmp_describ->surround & 0x80) { tmp_describ->pattern_cnt--; } } // only if the specific pattern and neighbour number is satisfied // then check the (c) and (d) condition in the paper if (tmp_describ->pattern_cnt == 1 && tmp_describ->neighbour_cnt > 1 && tmp_describ->neighbour_cnt < 7) { // delete east north points and corner points of west sourth if (!( (tmp_describ->surround & mask_east && tmp_describ->surround & mask_south) && (tmp_describ->surround & mask_west || tmp_describ->surround & mask_north) ) && iter_cnt % 2) { // this condition is derived from {!(s & m_e) || !(s & m_s)} || {!(s & m_w) && !(s & m_n)} by applying converse-negative proposition tmp_describ->delete_state = true; tmp_describ->iter_idx = iter_cnt; del_found = true; *(data_a[1] + j) = 0; } // delete west south points and corner points of east north(ok) if (!( (tmp_describ->surround & mask_west && tmp_describ->surround & mask_north) && (tmp_describ->surround & mask_east || tmp_describ->surround & mask_south) ) && (iter_cnt + 1) % 2) { // this condition is derived from {!(s & m_w) || !(s & m_n)} || {!(s & m_e) && !(s & m_s)} by applying converse-negative proposition tmp_describ->delete_state = true; tmp_describ->iter_idx = iter_cnt; del_found = true; *(data_a[1] + j) = 0; } } } if (!tmp_describ->delete_state) { // reset the descriptor tmp_describ->neighbour_cnt = 0; tmp_describ->pattern_cnt = 0; tmp_describ->surround = 0; } } } iter_cnt++; if (!del_found) { break; } } }
调用
int main(int argc, char* argv) { //callEdgeClose(); cv::Mat test(cv::Size(112, 112), CV_8UC1, cv::Scalar(0)); cv::Mat region = test(cv::Rect(1, 1, 100, 100)); cv::Mat region2 = test(cv::Rect(10, 10, 100, 100)); cv::Mat region1 = test(cv::Rect(10, 10, 80, 80)); cv::Mat region3 = test(cv::Rect(90, 90, 5, 5)); cv::Mat region4 = test(cv::Rect(40, 40, 15, 66)); cv::Mat region5 = test(cv::Rect(40, 40, 5, 25)); region = cv::Scalar(255); region2 = cv::Scalar(255); region1 = cv::Scalar(0); region3 = cv::Scalar(0); region4 = cv::Scalar(0); region5 = cv::Scalar(255); cv::circle(test, cv::Point(55, 55), 40, cv::Scalar(255), 2); //cv::rectangle(test, cv::Rect(1, 1, 100, 100), cv::Scalar(255), 10); std::string im_name = "origin"; cv::namedWindow(im_name, 0); cv::imshow(im_name, test); cv::waitKey(); cv::Mat skeleton_im = test.clone(); skeletonTry(skeleton_im); im_name = "skeleton"; cv::namedWindow(im_name, 0); cv::imshow(im_name, test - skeleton_im / 2); cv::waitKey(); return 0; }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。