这是视觉组dalao给大家布置的学期末学习任务。
因为之前没有接触过linux,环境也没有配置好,对很多操作不够熟悉,做这个任务从头到尾大约花了两天合计15个小时的时间,中间还问过dalao两个小时左右的问题(此处给大佬比心),虽然和大佬说的三个小时相去甚远但是最后还是赶在ddl之前完成了任务。
总的来说收获很多,了解了ZhangSuen骨架提取算法的原理和算法实现,复习了面向对象编程的C++,还对linux系统(Ubuntu)有了进一步的了解,写第一篇博文记录一下大致过程和心得。
使用ZhangSuen骨架提取算法实现简化数字轮廓
这里参考了CSDN上和博客园的两篇文章,分别涉及原理和代码实现,链接附上
sudo gedit /etc/apt/sources.list
,在打开的文件窗口中将内容全部删除并复制上镜像源地址:参考Ubuntu 18.04换国内源 中科大源 阿里源 163源 清华源 博主:nudt_qxx,从中选一个复制粘贴(终端中的粘贴是Ctrl+Shift+V
),然后把结尾CSDN附带的注释删除再保存就可以了,不知道需不需要重启。sudo apt-get install g++
安装的)或者用makefile。不配置opencv的环境无法编译项目。sudo apt-get install synapic
,再在系统自带的新立得安装包管理器里搜索opencv就可以下载了。出乎意料的方便。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
就可以执行这个文件了。可以看到输出结果。头文件
#ifndef C_ZHANGSUEN_DIY_H
#define C_ZHANGSUEN_DIY_H
#include<opencv2/core.hpp> ///基本数据类型
#include<opencv2/imgproc/imgproc.hpp> ///图像存取&线性变换
#include<opencv2/highgui.hpp> ///输出接口&交互接口
using namespace std;
using namespace cv;
class Skeleton {
public:
void initialize();
void getDst(Mat src);
void DstToImg();
int getAP(int i, int j);
int getBP(int i, int j);
bool condition34IsOK_1(int i, int j);
bool condition34IsOK_2(int i, int j);
void erasePoint(int i, int j);
Mat Mat_return();
Skeleton();
private:
Mat dst;
Mat tmpImg;
int height;
int width;
};
//主函数调用函数 函数调用类 并在函数体中判断条件 最后利用函数体返回
Mat skeleton(Mat src);
#endif //C_ZHANGSUEN_DIY_H
主文件
//*recoverd by Zhenglin/on 2018/12/29
#include "ZhangSuen_DIY.h"/
Mat skeleton(Mat src);
int main(){
Mat src = imread("E:/Python/6.jpg");
Mat gray, binary, dst, skeleton_image;
//图像预处理
cvtColor(src, gray, CV_BGR2GRAY);
threshold(gray, binary, 200, 255, CV_THRESH_BINARY); ///二值化需要在灰度基础上进行
namedWindow("Binary", 0); ///只能对黑底白线进行提取,否则需要位反
imshow("Binary", binary);
//骨架提取
skeleton_image = skeleton(binary);
//输出图像
namedWindow("Result", 0);
imshow("Result", skeleton_image);
waitKey(0);
}
成员函数实现
#include "ZhangSuen_DIY.h"
//此算法的四个条件:
//(a) 2 ≤ B(P1) ≤ 6
//(b) A(P1) = 1
//(c)
// 1. P2 x P4 x P6 = 0 in odd iterations
// 2. P2 x P4 x P8 = 0 in even iterations
//(d)
// 1. P4 x P6 x P8 = 0 in odd iterations
// 2. P2 x P6 x P8 = 0 in even iterations
void Skeleton::initialize() {
height = dst.cols - 1;
width = dst.rows - 1;
}
Skeleton::Skeleton() {
height = dst.cols - 1;
width = dst.rows - 1;
}
void Skeleton::getDst(Mat src) {
src.copyTo(dst);
}
void Skeleton::DstToImg() {
dst.copyTo(tmpImg);
}
int Skeleton::getAP(int i, int j) {
int ap = 0;
uchar *pU, *pC, *pD;
pU = tmpImg.ptr<uchar>(i - 1); ///uchar类型的行指针
pC = tmpImg.ptr<uchar>(i);
pD = tmpImg.ptr<uchar>(i + 1);
if (pC[j] > 0) {
int ap = 0;
int p2 = (pU[j] > 0);
int p3 = (pU[j + 1] > 0);
if (p2 == 0 && p3 == 1)
ap++;
int p4 = (pC[j + 1] > 0);
if (p3 == 0 && p4 == 1)
ap++;
int p5 = (pD[j + 1] > 0); ///9 2 3
if (p4 == 0 && p5 == 1) ///8 j 4
ap++; ///7 6 5
int p6 = (pD[j] > 0);
if (p5 == 0 && p6 == 1)
ap++;
int p7 = (pD[j - 1] > 0);
if (p6 == 0 && p7 == 1)
ap++;
int p8 = (pC[j - 1] > 0);
if (p7 == 0 && p8 == 1)
ap++;
int p9 = (pU[j - 1] > 0);
if (p8 == 0 && p9 == 1)
ap++;
if (p9 == 0 && p2 == 1)
ap++;
return ap;
}
}
int Skeleton::getBP(int i, int j) {
uchar *pU, *pC, *pD;
pU = tmpImg.ptr<uchar>(i - 1);
pC = tmpImg.ptr<uchar>(i);
pD = tmpImg.ptr<uchar>(i + 1);
if (pC[j] > 0) {
int p2 = (pU[j] > 0);
int p3 = (pU[j + 1] > 0);
int p4 = (pC[j + 1] > 0);
int p5 = (pD[j + 1] > 0);
int p6 = (pD[j] > 0);
int p7 = (pD[j - 1] > 0);
int p8 = (pC[j - 1] > 0);
int p9 = (pU[j - 1] > 0);
return p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9;
}
}
bool Skeleton::condition34IsOK_1(int i, int j) {
uchar *pU, *pC, *pD;
pU = tmpImg.ptr<uchar>(i - 1);
pC = tmpImg.ptr<uchar>(i);
pD = tmpImg.ptr<uchar>(i + 1);
if (pC[j] > 0) {
int p2 = (pU[j] > 0);
int p3 = (pU[j + 1] > 0);
int p4 = (pC[j + 1] > 0);
int p5 = (pD[j + 1] > 0);
int p6 = (pD[j] > 0);
int p7 = (pD[j - 1] > 0);
int p8 = (pC[j - 1] > 0);
int p9 = (pU[j - 1] > 0);
if ((p2*p4*p6 == 0) && (p4*p6*p8 == 0)) return 1;
else return 0;
}
};
bool Skeleton::condition34IsOK_2(int i, int j) {
uchar *pU, *pC, *pD;
pU = tmpImg.ptr<uchar>(i - 1);
pC = tmpImg.ptr<uchar>(i);
pD = tmpImg.ptr<uchar>(i + 1);
if (pC[j] > 0) {
int p2 = (pU[j] > 0);
int p3 = (pU[j + 1] > 0);
int p4 = (pC[j + 1] > 0);
int p5 = (pD[j + 1] > 0);
int p6 = (pD[j] > 0);
int p7 = (pD[j - 1] > 0);
int p8 = (pC[j - 1] > 0);
int p9 = (pU[j - 1] > 0);
if ((p2*p4*p8 == 0) && (p2*p6*p8 == 0)) return 1;
else return 0;
}
};
void Skeleton::erasePoint(int i, int j) {
dst.ptr<uchar>(i)[j] = 0;
}
Mat Skeleton::Mat_return() {
return dst;
};
Mat skeleton(Mat src) {
int i = 0, j = 0;
bool isFinished = false;
Skeleton skel;
skel.getDst(src);
skel.initialize();
while (true) {
skel.DstToImg();
isFinished = false;
//扫描过程一 开始
for (i = 1; i < src.cols - 1; i++) {
for (int j = 1; j < src.rows - 1; j++) {
if (skel.getBP(i, j) > 1 && skel.getBP(i, j) < 7) {
if (skel.getAP(i, j) == 1) {
if (skel.condition34IsOK_1(i, j)) {
skel.erasePoint(i, j);
isFinished = true;
}
}
}
}
}
//扫描过程一 结束
skel.DstToImg();
//扫描过程二 开始
for (i = 1; i < src.cols - 1; i++) {
for (int j = 1; j < src.rows - 1; j++) {
if (skel.getBP(i, j) > 1 && skel.getBP(i, j) < 7) {
if (skel.getAP(i, j) == 1) {
if (skel.condition34IsOK_2(i, j)) {
skel.erasePoint(i, j);
isFinished = true;
}
}
}
}
}
//扫描过程二 结束
if (isFinished == false)///如果在扫描过程中没有删除点则提前退出
return skel.Mat_return();
}
}
经验+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