引言
记录两个基于二值图像分析的较为经典的例子,希望能够得到更多的启发,从而想到更好的解决类似问题的思路。
01
问题一:寻找靶心
仔细观察上图,可以看到两个最直接的是靶心有十字交叉线,而在OpenCV形态学处理中,支持十字交叉结构元素,所以我们可以先检测两条线,然后获取十字交叉结构,最后对结构进行轮廓分析,获取中心点,即可获得最终的靶心位置,最终寻找到的靶心位置。
opencv实现:
Mat src = imread("D:/opencv练习图片/寻找靶心.jpg");
imshow("原图", src);
Mat gray,binary,hline,vline;
cvtColor(src, gray, COLOR_RGB2GRAY);
//二值化
threshold(gray, binary, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);
imshow("二值化", binary);
//形态学处理
Mat kernel1 = getStructuringElement(MORPH_RECT, Size(70, 1), Point(-1, -1));
Mat kernel2 = getStructuringElement(MORPH_RECT, Size(1, 50), Point(-1, -1));
morphologyEx(binary, hline, MORPH_OPEN, kernel1, Point(-1, -1));
morphologyEx(binary, vline, MORPH_OPEN, kernel2, Point(-1, -1));
vector> contours;
findContours(hline, contours, RETR_EXTERNAL, CHAIN_APPROX_NONE, Point());
Mat mask = Mat::zeros(hline.size(), CV_8U);
int max = 0;
int index = 0;
for (int i = 0; i < contours.size(); i++)
{
double area = contourArea(contours[i]);
if (area > max)
{
max = area;
index = i;
cout << index << endl;
drawContours(mask, contours, index, Scalar(255, 255, 255), -1, 8);
}
}
imshow("h", mask);
vector> contours1;
findContours(vline, contours1, RETR_EXTERNAL, CHAIN_APPROX_NONE, Point());
int max1 = 0;
int index1 = 0;
for (int i = 0; i < contours1.size(); i++)
{
double area = contourArea(contours1[i]);
if (area > max1)
{
max1 = area;
index1 = i;
drawContours(mask, contours1, index1, Scalar(255, 255, 255), -1, 8);
}
}
imshow("提取十字", mask);
Mat kernel3 = getStructuringElement(MORPH_CROSS, Size(13, 13), Point(-1, -1));
morphologyEx(mask, mask, MORPH_OPEN, kernel3, Point(-1, -1));
imshow("交点", mask);
vector> contours3;
findContours(mask, contours3, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, Point());
RotatedRect rect = minAreaRect(contours3[0]);
Point center = rect.center;
circle(src, center, 4, Scalar(0, 0, 255), 1, 8);
imshow("结果", src);
02
问题二:计数并寻找其中的缺失点
仔细分析图像发现,中间都毫无另外的有个白色很亮的圆圈。
因此我们可以通过二值图像分析来提取 + 轮廓分析来提取到这些点,得到这些轮廓点之后通过分析整个轮廓区域得到倾斜角度,进行纠偏,然后通过X与Y投影进行分割,得到每个零件的中心位置坐标,根据每一行的间隔设置阈值,从而实现缺少部分部分的标出与件数统计
opencv分析:
(一)读入图像,预处理(形态学梯度,二值化)
Mat srcImage = imread("D:/opencv练习图片/工件计数.jpg");
namedWindow("原始图");
imshow("原始图", srcImage);
Mat grayImage;
cvtColor(srcImage, grayImage, COLOR_RGB2GRAY);
Mat kernal = getStructuringElement(MORPH_RECT, Size(3, 3));
Mat gradientImage;
morphologyEx(grayImage, gradientImage, MORPH_GRADIENT, kernal);
namedWindow("gradientImage形态学梯度");
imshow("gradientImage形态学梯度", gradientImage);
Mat thresholdImage;
threshold(gradientImage, thresholdImage, 0, 255, THRESH_OTSU);
namedWindow("二值化OTSU图");
imshow("二值化OTSU图", thresholdImage);
(二)再次形态学(保留圆形区域)
kernal = getStructuringElement(MORPH_ELLIPSE, Size(5, 5));
Mat openImage, closeImage;
morphologyEx(thresholdImage, openImage, MORPH_OPEN, kernal);
imshow("openImage", openImage);
kernal = getStructuringElement(MORPH_ELLIPSE, Size(10, 10));
morphologyEx(openImage, closeImage, MORPH_CLOSE, kernal);
imshow("closeImage", closeImage);
开运算:分割区域 闭运算:填充孔洞
(三)寻找轮廓并筛选,最后提取点
vector > contours;
vector hierarchy;
findContours(closeImage, contours, hierarchy, RETR_EXTERNAL,CHAIN_APPROX_SIMPLE);
Mat resultImage = Mat::zeros(grayImage.rows, grayImage.cols, grayImage.type());
int total = 0;//计数值
for (int i = 0; i < contours.size(); i++)
{
double area = contourArea(contours[i]);
if (area < 55) continue;//面积大于55的继续执行
total++;//计算有多少面积大于55的个数(工件数)
RotatedRect rect = minAreaRect(contours[i]);
circle(resultImage, rect.center, 5, Scalar(255), -1);//用圆显示
}
imshow("提取点", resultImage);
(四)对点区域求最小外接矩形,计算角度并矫正
vector pts;
for (int i = 0; i < resultImage.rows; i++)
{
for (int j = 0; j < resultImage.cols; j++)
{
if (resultImage.ptr(i)[j] == 255)
{
pts.push_back(Point(j, i));
}
}
}
RotatedRect rect = minAreaRect(pts);
Point2f rectVertex[4];
rect.points(rectVertex);
for (int i = 0; i < 4; i++)
{
putText(resultImage, to_string(i), rectVertex[i], FONT_HERSHEY_SIMPLEX, 1.0, Scalar(200));
line(resultImage, rectVertex[i], rectVertex[(i + 1) % 4], Scalar(100), 2, 8);
}
cout << "角度为:" << rect.angle;
cout << "宽度为:" << rect.size.width;
cout << "高度为:" << rect.size.height;
namedWindow("resultImage");
imshow("最小外接矩形", resultImage);
float angle = rect.angle - 90;
Point center = rect.center;
//计算旋转后的画布大小,并将旋转中心平移到新的旋转中心
Rect bbox = RotatedRect(center, Size(srcImage.cols, srcImage.rows), angle).boundingRect();
Mat matrix = getRotationMatrix2D(rect.center, angle, 1);
matrix.at(0, 2) += bbox.width / 2.0 - center.x;
matrix.at(1, 2) += bbox.height / 2.0 - center.y;
warpAffine(resultImage, resultImage, matrix, resultImage.size());
warpAffine(srcImage, srcImage, matrix, srcImage.size());
imshow("矫正原图", srcImage);
imshow("矫正二值图", resultImage);
注意:
再通过RotatedRect类获得的矩形角度angle,在计算旋转矩阵时,应该再减去90度,才是旋转角度。
(五)构造查找近邻函数Y_projection()
void Y_projection(Mat &warp, Mat &src, int max_gap, int &first, int &end)
{
vector y_bins;
for (int i = 0; i < warp.cols; i++) {
int found_y = 0;
for (int j = first; j < end; j++) {
if (warp.at(j, i) == 255) {
found_y += 1;
}
}
if (found_y > 0) {
y_bins.push_back(i);
}
}
vector y_tbins;
for (int i = 0; i < y_bins.size() - 1; i++)
{
int gap = y_bins[i + 1] - y_bins[i];
if (gap >= 15) {
y_tbins.push_back(y_bins[i + 1] - (gap / 2));
}
if (gap >= 50) {
circle(src, Point(y_bins[i + 1] - (gap / 2), (end - (end - first) / 2)), 5, Scalar(0, 255, 255), -1);
}
}
}
(六)缺失排查,并显示结果
//计算每行工具所在的行数
vector bins;
vector tbins;
for (int i = 0; i < resultImage.rows; i++) {
int found = 0;
for (int j = 0; j < resultImage.cols; j++) {
if (resultImage.at(i, j) == 255) {
found += 1;
}
}
if (found > 0) {
bins.push_back(i);
}
}
for (int i = 0; i < (bins.size() - 1); i++) {
int gap = bins[i + 1] - bins[i];
if (gap >= 15) {
cout << "tbins: " << bins[i + 1] - (gap / 2) << endl;
tbins.push_back(bins[i + 1] - (gap / 2));
}
}
//逐行排查缺失工具的位置
Mat dstImage;
srcImage.copyTo(dstImage);
int h = resultImage.rows - 1;
for (int i = 0; i < tbins.size(); i++) {
if (i == 0)
{
//第一排工具所占行数从0到第一个位置
Y_projection(resultImage, dstImage, 50, i, tbins[i]);
}
else if (i == (tbins.size() - 1))
{
//最后一排工具所占行数从最后一个位置到图片最后一行
Y_projection(resultImage, dstImage, 50, tbins[i], h);
}
else
{
//中间行工具所占行数为上下两个位置之间
int end = tbins[i] - 1;
Y_projection(resultImage, dstImage, 50, tbins[i - 1], end);
}
}
putText(dstImage, "numbers: " + to_string(total), Point(50, 50), FONT_HERSHEY_SIMPLEX, 1.0, Scalar(0, 0, 255));
imshow("dstImage", dstImage);
waitKey(0);
摘录于:Opencv学堂(自己用c++实现)