视觉组学习内容:Zhang-Suen骨架提取算法

╰+攻爆jí腚メ 2022-04-01 11:24 618阅读 0赞

视觉组学习内容:Zhang-Suen骨架提取算法

  • 前言
    • 任务要求
    • 实现要求
    • 任务完成流程概要
      • 1.在Windows平台的VS 2017上写好函数风格代码
      • 2.使用类对原函数进行封装与调试
      • 3.配置Ubuntu环境
      • 4.在Terminal中使用g++完成编译
    • 封装代码
    • 参考文献:

前言

这是视觉组dalao给大家布置的学期末学习任务。
因为之前没有接触过linux,环境也没有配置好,对很多操作不够熟悉,做这个任务从头到尾大约花了两天合计15个小时的时间,中间还问过dalao两个小时左右的问题(此处给大佬比心),虽然和大佬说的三个小时相去甚远但是最后还是赶在ddl之前完成了任务。
总的来说收获很多,了解了ZhangSuen骨架提取算法的原理和算法实现,复习了面向对象编程的C++,还对linux系统(Ubuntu)有了进一步的了解,写第一篇博文记录一下大致过程和心得。

任务要求

使用ZhangSuen骨架提取算法实现简化数字轮廓

效果如图所示: Alt

实现要求

  • 使用类简化代码量,增强可读性;类的声明放在.h头文件中,函数实现单独放在.cpp文件中
  • 不允许包含;把要用到的模块搞清楚,包含最少的头文件
  • Linux下使用命令行或makefile编译程序,不允许使用pkg-config,不能有-lopencv_*这种写法,需要用到哪个库文件就明确调用哪个库文件

任务完成流程概要

1.在Windows平台的VS 2017上写好函数风格代码

这里参考了CSDN上和博客园的两篇文章,分别涉及原理和代码实现,链接附上

  • 原理
    两种图像骨架提取算法的研究(1)原理部分 博主:zhubaohua_bupt
  • 代码实现
    【20160924】GOCVHelper 图像增强部分(3) 博主:jsxyhelu
  • 了解opencv各个模块的功能
    要求2是不能使用opencv.hpp,那就要了解其他模块的功能。
    参考Opencv3.2各个模块功能详细简介(包括与Opencv2.4的区别) 博主:朱铭德

2.使用类对原函数进行封装与调试

  • 复习C++面向对象编程
    类的成员函数定义、初始化函数等概念的复习
  • 类的声明/定义/使用
    类的封装风格参考过图像处理之Zhang Suen细化算法 博主:gloomyfish,虽然是java平台的opencv,但是可以启发灵感
    代码应该是在Ubuntu的环境中直接写的,但是由于没有配置,所以先在Windows写好以后再到Ubuntu里面使用,代码见后。

3.配置Ubuntu环境

  • 换源
    换源是为了提高网络速度。这里弄了一阵子的时间,原因是因为很多教程都不是从零起步,而且有一定问题。很多网站上都是直接用vim(有可能没安装) 或者使用“打开”/“编辑”这种用语,对初入Ubuntu的小白很不友好。
    正确操作是先按Ctrl+Alt+T打开终端,然后输入sudo gedit /etc/apt/sources.list,在打开的文件窗口中将内容全部删除并复制上镜像源地址:参考Ubuntu 18.04换国内源 中科大源 阿里源 163源 清华源 博主:nudt_qxx,从中选一个复制粘贴(终端中的粘贴是Ctrl+Shift+V),然后把结尾CSDN附带的注释删除再保存就可以了,不知道需不需要重启。
  • 安装cmake(虽然没用上)
    用于cmake和makefile(?)
  • 安装opencv环境
    任务3要求在终端中编译(编译用g++,这个我是自己用sudo apt-get install g++安装的)或者用makefile。不配置opencv的环境无法编译项目。
    在终端中使用sudo apt-get install synapic,再在系统自带的新立得安装包管理器里搜索opencv就可以下载了。出乎意料的方便。
  • 安装编辑器及其他软件

4.在Terminal中使用g++完成编译

  • 使用g++编译
    大佬实力操作。
    到这一步要准备编译。首先把我写好的两个cpp和一个.h文件放到同一个文件夹下,然后在右键菜单中点击“在终端打开”,输入g++ -o Zhenglin main.cpp class.cpp `pkg-config --libs --cflags opencv` 要求3是不允许使用pkg-config和-lopencv_*,那写g++ -o z class.cpp main.cpp -lopencv_core -lopencv_highgui -lopencv_imgproc -lopencv_imgcodecs也是正确的。注意,虽然头文件没有,但是终端还是要通过-l调用-lopencv_imgcodecs,否则会报错。
    如果程序正确,编译成功了就会生成一个叫做Zhenglin的文件,此时再输入./Zhenglin就可以执行这个文件了。可以看到输出结果。
    结果
    注意:使用文件时要注意有没有输入的图片,需要在源文件中修改路径。

封装代码

头文件

  1. #ifndef C_ZHANGSUEN_DIY_H
  2. #define C_ZHANGSUEN_DIY_H
  3. #include<opencv2/core.hpp> ///基本数据类型
  4. #include<opencv2/imgproc/imgproc.hpp> ///图像存取&线性变换
  5. #include<opencv2/highgui.hpp> ///输出接口&交互接口
  6. using namespace std;
  7. using namespace cv;
  8. class Skeleton {
  9. public:
  10. void initialize();
  11. void getDst(Mat src);
  12. void DstToImg();
  13. int getAP(int i, int j);
  14. int getBP(int i, int j);
  15. bool condition34IsOK_1(int i, int j);
  16. bool condition34IsOK_2(int i, int j);
  17. void erasePoint(int i, int j);
  18. Mat Mat_return();
  19. Skeleton();
  20. private:
  21. Mat dst;
  22. Mat tmpImg;
  23. int height;
  24. int width;
  25. };
  26. //主函数调用函数 函数调用类 并在函数体中判断条件 最后利用函数体返回
  27. Mat skeleton(Mat src);
  28. #endif //C_ZHANGSUEN_DIY_H

主文件

  1. //*recoverd by Zhenglin/on 2018/12/29
  2. #include "ZhangSuen_DIY.h"/
  3. Mat skeleton(Mat src);
  4. int main(){
  5. Mat src = imread("E:/Python/6.jpg");
  6. Mat gray, binary, dst, skeleton_image;
  7. //图像预处理
  8. cvtColor(src, gray, CV_BGR2GRAY);
  9. threshold(gray, binary, 200, 255, CV_THRESH_BINARY); ///二值化需要在灰度基础上进行
  10. namedWindow("Binary", 0); ///只能对黑底白线进行提取,否则需要位反
  11. imshow("Binary", binary);
  12. //骨架提取
  13. skeleton_image = skeleton(binary);
  14. //输出图像
  15. namedWindow("Result", 0);
  16. imshow("Result", skeleton_image);
  17. waitKey(0);
  18. }

成员函数实现

  1. #include "ZhangSuen_DIY.h"
  2. //此算法的四个条件:
  3. //(a) 2 ≤ B(P1) ≤ 6
  4. //(b) A(P1) = 1
  5. //(c)
  6. // 1. P2 x P4 x P6 = 0 in odd iterations
  7. // 2. P2 x P4 x P8 = 0 in even iterations
  8. //(d)
  9. // 1. P4 x P6 x P8 = 0 in odd iterations
  10. // 2. P2 x P6 x P8 = 0 in even iterations
  11. void Skeleton::initialize() {
  12. height = dst.cols - 1;
  13. width = dst.rows - 1;
  14. }
  15. Skeleton::Skeleton() {
  16. height = dst.cols - 1;
  17. width = dst.rows - 1;
  18. }
  19. void Skeleton::getDst(Mat src) {
  20. src.copyTo(dst);
  21. }
  22. void Skeleton::DstToImg() {
  23. dst.copyTo(tmpImg);
  24. }
  25. int Skeleton::getAP(int i, int j) {
  26. int ap = 0;
  27. uchar *pU, *pC, *pD;
  28. pU = tmpImg.ptr<uchar>(i - 1); ///uchar类型的行指针
  29. pC = tmpImg.ptr<uchar>(i);
  30. pD = tmpImg.ptr<uchar>(i + 1);
  31. if (pC[j] > 0) {
  32. int ap = 0;
  33. int p2 = (pU[j] > 0);
  34. int p3 = (pU[j + 1] > 0);
  35. if (p2 == 0 && p3 == 1)
  36. ap++;
  37. int p4 = (pC[j + 1] > 0);
  38. if (p3 == 0 && p4 == 1)
  39. ap++;
  40. int p5 = (pD[j + 1] > 0); ///9 2 3
  41. if (p4 == 0 && p5 == 1) ///8 j 4
  42. ap++; ///7 6 5
  43. int p6 = (pD[j] > 0);
  44. if (p5 == 0 && p6 == 1)
  45. ap++;
  46. int p7 = (pD[j - 1] > 0);
  47. if (p6 == 0 && p7 == 1)
  48. ap++;
  49. int p8 = (pC[j - 1] > 0);
  50. if (p7 == 0 && p8 == 1)
  51. ap++;
  52. int p9 = (pU[j - 1] > 0);
  53. if (p8 == 0 && p9 == 1)
  54. ap++;
  55. if (p9 == 0 && p2 == 1)
  56. ap++;
  57. return ap;
  58. }
  59. }
  60. int Skeleton::getBP(int i, int j) {
  61. uchar *pU, *pC, *pD;
  62. pU = tmpImg.ptr<uchar>(i - 1);
  63. pC = tmpImg.ptr<uchar>(i);
  64. pD = tmpImg.ptr<uchar>(i + 1);
  65. if (pC[j] > 0) {
  66. int p2 = (pU[j] > 0);
  67. int p3 = (pU[j + 1] > 0);
  68. int p4 = (pC[j + 1] > 0);
  69. int p5 = (pD[j + 1] > 0);
  70. int p6 = (pD[j] > 0);
  71. int p7 = (pD[j - 1] > 0);
  72. int p8 = (pC[j - 1] > 0);
  73. int p9 = (pU[j - 1] > 0);
  74. return p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9;
  75. }
  76. }
  77. bool Skeleton::condition34IsOK_1(int i, int j) {
  78. uchar *pU, *pC, *pD;
  79. pU = tmpImg.ptr<uchar>(i - 1);
  80. pC = tmpImg.ptr<uchar>(i);
  81. pD = tmpImg.ptr<uchar>(i + 1);
  82. if (pC[j] > 0) {
  83. int p2 = (pU[j] > 0);
  84. int p3 = (pU[j + 1] > 0);
  85. int p4 = (pC[j + 1] > 0);
  86. int p5 = (pD[j + 1] > 0);
  87. int p6 = (pD[j] > 0);
  88. int p7 = (pD[j - 1] > 0);
  89. int p8 = (pC[j - 1] > 0);
  90. int p9 = (pU[j - 1] > 0);
  91. if ((p2*p4*p6 == 0) && (p4*p6*p8 == 0)) return 1;
  92. else return 0;
  93. }
  94. };
  95. bool Skeleton::condition34IsOK_2(int i, int j) {
  96. uchar *pU, *pC, *pD;
  97. pU = tmpImg.ptr<uchar>(i - 1);
  98. pC = tmpImg.ptr<uchar>(i);
  99. pD = tmpImg.ptr<uchar>(i + 1);
  100. if (pC[j] > 0) {
  101. int p2 = (pU[j] > 0);
  102. int p3 = (pU[j + 1] > 0);
  103. int p4 = (pC[j + 1] > 0);
  104. int p5 = (pD[j + 1] > 0);
  105. int p6 = (pD[j] > 0);
  106. int p7 = (pD[j - 1] > 0);
  107. int p8 = (pC[j - 1] > 0);
  108. int p9 = (pU[j - 1] > 0);
  109. if ((p2*p4*p8 == 0) && (p2*p6*p8 == 0)) return 1;
  110. else return 0;
  111. }
  112. };
  113. void Skeleton::erasePoint(int i, int j) {
  114. dst.ptr<uchar>(i)[j] = 0;
  115. }
  116. Mat Skeleton::Mat_return() {
  117. return dst;
  118. };
  119. Mat skeleton(Mat src) {
  120. int i = 0, j = 0;
  121. bool isFinished = false;
  122. Skeleton skel;
  123. skel.getDst(src);
  124. skel.initialize();
  125. while (true) {
  126. skel.DstToImg();
  127. isFinished = false;
  128. //扫描过程一 开始
  129. for (i = 1; i < src.cols - 1; i++) {
  130. for (int j = 1; j < src.rows - 1; j++) {
  131. if (skel.getBP(i, j) > 1 && skel.getBP(i, j) < 7) {
  132. if (skel.getAP(i, j) == 1) {
  133. if (skel.condition34IsOK_1(i, j)) {
  134. skel.erasePoint(i, j);
  135. isFinished = true;
  136. }
  137. }
  138. }
  139. }
  140. }
  141. //扫描过程一 结束
  142. skel.DstToImg();
  143. //扫描过程二 开始
  144. for (i = 1; i < src.cols - 1; i++) {
  145. for (int j = 1; j < src.rows - 1; j++) {
  146. if (skel.getBP(i, j) > 1 && skel.getBP(i, j) < 7) {
  147. if (skel.getAP(i, j) == 1) {
  148. if (skel.condition34IsOK_2(i, j)) {
  149. skel.erasePoint(i, j);
  150. isFinished = true;
  151. }
  152. }
  153. }
  154. }
  155. }
  156. //扫描过程二 结束
  157. if (isFinished == false)///如果在扫描过程中没有删除点则提前退出
  158. return skel.Mat_return();
  159. }
  160. }

经验+5

参考文献:

[1]两种图像骨架提取算法的研究(1)原理部分 博主:zhubaohua_bupt
[2]【20160924】GOCVHelper 图像增强部分(3) 博主:jsxyhelu
[3]Opencv3.2各个模块功能详细简介(包括与Opencv2.4的区别) 博主:朱铭德
[4]图像处理之Zhang Suen细化算法 博主:gloomyfish
[5]Ubuntu 18.04换国内源 中科大源 阿里源 163源 清华源 博主:nudt_qxx

发表评论

表情:
评论列表 (有 0 条评论,618人围观)

还没有评论,来说两句吧...

相关阅读