网站制作需要多长时间网站产品推广
**
关于胶囊检测的思考-代码实现
作者:Simon Song
**
先看两张图。我们要实现对生产胶囊的快速检测,有两种方案,一种是DNN方式,一种是openCV方法。为避开大量样本集的问题,我选择的是openCV方式实现胶囊检测。
检测规则如下:
一、三个胶囊不全为空,且检测结果均为好品,该幅图判断为好品。三个胶囊不全为空,有至少一个胶囊为坏品,该幅图判断为坏品。
二、每个胶囊分别检测,顶部蓝色数字代表每个胶囊检测结果:
-1:空
0:好品
其他大于1的数字代表不同缺陷种类,请自行编号。
三、左侧显示检测参数,例如:W宽,H高,DA脏点面积。可按实际需要显示。
四、好品胶囊标注为绿色。坏品胶囊标注为红色,相应错误参数也标注为红色。
代码环境:
VS2005+openCV341+C++
先给出我的实现代码:
#include<iostream>
#include<opencv2/opencv.hpp>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;;//显示文件名//读取图片,参数:(文件名),位置后加1src= 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);//按颜色范围获得黄颜色掩码//转为hsvMat 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<vector<Point>> 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<vector<Point>> 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%返回trueputText(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);//绘制线,50line(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<Vec4f> 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]);//点1pointlines[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]);//点1pointlines[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]);//点1pointlines[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=" <<area<< endl;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;//高为0data.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之间时使用阈值,否则给固定阈值10int 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]++;//当前状态阈值加1counter_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]++;//当前状态阈值加1counter_threshold[1] = 0;//空阈值为0}}}
}
好的样本模型抠图(goodmodel.bmp):
实现部分效果:
主要思想:
1.获得胶囊本身。(重点)
2.对胶囊进行各种检测。(重点)
3.对每组胶囊的状态进行组合,判断给出结果。
4.给后台或显示结果。
细节说明:
1.对于胶囊提取:我们需要使用掩码、颜色范围、二值化三个部分完成操作。原因很简单,单个算法是无法完成总胶囊提取的。
2.对于胶囊检测来说,按照不同检测内容,使用不同的方法。其中异型使用凸包+形状比较得分处理,大小端使用轮廓斜矩阵切半+上下两部分形状比较得分处理,气泡和黑点使用Canny(边缘提取)+MorphologyEx(图像学)+findContours(轮廓)完成不同面积的抽取,再进行筛选。
3.对于胶囊外部的虚线如何实现:使用轮廓向量(多个点形成的完整轮廓),自己实现绘制轮廓功能,因为openCV没有给出画虚线的直接方法。实现思路就是间隔几个点绘制线,再间隔几个点不绘线,虚线就形成了。
以上程序和说明都是实际测试过的。
需要再优化的内容:
1.对于胶囊边缘的提取,如何更好的去噪。
2.对于检测点的提取,如何更好。
我是Simon, 在这里期待与您的交流。