基于机器视觉的玻璃缺陷检测算法研究与设计
摘要
本文研究并设计了一种基于机器视觉的玻璃缺陷检测算法,利用OpenCV库实现玻璃镜面缺陷的检测、缺陷信息的提取和标记。本文首先介绍了系统环境和依赖库的配置,然后详细描述了检测算法的设计与实现过程,最后通过实验验证了算法的有效性。
关键词
机器视觉, 玻璃缺陷检测, OpenCV, 图像处理, 连通区域
完整资料下载地址:
https://docs.qq.com/doc/p/ffc421513a628b30450ae742547c0b850456d7c8
1. 引言
玻璃缺陷检测在工业生产中具有重要意义。传统的人工检测方法效率低下且易受人为因素影响,而基于机器视觉的自动检测系统能够提高检测速度和准确性。本文设计了一种基于OpenCV的玻璃缺陷检测算法,通过图像处理技术实现缺陷的检测与标记。
2. 系统环境与配置
2.1 系统环境
- 操作系统:Windows 10
- 集成开发环境:Visual Studio 2015
- 图像处理库:OpenCV 3.0
2.2 环境配置
本文使用OpenCV 3.0进行图像处理,具体的环境配置可以通过百度搜索相关教程进行设置。
3. 算法设计
3.1 变量定义
// 定义灰度图像变量
IplImage *g_GrayImage = NULL;
// 定义二值化图片变量
IplImage *g_BinaryImage = NULL;
// 定义二值化窗口标题
const char *WindowBinaryTitle = "二值化图片";
// 创建源图像窗口标题变量
const char *WindowSrcTitle = "灰度图像";
// 创建滑块标题变量
const char *TheSliderTitle = "二值化阀值";
const char *SrcPath = "3.jpg"; // 定义图片路径
IplImage *g_pGrayImage_liantong = NULL;
IplImage *g_pBinralyImage_liantong = NULL;
int contour_num = 0; // 数字编号
char number_buf[10]; // 数字编号存入数组,puttext
#define num_col 11 // 二维数组的列,每一个点缺陷信息的详细信息
long int liantong_all_area = 0; // 连通区域总面积
long int Rect_all_area = 0; // 保存最小外接矩形总的面积
3.2 结构体定义
struct my_struct1 {
double scale; // 定义显示图像的比例
const int threshold_value_binaryzation; // 定义第一次二值化阀值
const int threshold_value_second_binaryzation; // 定义第二次二值化阀值
};
my_struct1 picture = {0.3, 50, 100};
struct my_struct2 {
int Model1_k1; // 图像膨胀腐蚀
int Model1_k2; // 图像膨胀腐蚀
int Model2_k1; // 图像膨胀腐蚀
int Model2_k2; // 图像膨胀腐蚀
};
my_struct2 value = {5, 2, 3, 2};
struct my_struct3 {
double maxarea; // 最大缺陷面积
double minarea; // 最小显示保留的缺陷面积
double font_scale; // 字体大小
int font_thickness; // 字体粗细
const int Feature_value2_number; // 定义一个二维数组的列,即缺陷的个数
};
my_struct3 value2 = {0, 4, 0.6, 0.8, 100};
struct my_struct4 {
const int hough_Canny_thresh1;
const int hough_Canny_thresh2;
const int hough_Canny_kernel;
const int cvHoughLines2_thresh; // 像素值大于多少才显示,值越大,显示的线段越少
const int cvHoughLines2_param1; // 显示线段的最小长度
const int cvHoughLines2_param2; // 线段之间的最小间隔
};
my_struct4 Hough = {50, 100, 3, 50, 20, 10};
3.3 图像处理与缺陷检测
3.3.1 图像预处理
int **on_trackbar() {
CvSeq *contour = 0;
CvSeq *_contour = contour;
// 定义存放数组的二维数组,返回指针数组
int **Feature_value2 = 0;
Feature_value2 = new int *[value2.Feature_value2_number];
IplImage *SrcImage_or;
CvSize src_sz;
// 载入原图
IplImage *SrcImage_origin = cvLoadImage(SrcPath, CV_LOAD_IMAGE_UNCHANGED);
// resize
src_sz.width = SrcImage_origin->width * picture.scale;
src_sz.height = SrcImage_origin->height * picture.scale;
SrcImage_or = cvCreateImage(src_sz, SrcImage_origin->depth, SrcImage_origin->nChannels);
cvResize(SrcImage_origin, SrcImage_or, CV_INTER_CUBIC);
cvNamedWindow("原图", CV_WINDOW_AUTOSIZE);
cvShowImage("原图", SrcImage_origin);
// 单通道灰度化处理
g_GrayImage = cvCreateImage(cvSize(SrcImage_or->width, SrcImage_or->height), IPL_DEPTH_8U, 1);
cvCvtColor(SrcImage_or, g_GrayImage, CV_BGR2GRAY);
// 创建二值化原图
g_BinaryImage = cvCreateImage(cvGetSize(g_GrayImage), IPL_DEPTH_8U, 1);
cvThreshold(g_GrayImage, g_BinaryImage, picture.threshold_value_binaryzation, 255, CV_THRESH_BINARY);
cvShowImage(WindowBinaryTitle, g_BinaryImage);
3.3.2 图像膨胀腐蚀
g_BinaryImage = cvCloneImage(g_BinaryImage);
IplImage *temp_cvDilate = cvCreateImage(cvGetSize(g_BinaryImage), IPL_DEPTH_8U, 1);
IplImage *temp_cvErode = cvCreateImage(cvGetSize(g_BinaryImage), IPL_DEPTH_8U, 1);
IplImage *temp_cvErode_cvErode = cvCreateImage(cvGetSize(g_BinaryImage), IPL_DEPTH_8U, 1);
IplConvKernel *myModel1;
myModel1 = cvCreateStructuringElementEx(value.Model1_k1, value.Model1_k1, value.Model1_k2, value.Model1_k2, CV_SHAPE_ELLIPSE);
IplConvKernel *myModel2;
myModel2 = cvCreateStructuringElementEx(value.Model2_k1, value.Model2_k1, value.Model2_k2, value.Model2_k2, CV_SHAPE_RECT);
cvDilate(g_BinaryImage, temp_cvDilate, myModel1, 1);
cvErode(temp_cvDilate, temp_cvErode_cvErode, myModel2, 1);
namedWindow("膨胀腐蚀", CV_WINDOW_AUTOSIZE);
cvShowImage("膨胀腐蚀", temp_cvErode_cvErode);
g_BinaryImage = cvCloneImage(temp_cvErode_cvErode);
3.3.3 连通区域检测
CvMemStorage *liantong_storage = cvCreateMemStorage();
IplImage *liantogn_dst = cvCreateImage(cvGetSize(g_BinaryImage), 8, 3);
cvFindContours(g_BinaryImage, liantong_storage, &contour, sizeof(CvContour), CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE);
cvZero(liantogn_dst);
int n = -1, m = 0;
for (; contour != 0; contour = contour->h_next) {
double tmparea = fabs(cvContourArea(contour));
if (tmparea <= value2.minarea) {
cvSeqRemove(contour, 0);
continue;
} else {
liantong_all_area = liantong_all_area + tmparea;
}
CvRect aRect = cvBoundingRect(contour, 0);
if (tmparea > value2.maxarea) {
value2.maxarea = tmparea;
n = m;
}
m++;
CvScalar color = CV_RGB(0, 255, 255);
cvDrawContours(liantogn_dst, contour, color, color, -1, -1, 8);
}
long int sizeof_pic = liantogn_dst->width * liantogn_dst->height;
cvNamedWindow("连通区域", 1);
cvShowImage("连通区域", liantogn_dst);
3.3.4 缺陷标记与统计
IplImage *label_liantogn_dst_origin = cvCloneImage(liantogn_dst);
Mat label_liantogn_dst = cvarrToMat(label_liantogn_dst_origin);
IplImage *g_BinaryImage_fanse_origin = NULL;
g_pGrayImage_liantong = cvCreateImage(cvGetSize(liantogn_dst), IPL_DEPTH_8U, 1);
cvCvtColor(liantogn_dst, g_pGrayImage_liantong, CV_BGR2GRAY);
g_pBinralyImage_liantong = cvCreateImage(cvGetSize(g_pGrayImage_liantong), IPL_DEPTH_8U, 1);
cvThreshold(g_pGrayImage_liantong, g_pBinralyImage_liantong, picture.threshold_value_second_binaryzation, 255, CV_THRESH_BINARY);
cvNamedWindow("连通二值化", CV_WINDOW_AUTOSIZE);
cvShowImage("连通二值化", g_pBinralyImage_liantong);
Mat g_pBinralyImage_liantong_2 = cvarrToMat(g_pBinralyImage_liantong);
IplImage *fanse_origin = cvCloneImage(g_pBinralyImage_liantong);
CvMemStorage *storage = cvCreateMemStorage();
CvSeq *seq = NULL;
int cnt = cvFindContours(g_pBinralyImage_liantong, storage, &seq);
seq = seq->h_next;
double length = cvArcLength(seq);
double area = cvContourArea(seq);
CvRect rect = cvBoundingRect(seq, 1);
CvBox2D box = cvMinAreaRect2(seq, NULL);
IplImage *dst_min_rec = cvLoadImage(SrcPath, 1);
CvSize sz;
sz.width = dst_min_rec->width * picture.scale;
sz.height = dst_min_rec->height * picture.scale;
IplImage *SrcImage = cvCreateImage(sz, dst_min_rec->depth, dst_min_rec->nChannels);
cvResize(dst_min_rec, SrcImage, CV_INTER_CUBIC);
dst_min_rec = cvCloneImage(SrcImage);
cvFindContours(g_pBinralyImage_liantong, storage, &contour, sizeof(CvContour), CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE);
for (; contour != 0; contour = contour->h_next) {
CvBox2D rect = cvMinAreaRect2(contour, storage);
CvPoint2D32f rect_pts0[4];
cvBoxPoints(rect, rect_pts0);
int npts = 4, k = 0;
int aaa = 0, bbb = 0;
CvPoint rect_pts[4], *pt = rect_pts;
int sum_rect_x = 0, sum_rect_y = 0;
Feature_value2[contour_num] = new int[num_col];
for (int i = 0; i < 4; i++) {
rect_pts[i] = cvPointFrom32f(rect_pts0[i]);
Feature_value2[contour_num][i] = rect_pts[i].x;
Feature_value2[contour_num][i + 4] = rect_pts[i].y;
sum_rect_x += rect_pts[i].x;
sum_rect_y += rect_pts[i].y;
aaa = (int)sqrt((pow((rect_pts[0].x - rect_pts[1].x), 2) + pow((rect_pts[0].y - rect_pts[1].y), 2)));
bbb = (int)sqrt((pow((rect_pts[0].x - rect_pts[3].x), 2) + pow((rect_pts[0].y - rect_pts[3].y), 2)));
if (aaa < bbb) {
k = aaa;
aaa = bbb;
bbb = k;
}
}
Feature_value2[contour_num][8] = aaa;
Feature_value2[contour_num][9] = bbb;
Feature_value2[contour_num][10] = aaa * bbb;
Rect_all_area = Rect_all_area + aaa * bbb;
int font_face = cv::FONT_HERSHEY_COMPLEX;
cv::Point origin;
origin.x = sum_rect_x / 4;
origin.y = sum_rect_y / 4;
sprintf(number_buf, "%3d", contour_num);
string number_buf_string = number_buf;
putText(label_liantogn_dst, number_buf_string, origin, font_face, value2.font_scale, cv::Scalar(0, 255, 255), value2.font_thickness, 8, 0);
cvPolyLine(dst_min_rec, &pt, &npts, 1, 1, CV_RGB(255, 0, 0), 1);
contour_num++;
}
cvNamedWindow("label_liantogn_dst_result", CV_WINDOW_AUTOSIZE);
line(label_liantogn_dst, Point(0, dst_min_rec->height * 0.25), Point(dst_min_rec->width, dst_min_rec->height * 0.25), Scalar(89, 90, 90), 1);
line(label_liantogn_dst, Point(0, dst_min_rec->height * 0.5), Point(dst_min_rec->width, dst_min_rec->height * 0.5), Scalar(89, 90, 90), 1);
line(label_liantogn_dst, Point(0, dst_min_rec->height * 0.75), Point(dst_min_rec->width, dst_min_rec->height * 0.75), Scalar(89, 90, 90), 1);
line(label_liantogn_dst, Point(dst_min_rec->width * 0.25, 0), Point(dst_min_rec->width * 0.25, dst_min_rec->height), Scalar(89, 90, 90), 1);
line(label_liantogn_dst, Point(dst_min_rec->width * 0.5, 0), Point(dst_min_rec->width * 0.5, dst_min_rec->height), Scalar(89, 90, 90), 1);
line(label_liantogn_dst, Point(dst_min_rec->width * 0.75, 0), Point(dst_min_rec->width * 0.75, dst_min_rec->height), Scalar(89, 90, 90), 1);
Point p2;
p2.x = 10;
p2.y = 10;
circle(label_liantogn_dst, p2, 5, Scalar(0, 0, 255), -1);
imshow("连通标记", label_liantogn_dst);
printf("连通区域个数:%4d \n", contour_num);
cvNamedWindow("外接矩形", CV_WINDOW_AUTOSIZE);
cvLine(dst_min_rec, cvPoint(0, 50), cvPoint(dst_min_rec->width, 50), CV_RGB(255, 0, 0), 1);
cvShowImage("外接矩形", dst_min_rec);
Mat dst_min_rec_result = cvarrToMat(dst_min_rec);
cvSaveImage("外接矩形.jpg", dst_min_rec);
float temp_percent = 0.0;
printf("连通区域面积:%d\r\n", liantong_all_area);
printf("图像矩形面积:%d\r\n", Rect_all_area);
printf("整副图像面积:%d\r\n", sizeof_pic);
temp_percent = (float)liantong_all_area / sizeof_pic * 100;
printf("缺陷面积占比:%0.2f %%\r\n", temp_percent);
3.3.5 霍夫变换检测直线
IplImage *lines_dst;
IplImage *color_dst;
CvMemStorage *lines_storage = cvCreateMemStorage(0);
CvSeq *lines = 0;
int hough_i;
lines_dst = cvCreateImage(cvGetSize(liantogn_dst), 8, 1);
color_dst = cvCreateImage(cvGetSize(liantogn_dst), 8, 3);
cvCanny(liantogn_dst, lines_dst, Hough.hough_Canny_thresh1, Hough.hough_Canny_thresh2, Hough.hough_Canny_kernel);
cvCvtColor(lines_dst, color_dst, CV_GRAY2BGR);
lines = cvHoughLines2(lines_dst, lines_storage, CV_HOUGH_PROBABILISTIC, 1, CV_PI / 180, Hough.cvHoughLines2_thresh, Hough.cvHoughLines2_param1, Hough.cvHoughLines2_param2);
for (hough_i = 0; hough_i < lines->total; hough_i++) {
CvPoint *line = (CvPoint *)cvGetSeqElem(lines, hough_i);
cvLine(dst_min_rec, line[0], line[1], CV_RGB(0, 255, 255), 3, CV_AA, 0);
}
cvNamedWindow("Hough", 1);
cvShowImage("霍夫变换", dst_min_rec);
cvSaveImage("霍夫变换.jpg", liantogn_dst);