**
作者:Simon Song
**
先看两张图。我们要实现对生产胶囊的快速检测,有两种方案,一种是DNN方式,一种是openCV方法。为避开大量样本集的问题,我选择的是openCV方式实现胶囊检测。
检测规则如下:
一、三个胶囊不全为空,且检测结果均为好品,该幅图判断为好品。三个胶囊不全为空,有至少一个胶囊为坏品,该幅图判断为坏品。
二、每个胶囊分别检测,顶部蓝色数字代表每个胶囊检测结果:
-1:空
0:好品
其他大于1的数字代表不同缺陷种类,请自行编号。
三、左侧显示检测参数,例如:W宽,H高,DA脏点面积。可按实际需要显示。
四、好品胶囊标注为绿色。坏品胶囊标注为红色,相应错误参数也标注为红色。
代码环境:
VS2005+openCV341+C++
先给出我的实现代码:
#include
#include
using namespace std;
using namespace cv;
//定义所需文件路径
string sample_path = "G:/opencv_trainning/vs2015/myself_project/Capsule_Picture/";//样本路径
string good_example = "G:/opencv_trainning/vs2015/myself_project/Capsule_Picture/good_example/goodmodel.bmp";//好的模板
//定义数据结构,用于返回数据方便操作
typedef struct mydata{
char classification;//分类,-1:坏品,0:好品,1:异形,2:大小端,3:气泡,4:黑点, 5:空位置
short width;//宽
short height;//高
double area;//面积
//其他再添加
} Mydata;//起个别名方便调用
vector<string> filelist;//文件列表
//定义变量和判断函数
vector<vector<Point>> model_contours;//模型轮廓
vector<Vec4i> model_hierarchy;//模型拓扑结构
int max_model_contours;//模型最大轮廓变量
void model_reader(void);//模型读取器
Mat processing(Mat& capsule,Mat& black_mask,Mat& small_mask);//胶囊图片处理
bool is_empty_func(Mat& mask);//判空函数
bool is_alien_func(Mat& mask,Mydata& data);//判变异函数
bool is_big_and_small_func(Mat& mask, Mydata& data);//判断大小头函数
bool is_bobble_and_blackP_func(Mat& capsule,Mat& mask, Mydata& data);//判气泡和黑点函数,绘制问题位置
Mydata capsule_processing_funciton(Mat& capsule,Mat& mask,Mat&small_mask);//胶囊处理函数,返回胶囊所需数据
void drawCapsule_function(Mat& capsule,Mat&blk_mask,Mat& mask,Mat& small_mask,Mydata& data);//绘制胶囊掩码
void putText_function(Mat& src,Rect&pos,Mydata& data);//在图片上写文本
void drawDashContours(InputOutputArray image, InputArrayOfArrays contours,
int contourIdx, const Scalar& color, int thinkness, int lineType,int gap_threshold);//自定义函数,实现画虚线轮廓
/*
此文件用于胶囊检测验证,目录中已提供了样本图
说明:
*/
int main(int argc, char** argv) {
//读取文件列表
ifstream file((sample_path+"file.txt").c_str());//定义文件流
if (!file.is_open()) {//打开文件
cout << "open fault" << endl;
return -1;
}
string line;//定义字符串
while (!file.eof()) {//当文件可读时
//读取一行,参数:(文件流,字符串)
getline(file, line);
//printf("%s\n",line.c_str());//打印提示
//写入文件列表,转为c字符串保存
filelist.push_back(line.c_str());
}
//读取好的模型
model_reader();
//循环处理图片
int index = 0;//索引位置
Mat src;//定义图矩阵
while (index < filelist.size()){//当位置有效时
cout << "file_path:" << filelist[index] << endl;;//显示文件名
//读取图片,参数:(文件名),位置后加1
src= imread(filelist[index++]);
if (src.empty()) {//判空处理
cout << "open fault" << endl;
return -1;
}
//显示
imshow("src", src);
//发现黑色框
Mat black_mask;
inRange(src, Scalar(0, 0, 0), Scalar(180, 255, 46), black_mask);//获取黑色区域
bitwise_not(black_mask, black_mask);//反色处理,得到胶囊的黑框部分
//图形学-闭操作,使得边缘更好
//参数:(形状,核尺寸,锚点)
Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));//定义结构元素
//参数:(图,目标图,操作标记,结构元素,锚点,迭代次数), 其他参数默认
morphologyEx(black_mask, black_mask, MORPH_CLOSE, kernel, Point(-1, -1), 8);//闭操作处理
//腐蚀-让边缘收进去一部分
//参数:(图,目标图,操作标记,结构元素,锚点,迭代次数), 其他参数默认
morphologyEx(black_mask, black_mask, MORPH_ERODE, kernel, Point(-1, -1),5);//腐蚀操作处理,8
//获得掩码图,用于显示
Mat black_mask_img;
src.copyTo(black_mask_img,black_mask);//获得胶囊以外的图
//imshow("black_mask_img",black_mask_img);//显示
bitwise_not(black_mask,black_mask);//反色操作,得到胶囊位置为黑色
//imshow("black_mask", black_mask);//显示
//0.0转为灰度图
Mat gray;//定义灰度图
cvtColor(src,gray,CV_BGR2GRAY);
//imshow("gray",gray);
//GaussianBlur(gray,gray);
//0.1二值化图
Mat binary;//二值化图
//参数:(灰度图,目标图,低阈值,最大值,阈值模式)
threshold(gray,binary,0,255,CV_THRESH_BINARY|CV_THRESH_OTSU);//多峰自动阈值
//imshow("binary", binary);
//0.2图形学-闭操作,去除内部干扰
//参数:(形状宏定义,核尺寸,锚点)
Mat element = getStructuringElement(MORPH_RECT,Size(10,10),Point(-1,-1));//定义结构元素
//参数:(图,目标图,操作宏定义,结构元素),其他参数默认
morphologyEx(binary,binary,MORPH_CLOSE,element,Point(-1,-1));//闭操作
//1.获得每个一个胶囊的位置图片
//1.1发现轮廓,只找最外层的轮廓
vector<vector<Point>> contours;//轮廓向量
vector<Vec4i> hierarchy;//拓扑结构变量,单个结构数据含义为[后一个标记位置,前一个标记位置,子轮廓标记位置,父轮廓标记位置]
//参数:(二值化图,轮廓向量,检测方法,发现方法,偏移点),偏移点用于大图还原,此处未用到
findContours(binary,contours,RETR_EXTERNAL,CHAIN_APPROX_SIMPLE,Point(0,0));
//1.2获取每个胶囊图
Mat contoursImg=src.clone();//克隆图为了整体显示
vector<Mat> capsules;//胶囊向量
vector<Mat> blk_mask;//胶囊掩码向量
vector<Mat> small_mask;//胶囊的小掩码
vector<Rect> good_pos;//好的胶囊位置
for (int c = 0; c < contours.size(); c++) {//循环轮廓位置
//计算面积,参数:(单个轮廓)
double area = contourArea(contours[c]);
if (area/100 < 600)continue;//当面积太小,继续下一次
cout << "area/100=" << area / 100 << endl;//打印提示
//获得框位置,参数:单个轮廓,返回矩形变量
Rect pos = boundingRect(contours[c]);
//截取图片局部
capsules.push_back(src(pos));
//截取掩码局部
blk_mask.push_back(black_mask(pos));
//保存好的矩形位置信息,后面使用
good_pos.push_back(pos);
//保存全黑图到小胶囊掩码,后面使用
small_mask.push_back(Mat::zeros(pos.height,pos.width,CV_8UC1));
//绘制到图上,参数:(图,rect变量,颜色,线宽)
rectangle(contoursImg,pos,Scalar(0,0,255),1);
//显示每个胶囊
//imshow(format("capsules[%d]", capsules.size() - 1).c_str(),capsules[capsules.size()-1]);
}
cout << "--------" << endl;//中断打印提示
//imshow("contoursImg",contoursImg);
//Mat processing_board = src.clone();//克隆一张图给处理图,后面用于显示
vector<char> class_group;//分类组变量,用于统计各种综合情况的变量
//2.检测胶囊内的几种情况:空,异型,气泡,黑点等
for (int c = 0; c < capsules.size(); c++) {//循环胶囊
//显示胶囊
//imshow("org_capsule",capsules[c]);
//处理图片为纯胶囊掩码图,方便后面操作,参数:(胶囊原图,胶囊位置掩码,胶囊小掩码图)
Mat mask = processing(capsules[c],blk_mask[c], small_mask[c]);
//胶囊处理,获得胶囊数据,参数:(胶囊原图,胶囊小掩码图)
Mydata data = capsule_processing_funciton(capsules[c],mask,small_mask[c]);
//绘制胶囊线,参数:(胶囊原图,胶囊位置掩码,纯胶囊掩码,胶囊小掩码,胶囊数据)
drawCapsule_function(capsules[c],blk_mask[c],mask,small_mask[c],data);
//绘制文本,参数:(图,胶囊位置信息(rect),胶囊数据)
putText_function(src, good_pos[c],data);
//保存分类号
class_group.push_back(data.classification);
}
//判断整体好品情况
if ((class_group[0]==5&&class_group[1]==5&&class_group[3]==5)////当全为空(5)时
||(class_group[0] != 0|| class_group[1] != 0|| class_group[2] != 0)) {//或当有不为好(0)时,为坏品
//放入文字-坏品
//参数:(图,文本,原点,字体,缩放比,颜色,线宽,线型,底左对齐(倒过来的效果))
putText(src,"Bad",Point(0,25),CV_FONT_NORMAL,1.0,Scalar(0,0,255),1,8,false);
}else {//否则为好品
//放入文字-好品
//参数:(图,文本,原点,字体,缩放比,颜色,线宽,线型,底左对齐(倒过来的效果))
putText(src, "Good", Point(0, 20), CV_FONT_NORMAL, 1.0, Scalar(0, 255, 0), 1, 8, false);
}
imshow("processing_board", src);
cv::waitKey(4000);//显示等待
}
cv::waitKey(0);//按键等待
return 0;
}
void model_reader(void) {//模型读取器
//读取模板图
Mat good_model = imread(good_example.c_str(), IMREAD_GRAYSCALE);//灰度图
if (good_model.empty()) { //判空处理
cout << "read model fault" << endl;
return;
}
//imshow("model_gray",good_model);//显示
//二值化图
Mat model_bianry;//定义二值化图
//参数:(灰度图,二值化画图,阈值,最大值,二值化标记)
threshold(good_model, model_bianry, 230, 255, CV_THRESH_BINARY | CV_THRESH_TRIANGLE);//单峰自动
//imshow("model_bianry", model_bianry);
//图形学-闭操作,去掉外围噪点
//定义元素结构,参数:(形状,核尺寸,锚点)
Mat element = getStructuringElement(MORPH_RECT,Size(3,3),Point(-1,-1));
//开操作,参数:(图,目标图,操作标记,结构元素,锚点,迭代次数),其他参数默认
morphologyEx(model_bianry,model_bianry,MORPH_CLOSE,element,Point(-1,-1),1);
//反色,获得黑底白面图 参数:(原图,目标图)
bitwise_not(model_bianry,model_bianry);
//imshow("model_morphologyEx", model_bianry);//显示
//模型轮廓发现,参数:(二值化画图,模型轮廓,模型拓扑结构,检测方法,发现方法,偏移量)
findContours(model_bianry, model_contours, model_hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, Point(-1, -1));
//发现最大的那个轮廓
double max_area = 0;//最大面积变量
int max_pos = -1;//最大位置变量
for (int c = 0; c < model_contours.size(); c++) {//循环模型轮廓位置
double area = contourArea(model_contours[c]);//计算面积
if (area > max_area) {//当前轮廓面积大于最大面积时
max_area = area;//赋值最大值
max_pos = c;//保存最大位置
}
}
max_model_contours = max_pos;//赋值到全局变量
cout << "max_model_contours=" << max_model_contours << endl;//打印测试
//显示模型轮廓图
Mat model_contours_img = Mat::zeros(good_model.size(),good_model.type());//定义显示图
//参数:(图,轮廓向量,轮廓位置下标,颜色,线宽,线型,拓扑结构变量)
drawContours(model_contours_img,model_contours, max_model_contours,Scalar(255),-1,8,model_hierarchy);//绘制最大轮廓
//imshow("model_contours_img", model_contours_img);//显示
//清除向量,重新发现轮廓,确保掩码的一致性
model_contours.clear();//轮廓清除
model_hierarchy.clear();//拓扑结构变量清除
max_area = 0;//最大面积变量
max_pos = -1;//最大位置变量
//模型再次轮廓发现,参数:(二值化画图,模型轮廓,模型拓扑结构,检测方法,发现方法,偏移量)
findContours(model_contours_img, model_contours, model_hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, Point(-1, -1));
//发现最大的那个轮廓
for (int c = 0; c < model_contours.size(); c++) {//循环轮廓位置
double area = contourArea(model_contours[c]);//计算面积
if (area > max_area) {//当前轮廓面积大于最大面积时
max_area = area;//赋值最大值
max_pos = c;//保存最大位置
}
}
}
//定义判断函数:颜色掩码+二值化掩码=实际掩码
Mat processing(Mat& capsule, Mat& black_mask,Mat& small_mask) {//胶囊图片处理
//显示原图
//imshow("capsule",capsule);
//获得反的掩码图(黑底白面)
Mat ref_black_mask;//定义反色掩码图
bitwise_not(black_mask,ref_black_mask);//反色处理
//imshow("ref_black_mask", ref_black_mask);
//按颜色范围获得黄颜色掩码
//转为hsv
Mat hsv;//定义HSV图
cvtColor(capsule,hsv,CV_BGR2HSV);//参数:(图1,图2,转换标记)
//提取黄色
Mat mask_yellow;//定义掩码图
inRange(hsv,Scalar(26,43,46),Scalar(34,255,255),mask_yellow);//获取掩码,参数:(图,低值,高值,掩码图)
//掩码处理
Mat new_mask_yellow;//定义新图
mask_yellow.copyTo(new_mask_yellow,ref_black_mask);//掩码拷贝胶囊,使其在胶囊范围内
//图形学-开操作,去掉外面的噪点
Mat element_yellow = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));//参数:(形状,核尺寸,锚点)
morphologyEx(new_mask_yellow, new_mask_yellow, MORPH_OPEN, element_yellow, Point(-1, -1), 4);//参数:(原图,目标图,操作标记,元素矩阵,锚点,迭代次数),其他参数默认
//imshow("inRange", new_mask_yellow);
//使用二值化获得黑白图
//灰度图
Mat gray;//定义灰度图
cvtColor(capsule,gray,CV_BGR2GRAY);//参数:(原图,目标图,转换标记)
//二值化处理
Mat binary;//定义二值化图
threshold(gray,binary,230,255,THRESH_BINARY);//参数:(图,二值化图,阈值,最大值,阈值标记)
//反色处理
bitwise_not(binary,binary);
//获得掩码图
Mat new_binary;
binary.copyTo(new_binary,ref_black_mask);//获得胶囊的框内掩码图,参数:(新图,掩码)
//图像学-开操作
Mat element = getStructuringElement(MORPH_RECT,Size(3,3),Point(-1,-1));//定义核元素,参数:(形状,核尺寸,锚点)
morphologyEx(new_binary,new_binary,MORPH_OPEN,element,Point(-1,-1),4);//开操作,参数:(图,目标图,操作标记,核元素,锚点,迭代次数),其他参数默认
//imshow("threshold_mask", new_binary);
//合并掩码图
Mat mask;//定义总掩码图
bitwise_or(new_mask_yellow,new_binary,mask);//或处理,得到完成掩码,参数:(图1,图2,掩码图)
//图形学-闭操作
Mat element_mask = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));//定义核元素,参数:(形状,核尺寸,锚点)
morphologyEx(mask, mask, MORPH_CLOSE, element_mask, Point(-1, -1), 2);//闭操作,参数:(图,目标图,操作标记,核元素,锚点,迭代次数),其他参数默认
//imshow("mask",mask);
//单掩码处理
//1.发现轮廓
vector<vector<Point>> capsule_contours;//轮廓向量
vector<Vec4i> capsule_hierarchy;//拓扑结构变量
//参数:(二值化图,轮廓向量,拓扑结构变量,检测方法,发现方法,偏移量)
findContours(mask, capsule_contours, capsule_hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, Point(-1, -1));
//当没有轮廓时,返回全黑图
if (capsule_contours.size()<1) {
return Mat::zeros(mask.size(), mask.type());
}
//发现最大的那个轮廓
double max_area = 0;//最大面积变量
int max_pos = -1;//最大位置变量
for (int c = 0; c < capsule_contours.size(); c++) {
double area = contourArea(capsule_contours[c]);//计算面积
if (area > max_area) {//当前轮廓面积大于最大面积时
max_area = area;//赋值最大面积
max_pos = c;//保存最大位置
}
}
//当面积太小时,返回全黑
double area = contourArea(capsule_contours[max_pos])/100;//计算面积,/100方便计算,参数:(单个轮廓)
cout << "area=" << area << endl;
if (area<10) {
return Mat::zeros(mask.size(),mask.type());
}
//绘制轮廓
Mat contours_mask = Mat::zeros(mask.size(), mask.type());//定义图
drawContours(contours_mask, capsule_contours, max_pos, Scalar(255), -1, 8, capsule_hierarchy);//绘制轮廓
//imshow("contours_mask", contours_mask);//显示
//获取原图内容
Mat capsule_small_mask;//定义胶囊小图
gray.copyTo(capsule_small_mask,contours_mask);//获得灰度图的胶囊部分,参数:(新图,掩码)
//imshow("capsule_small_mask", capsule_small_mask);
//二值化处理
Mat binary_small_mask;//定义二值化图
threshold(capsule_small_mask, binary_small_mask,200,255,THRESH_BINARY);//参数:(图,二值化图,阈值,最大值,二值化操作标记)
//图像学-闭操作,去内噪点,此处不需要,故注掉
//Mat element_test = getStructuringElement(MORPH_RECT,Size(3,3),Point(0,0));//定义核元素,参数:(形状,核尺寸,锚点)
//morphologyEx(binary_small_mask,binary_small_mask,MORPH_CLOSE,element,Point(-1,-1),1);//闭操作,参数:(图,目标图,操作标记,核元素,锚点,迭代次数),其他参数默认
//发现轮廓
vector<vector<Point>> small_mask_contours;//轮廓向量
vector<Vec4i> small_mask_hierarchy;//拓扑结果向量
//参数:(二值化图,轮廓向量,拓扑结构变量,检测方法,发现方法,偏移量)
findContours(binary_small_mask,small_mask_contours,small_mask_hierarchy,RETR_TREE,CHAIN_APPROX_SIMPLE,Point(0,0));
//循环查找面积最大的那个
double small_mask_max_area=-1;//定义小轮廓最大面积
int small_mask_max_area_pos=-1;//定义最大面积的位置
for (int i = 0; i < small_mask_contours.size();i++) {//循环轮廓位置
double area = contourArea(small_mask_contours[i]);//计算面积
if (area > small_mask_max_area) {//当前面积大于最大面积时,
small_mask_max_area_pos = i;//记录位置
small_mask_max_area = area;//更改最大面积
}
}
//发现凸包
vector<vector<Point>> convex_contours(small_mask_contours.size());//定义凸包个数为一致大小
for (int i = 0; i < small_mask_contours.size(); i++) {//循环轮廓位置
//发现凸包, 参数:(原轮廓向量,目标轮廓向量,顺时针方向状态,返回点状态)
convexHull(small_mask_contours[i], convex_contours[i], false, true);//获得凸包
//逼近多边形,参数:(原轮廓向量,目标轮廓向量,最小量,闭包状态),放在此处不合适
//approxPolyDP(small_mask_contours[i],convex_contours[i],5,true);
}
//绘制小轮廓
drawContours(small_mask, convex_contours, small_mask_max_area_pos,Scalar(255),-1);//绘制轮廓
//imshow("small_mask", small_mask);
return contours_mask;//返回
}
bool is_empty_func(Mat& mask) {//判空函数
//1.发现轮廓
vector<vector<Point>> capsule_contours;//轮廓向量
vector<Vec4i> capsule_hierarchy;//拓扑结构变量
//参数:(二值化图,轮廓向量,拓扑结构变量,检测方法,发现方法,偏移量)
findContours(mask,capsule_contours,capsule_hierarchy,RETR_EXTERNAL,CHAIN_APPROX_SIMPLE,Point(-1,-1));
if (capsule_contours.size()<1) {//当没有轮廓时
return true;//返回真,表示没有胶囊
}
//计算总面积
double area = 0;//白区面积
for (int c = 0; c < capsule_contours.size(); c++) {//循环胶囊轮廓
//cout << "hierarchy=" << capsule_hierarchy[c][0] << "," << capsule_hierarchy[c][1] << "," << capsule_hierarchy[c][2] << "," << capsule_hierarchy[c][3] << endl;
area += contourArea(capsule_contours[c]) / 100;//计算面积并累加,/100是为了方便计算
}
cout << "area/100=" << area << endl;//打印提示
//判断返回
if (area<300) {//空处理,按照白面积计算判断
return true;//返回真
}else{//否则
return false;//返回假
}
}
bool is_alien_func(Mat& mask, Mydata& data) {//判变异函数
//1.发现轮廓
vector<vector<Point>> capsule_contours;//轮廓向量
vector<Vec4i> capsule_hierarchy;//拓扑结构变量
//参数:(二值化图,轮廓向量,拓扑结构变量,检测方法,发现方法,偏移量)
findContours(mask,capsule_contours,capsule_hierarchy,RETR_EXTERNAL,CHAIN_APPROX_SIMPLE,Point(-1,-1));
//发现最大的那个轮廓
double max_area = 0;//最大面积变量
int max_pos = -1;//最大位置变量
RotatedRect max_rrect;//定义最大斜矩阵
for (int c = 0; c < capsule_contours.size(); c++) {
double area = contourArea(capsule_contours[c]);//计算面积
if (area > max_area) {//当前轮廓面积大于最大面积时
max_area = area;//赋值最大面积
max_pos = c;//保存最大位置
max_rrect = minAreaRect(capsule_contours[c]);//获得斜矩阵
}
}
//给定数据,为何防止出现宽高显示不对的情况,人为调整一下
Mydata local_data;//定义本地结构变量
local_data.height = (max_rrect.size.height>max_rrect.size.width? max_rrect.size.height: max_rrect.size.width);//给定高
local_data.width = (max_rrect.size.height>max_rrect.size.width ? max_rrect.size.width : max_rrect.size.height);//给定宽
local_data.area = max_area;//给定面积
//绘制轮廓,为了显示
Mat contours_img = Mat::zeros(mask.size(), mask.type());//定义图
drawContours(contours_img,capsule_contours,max_pos,Scalar(255),-1,8,capsule_hierarchy);//绘制轮廓
//imshow("is_alien_func_contours", contours_img);//显示
/*//从点数量上无法判别异形的特点,故注掉
//2.使用最小多边形点位置,判断是否为异型
//2.1胶囊的最小轮廓点
vector> min_contours(1);//轮廓数量与轮廓向量一致,此处1个就行
//基于RDP获得最小轮廓点 (多变型轮廓点数量),参数:(原轮廓,目标轮廓,最小量,封闭状态)
approxPolyDP(capsule_contours[max_pos],min_contours[0],5,true);
printf("capsule min_contours[%d].size()=%d\n",0, min_contours[0].size());
//2.2模型的最小轮廓
vector> model_min_contours(1);//轮廓数量与轮廓向量一致,此处1个就行
//基于RDP获得最小轮廓点 (多变型轮廓点数量),参数:(原轮廓,目标轮廓,最小量,封闭状态)
approxPolyDP(model_contours[max_model_contours],model_min_contours[0],5,true);
printf("model min_contours[%d].size()=%d\n", 0, model_min_contours[0].size());
//绘制,
Mat compare_contours_img = Mat::zeros(mask.size(),CV_8UC3);
drawContours(compare_contours_img, min_contours,0,Scalar(0,0,255),1);
drawContours(compare_contours_img, model_min_contours, 0, Scalar(0, 255, 0), 1);
imshow("compare_contours_img", compare_contours_img);
//2.3形状比较,完全相同返回0,最大值为1,参数:(轮廓1,轮廓2,轮廓匹配表示(CV_CONTOURS_MATCH_I1/I2/I3),0)
double score = matchShapes(model_min_contours[0], min_contours[0], CV_CONTOURS_MATCH_I1, 0);
cout << "score=" << score << endl;//打印提示
//判断得分
if (score > 0.10) {//大于10%返回true
putText(compare_contours_img, "alien", Point(20, 20), CV_FONT_NORMAL, 0.5, Scalar(0, 0, 255), 1);//添加文字显示
imshow("compare_contours_img", compare_contours_img);
return true;//返回真
}
else {//否则返回假
putText(compare_contours_img, "normal", Point(20, 20), CV_FONT_NORMAL, 0.5, Scalar(0, 255, 0), 1);//添加文字显示
imshow("compare_contours_img", compare_contours_img);
return false;//返回假
}
*/
//2.使用凸包测试
//2.1胶囊的最大轮廓点
vector<vector<Point>> convex_contours(1);//轮廓数量与轮廓向量一致,此处1个就行
//获得凸包,参数:(原轮廓,目标轮廓,顺时针方向状态,返回点状态)
convexHull(capsule_contours[max_pos], convex_contours[0],false,true);
//printf("capsule min_contours[%d].size()=%d\n", 0, convex_contours[0].size());
//2.2模型的最大轮廓
vector<vector<Point>> model_convex_contours(1);//轮廓数量与轮廓向量一致,此处1个就行
//获得凸包,参数:(原轮廓,目标轮廓,顺时针方向状态,返回点状态)
convexHull(model_contours[max_model_contours], model_convex_contours[0],false,true);
//printf("model min_contours[%d].size()=%d\n", 0, model_convex_contours[0].size());
//绘制轮廓,方便观察
Mat compare_contours_img = Mat::zeros(mask.size(), CV_8UC3);//定义比较图
//参数:(图,轮廓,轮廓下标位置,颜色,线宽)
drawContours(compare_contours_img, convex_contours, 0, Scalar(0, 0, 255), 1);
drawContours(compare_contours_img, model_convex_contours, 0, Scalar(0, 255, 0), 1);
//imshow("compare_contours_img", compare_contours_img);
//2.3形状比较,完全相同返回0,最大值为1,参数:(轮廓1,轮廓2,轮廓匹配表示(CV_CONTOURS_MATCH_I1/I2/I3),0)
double score = matchShapes(model_convex_contours[0], convex_contours[0], CV_CONTOURS_MATCH_I1, 0);
cout << "score=" << score << endl;//打印提示
//判断得分
if (score > 0.10) {//大于10%返回true
//putText(compare_contours_img,"alien",Point(20,20),CV_FONT_NORMAL,0.5,Scalar(0,0,255),1);//添加文字显示
//imshow("compare_contours_img", compare_contours_img);
//给定值
data.classification = 1;//给定异形类别
data.height = local_data.height;//高
data.width = local_data.width;//宽
data.area = local_data.area;//面积
return true;//返回真
}
else {//否则返回假
//putText(compare_contours_img, "normal", Point(20, 20), CV_FONT_NORMAL, 0.5, Scalar(0, 255, 0), 1);//添加文字显示
//imshow("compare_contours_img", compare_contours_img);
return false;//返回假
}
}
bool is_big_and_small_func(Mat& mask,Mydata& data) {//判断大小头函数
//imshow("is_big_and_small_func",mask);
//发现轮廓
vector<vector<Point>> contours;//轮廓向量
vector<Vec4i> hierarchy;//拓扑结构向量
//参数:(二值化图,轮廓向量,拓扑结构向量,发现方法,检测方法,偏移量点)
findContours(mask,contours,hierarchy,RETR_EXTERNAL,CHAIN_APPROX_NONE,Point(0,0));
//获得斜矩阵
RotatedRect rotaterect = minAreaRect(contours[0]);
//给定本地数据,宽高认为调整一下,以便显示正常
Mydata Local_data;//定义本地数据
Local_data.height = (rotaterect.size.height>rotaterect.size.width? rotaterect.size.height: rotaterect.size.width);//赋值高
Local_data.width = (rotaterect.size.height>rotaterect.size.width ? rotaterect.size.width : rotaterect.size.height);//赋值宽
Local_data.area = contourArea(contours[0]);//赋值面积
//更改斜矩阵大小,获得25%和75%的直线
int height = rotaterect.size.height;//获得高
int width= rotaterect.size.width;//获得宽
//cout << "width=" << width << endl;
//cout << "height=" << height << endl;
RotatedRect rrect_25_75 = rotaterect;//25%,75%
RotatedRect rrect = rotaterect;//50%
Point2f rrect25_75_points[4];//定义点信息
Point2f rrect_points[4];//定义点信息
if (width>height) {//宽大,调宽
//调整25%和75%
rrect_25_75.size.width = width / 2;//高处理
rrect_25_75.points(rrect25_75_points);//获得四个点信息
//50%处理
rrect.size.width = 1;//高处理
rrect.points(rrect_points);//获得四个点信息
}
else {//否则,高大,调高
//调整25%和75%
rrect_25_75.size.height = height / 2;//高处理
rrect_25_75.points(rrect25_75_points);//获得四个点信息
//50%处理
rrect.size.height = 1;//高处理
rrect.points(rrect_points);//获得四个点信息
}
//在新的掩码图中绘制直线
Mat new_mask=mask.clone();//定义图片;
for (int i = 0; i < 4;i++) {//循环点位置
//绘制线,25_75
//line(new_mask, rrect25_75_points[i], rrect25_75_points[(i+1)%4],Scalar(0),1,LINE_AA);
//绘制线,50
line(new_mask,rrect_points[i], rrect_points[(i+1)%4],Scalar(0),5,LINE_AA);
}
//imshow("new_mask", new_mask);
/*//换个思路,先注掉,此思路只对明显大小头有效
//反色获得直线图,并掩码只剩下白色直线
Mat ref_new_mask;
bitwise_not(new_mask,ref_new_mask);//非操作,得到反色图
Mat and_ref_new_mask;
bitwise_and(ref_new_mask,mask,and_ref_new_mask);//与掩码图像,得到纯值线图
//图形学-开操作,取出外侧的零散边缘
Mat element = getStructuringElement(MORPH_RECT,Size(3,3),Point(-1,-1));
morphologyEx(and_ref_new_mask, and_ref_new_mask,MORPH_OPEN,element,Point(-1,-1),1);
imshow("ref_new_mask", and_ref_new_mask);
//霍夫直线获得直线坐标
vector lines;//定义直线向量
//参数:(8位灰度图,vec4f的向量,像素扫描步长,角度扫描步长,交点阈值,最小直线长度,最大长度间隔)
HoughLinesP(and_ref_new_mask,lines,1,CV_PI/180,30,mask.cols/2,10);
//搜索合适的直线
Point2f pointlines[3][2] = {0,0,0,0,0,0};//用于保存最长直线位置,方便后续操作
float lengths[3] = {0,0,0};//用于保存长度的数组,[0]为上线,[1]位置为中线,[2]位置为下线
cout << "lines.size()=" << lines.size() << endl;
Mat hough_img = Mat::zeros(mask.size(),CV_8UC3);
for (int c = 0; c < lines.size();c++) {//循环线
Vec4f hline = lines[c];//获得点前向量
float length = abs(hline[0] - hline[2]) + abs(hline[1] - hline[3]);//计算长度
//判断,hline[1]表示y位置
if (hline[1]>100&& hline[1]<200&&length>lengths[0]) {//上线,且大于最大长度
//保存点向量
pointlines[0][0]=Point(hline[0], hline[1]);//点1
pointlines[0][1] = Point(hline[2], hline[3]);//点2
//保存长度
lengths[0] = length;
}else if (hline[1]>200 && hline[1]<300&&length>lengths[1]) {//中线,且大于最大长度
//保存点向量
pointlines[1][0] = Point(hline[0], hline[1]);//点1
pointlines[1][1] = Point(hline[2], hline[3]);//点2
//保存长度
lengths[1] = length;
}else if (hline[1]>300&&length>lengths[2]) {//下线,且大于最大长度
//保存点向量
pointlines[2][0] = Point(hline[0], hline[1]);//点1
pointlines[2][1] = Point(hline[2], hline[3]);//点2
//保存长度
lengths[2] = length;
}
}
//绘制显示
//画线
line(hough_img, pointlines[0][0], pointlines[0][1], Scalar(0, 0, 255), 1);//上线
line(hough_img, pointlines[1][0], pointlines[1][1], Scalar(0, 0, 255), 1);//中线
line(hough_img, pointlines[2][0], pointlines[2][1], Scalar(0, 0, 255), 1);//下线
printf("Point(%f,%f),Point(%f,%f),length=%f\n", pointlines[0][0].x, pointlines[0][0].y, pointlines[0][1].x, pointlines[0][1].y, lengths[0]);//打印提示,上线
printf("Point(%f,%f),Point(%f,%f),length=%f\n", pointlines[1][0].x, pointlines[1][0].y, pointlines[1][1].x, pointlines[1][1].y, lengths[1]);//打印提示,上线
printf("Point(%f,%f),Point(%f,%f),length=%f\n", pointlines[2][0].x, pointlines[2][0].y, pointlines[2][1].x, pointlines[2][1].y, lengths[2]);//打印提示,上线
imshow("hough_img", hough_img);
//筛选并计算直线长度,c^2=|x1-x2|^2+|y1-y2|^2
//比较长度,当大于N个点时,应给大小头,否则为正常范围内的胶囊
// ----- 1
// ----- 2
// ----- 3
// 大小端的全部条件
// 线1与线3的差值绝对值较大
// 线1与线2的差值绝对值较大
// 线2与线3的差值绝对值较大
*/
//发现轮廓,后面比较上下两部分
vector<vector<Point>> half_contours;//轮廓向量
vector<Vec4i> half_hierarchy;//拓扑结构
//参数:(二值化图,轮廓向量,拓扑结构向量,发现方法,检测方法,偏移量点)
findContours(new_mask,half_contours,half_hierarchy,RETR_LIST,CHAIN_APPROX_NONE,Point(0,0));
cout << "half_contours.size()=" <<half_contours.size()<< endl;
//形状比较, 完全相同返回0,最大值为1,参数:(轮廓1,轮廓2,轮廓匹配表示(CV_CONTOURS_MATCH_I1 / I2 / I3), 0)
double score = matchShapes(half_contours[0],half_contours[1],CV_CONTOURS_MATCH_I3,0);
cout << "is_big_and_small_func:score="<<score<< endl;
//判断
if (score > 0.085) {//当得分大于0.085时,返回真,表示大小头,此值需要按实际调整
//给定数据
data.classification = 2;//赋值分类器,2为大小端
data.height = Local_data.height;//赋值高
data.width = Local_data.width;//赋值宽
data.area = Local_data.area;//赋值面积
return true;
}
else {//否则
return false;//返回假
}
}
bool is_bobble_and_blackP_func(Mat& capsule,Mat& mask,Mydata& data) {//判气泡和黑点函数,绘制问题位置
//获得轮廓原图
Mat capsule_org;//定义胶囊图
capsule.copyTo(capsule_org, mask);//掩码拷贝图
//imshow("is_bobble_and_blackP_func:capsule_org", capsule_org);
//灰度图
Mat gray;
cvtColor(capsule_org,gray,CV_BGR2GRAY);
//二值化
Mat binary;//二值化图
//threshold(gray,binary,130,255,THRESH_BINARY_INV);//二值化
Canny(capsule,binary,150,230,3,true);//canny
//imshow("is_bobble_and_blackP_func:binary",binary);
//掩码图片
Mat new_binary;
binary.copyTo(new_binary,mask);
//膨胀处理,使位置更明显
Mat element = getStructuringElement(MORPH_RECT,Size(3,3),Point(-1,-1));//定义结构元素,参数:(形状,核尺寸,锚点)
morphologyEx(new_binary,new_binary,MORPH_DILATE,element,Point(-1,-1),1);//膨胀处理,参数:(图,目标图,操作标记,核元素,锚点,迭代次数)
//imshow("binary_mask",new_binary);
//1.发现轮廓
vector<vector<Point>> capsule_contours;//轮廓向量
vector<Vec4i> capsule_hierarchy;//拓扑结构变量
//参数:(二值化图,轮廓向量,拓扑结构变量,检测方法,发现方法,偏移量)
findContours(new_binary, capsule_contours, capsule_hierarchy, RETR_EXTERNAL, CHAIN_APPROX_NONE, Point(-1, -1));
//计算总面积
double total_area = 0;//白区面积
vector<vector<Point>> capsule_save;//胶囊子轮廓保存
int bubble_num=0;//气泡数量变量
int black_num = 0;//黑点数量变量
for (int c = 0; c < capsule_contours.size(); c++) {//循环胶囊轮廓
double area = contourArea(capsule_contours[c])/10;//计算面积
//cout << "area/10=" <
if (area > 7) {//气泡
bubble_num++;//气泡数量变量加1
//绘制轮廓
drawContours(capsule, capsule_contours, c, Scalar(0,0, 255), 1);
//面积累加
total_area += area;
}else if(area>=3){//噪点
black_num ++;//黑点数量变量
//绘制轮廓
drawContours(capsule, capsule_contours, c, Scalar(255, 0, 255), 1);
//面积累加
total_area += area;
}
}
//imshow("contours_img", capsule);
//判断返回
if (bubble_num>=1 || black_num >=1) {//当子轮廓数量有时
//给定数据
data.classification = (bubble_num > 0 ? 3 : 4);//当气泡数量大于0时,为气泡,否则为黑点
data.area = total_area;//赋值面积
return true;//返回真
}else {//否则
return false;//返回假
}
}
Mydata capsule_processing_funciton(Mat& capsule, Mat& mask, Mat&small_mask) {//胶囊处理函数,返回胶囊所需数据
//定义自定义结构体变量
Mydata data;
//初始化结构体变量
data.classification = -1;
data.height = -1;
data.width = -1;
data.area = -1;
//1判空
if (is_empty_func(mask)) {
data.classification = 5;//分类赋值,5为空
data.height = 0;//高为0
data.width = 0;//宽为0
}else{//2.否则
//判断
if (is_alien_func(mask,data)) {//2.1判异形
//打印提示
cout << "capsule_processing_funciton:alien shape"<< endl;
}else if (is_big_and_small_func(mask,data)) {//2.2判大小端
//打印提示
cout << "capsule_processing_funciton:big and small shape" << endl;
}else if (is_bobble_and_blackP_func(capsule, small_mask,data)) {//2.3判气泡或黑点,如果是,那么问题位置由函数绘制上去
cout << "capsule_processing_funciton:bobble or blackPoint" << endl;
//计算掩码的宽高
//发现轮廓
vector<vector<Point>> contours;//轮廓
vector<Vec4i> hierarchy;//拓扑结构变量
//参数:(二值化图,轮廓向量,检测方法,发现方法,偏移点),偏移点用于大图还原,此处未用到
findContours(mask, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_NONE, Point(0, 0));//发现轮廓
//获得最小斜矩形
RotatedRect rrect = minAreaRect(contours[0]);
//赋值数据,为何防止出现宽高显示不对的情况,人为调整一下
data.height = (rrect.size.height>rrect.size.width? rrect.size.height:rrect.size.width);//高
data.width = (rrect.size.height>rrect.size.width ? rrect.size.width :rrect.size.height);//宽
}else {//否则为正常胶囊
cout << "capsule_processing_funciton:normal capsule" << endl;
//计算掩码的宽高
//发现轮廓
vector<vector<Point>> contours;//轮廓
vector<Vec4i> hierarchy;//拓扑结构变量
//参数:(二值化图,轮廓向量,检测方法,发现方法,偏移点),偏移点用于大图还原,此处未用到
findContours(mask, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_NONE, Point(0, 0));//发现轮廓
//获得最小斜矩形
RotatedRect rrect = minAreaRect(contours[0]);
//赋值数据,为何防止出现宽高显示不对的情况,人为调整一下
data.height = (rrect.size.height>rrect.size.width ? rrect.size.height : rrect.size.width);//高
data.width = (rrect.size.height>rrect.size.width ? rrect.size.width : rrect.size.height);//宽
data.classification = 0;//赋值类型,0为好品
data.area = 0;//面积给0
}
}
return data;//返回数据
}
void drawCapsule_function(Mat& capsule,Mat&blk_mask, Mat& mask, Mat& small_mask,Mydata& data) {//绘制胶囊掩码
//发现大轮廓
vector<vector<Point>> big_contours;//大轮廓向量
vector<Vec4i> big_hierarchy;//大拓扑结构向量
//参数:(二值化图,轮廓向量,检测方法,发现方法,偏移点),偏移点用于大图还原,此处未用到
findContours(mask,big_contours,big_hierarchy,RETR_EXTERNAL,CHAIN_APPROX_NONE,Point(-1,-1));
//发现小轮廓
vector<vector<Point>> small_contours;//小轮廓向量
vector<Vec4i> small_hierarchy;//小拓扑结构向量
//参数:(二值化图,轮廓向量,检测方法,发现方法,偏移点),偏移点用于大图还原,此处未用到
findContours(small_mask,small_contours,big_hierarchy,RETR_EXTERNAL,CHAIN_APPROX_NONE,Point(-1,-1));
//根据分类,绘制内轮廓和外轮廓
if (data.classification >= 0 && data.classification<=4) {//好品(0)获得其他1-4的情况
//绘制轮廓,好品(0)绘制绿色,1-4情况绘制红色
drawContours(capsule,big_contours,-1,(data.classification==0? Scalar(0, 255, 0): Scalar(0, 0, 255)),1,LINE_AA,big_hierarchy);
}else if (data.classification == 5) {//空(5)
//反色掩码处理,方便获得轮廓
Mat ref_blk_mask;
bitwise_not(blk_mask,ref_blk_mask);
//发现大轮廓
vector<vector<Point>> ref_blk_mask_contours;//大轮廓向量
vector<Vec4i> ref_blk_mask_hierarchy;//大拓扑结构向量
//参数:(二值化图,轮廓向量,检测方法,发现方法,偏移点),偏移点用于大图还原,此处未用到
findContours(ref_blk_mask, ref_blk_mask_contours, ref_blk_mask_hierarchy, RETR_EXTERNAL, CHAIN_APPROX_NONE, Point(-1, -1));
//绘制轮廓,白色
drawContours(capsule, ref_blk_mask_contours, -1, Scalar(255, 255, 255), 1, LINE_AA, ref_blk_mask_hierarchy);
}else {//坏品(-1)
//绘制轮廓,红色
drawContours(capsule, big_contours, -1, Scalar(0, 0, 255), 1, LINE_AA, big_hierarchy);
}
//绘制内轮廓
if (data.classification!=5) {//判断不为空
//绘制内轮廓,蓝色(直线)
//drawContours(capsule,small_contours,-1,Scalar(255,0,0),1,LINE_AA,small_hierarchy);
//画虚线(自定义函数)
drawDashContours(capsule, small_contours, 0, Scalar(255, 0, 0),1,LINE_AA,5);
}
}
void putText_function(Mat& src, Rect&pos, Mydata& data) {//在图片上写文本
//判空
if (src.empty()) {//图为空
return;//返回
}
//定义对应类别数组
char* class_name[] = {"bad","good","alien","big_and_small","bubble","black point","empty"};
//放入文字-类别
//参数:(图,文本,原点,字体,缩放比,颜色,线宽,线型,底左对齐(倒过来的效果))
putText(src,format("%d(%s)",data.classification,class_name[data.classification+1]),Point(pos.x+pos.width/2,pos.y),CV_FONT_NORMAL,0.5,Scalar(255,0,0),1,LINE_AA,false);
if(data.classification!=5){//当分类不等于空(5)时,绘制文字
//放入文字-宽
putText(src,format("Width:%d",data.width),Point(pos.x-pos.width/5,pos.y+20),CV_FONT_NORMAL,0.5,Scalar(0,255,0),1,LINE_AA,false);
//放入文字-高
putText(src,format("Height:%d",data.height),Point(pos.x-pos.width/5,pos.y+40),CV_FONT_NORMAL,0.5,Scalar(0,255,0),1,LINE_AA,false);
//放入文字-面积
putText(src,format("Area:%.2f",data.area),Point(pos.x-pos.width/5,pos.y+60),CV_FONT_NORMAL,0.5,((data.area>0)?Scalar(0,0,255): Scalar(0, 255, 0)),1,LINE_AA,false);
}
}
void drawDashContours(InputOutputArray image, InputArrayOfArrays contours,
int contourIdx, const Scalar& color,int thinkness=1,int lineType=8, int gap_threshold=10) {//自定义函数,实现画虚线轮廓
//获得图像(引用关系)
Mat img = image.getMat();
//定义并获取轮廓总个数
int ncontours = (int)contours.total();
//当轮廓总数量为0或指定轮廓数字大于(总轮廓数-1)时直接返回
if (ncontours==0|| contourIdx>(ncontours-1)) {
return;
}
//定义存储区
AutoBuffer<Point*> _ptsptr(ncontours);//用于存储点的双指针,大小与轮廓数一致
AutoBuffer<int> _npts(ncontours);//用于存储每个轮廓点的个数的单指针,大小与轮廓数一致
Point** ptsptr= _ptsptr;//获得双指针,方便后面添加数据
int* npts= _npts;//获得单指针,方便后面添加数据
//判断轮廓标记
if (contourIdx <0) {//当轮廓号为-1时,循环处理
for (int i = 0; i < ncontours; i++) {//循环轮廓位置
Mat p = contours.getMat(i);//循环获取矩阵(引用关系)
CV_Assert(p.checkVector(2,CV_32S)>=0);//检查向量个数,有条件为假,断言判断报错,checkVector参数:(通道数,数据深度)返回向量个数,CV_Assert参数:(条件)
ptsptr[i]=p.ptr<Point>();//赋值当前轮廓,获得点指针
npts[i] = p.rows*p.cols*p.channels() / 2;//赋值点数量,行*列*通道数/2
}
}else {//当轮廓不为-1时,指定位置处理
Mat p = contours.getMat(contourIdx);
CV_Assert(p.checkVector(2, CV_32S) >= 0);//检查向量个数,有条件为假,断言判断报错,checkVector参数:(通道数,数据深度),CV_Assert参数:(条件)
ptsptr[contourIdx] = p.ptr<Point>();//赋值当前轮廓,获得点指针
npts[contourIdx] = p.rows*p.cols*p.channels() / 2;//赋值点数量,行*列*通道数/2
}
//循环轮廓数组指针位置和每个轮廓的点个数
int threshold = (gap_threshold<0||gap_threshold>15)?10:gap_threshold;//定义间隔阈值,当阈值在0-15之间时使用阈值,否则给固定阈值10
int counter_threshold[2] = {0,0};//阈值计数器,用于统计当前是否转化状态,[0]画线统计,[1]空统计
for (int i = 0; i < sizeof(ptsptr);i++ ) {//循环外层指针位置
for (int j = 0; j <npts[i];j++) {//循环此层的点个数
//判断间隔
if(counter_threshold[0]<threshold){//当[0]位置统计数小于阈值时,画线
//画线,参数:(图,点1,点2,颜色,线宽,线型)
line(img, ptsptr[i][j], ptsptr[i][(j+1)% npts[i]], color,thinkness,lineType);//%取余为了确保数据范围
counter_threshold[0]++;//当前状态阈值加1
counter_threshold[1] = 0;//空阈值为0
}else if(counter_threshold[1]<threshold){//当[1]位置统计数小于阈值时,计数
counter_threshold[1]++;//空阈值阈值加1
}else {//否则[0]位置计数统计归0,继续画线
counter_threshold[0]=0;//当前状态阈值为0
//画线,参数:(图,点1,点2,颜色,线宽,线型)
line(img, ptsptr[i][j], ptsptr[i][(j + 1) % npts[i]], color,thinkness,lineType);//%取余为了确保数据范围
counter_threshold[0]++;//当前状态阈值加1
counter_threshold[1] = 0;//空阈值为0
}
}
}
}
好的样本模型抠图(goodmodel.bmp):
实现部分效果:
主要思想:
1.获得胶囊本身。(重点)
2.对胶囊进行各种检测。(重点)
3.对每组胶囊的状态进行组合,判断给出结果。
4.给后台或显示结果。
细节说明:
1.对于胶囊提取:我们需要使用掩码、颜色范围、二值化三个部分完成操作。原因很简单,单个算法是无法完成总胶囊提取的。
2.对于胶囊检测来说,按照不同检测内容,使用不同的方法。其中异型使用凸包+形状比较得分处理,大小端使用轮廓斜矩阵切半+上下两部分形状比较得分处理,气泡和黑点使用Canny(边缘提取)+MorphologyEx(图像学)+findContours(轮廓)完成不同面积的抽取,再进行筛选。
3.对于胶囊外部的虚线如何实现:使用轮廓向量(多个点形成的完整轮廓),自己实现绘制轮廓功能,因为openCV没有给出画虚线的直接方法。实现思路就是间隔几个点绘制线,再间隔几个点不绘线,虚线就形成了。
以上程序和说明都是实际测试过的。
需要再优化的内容:
1.对于胶囊边缘的提取,如何更好的去噪。
2.对于检测点的提取,如何更好。
我是Simon, 在这里期待与您的交流。