文章目录
- 一、项目简介
- 二、思考步骤
- 1. 图像二值化
- 2. 滤波去噪
- 3. Canny算法检测边缘
- 4. 查找轮廓并计算
- 5. 绘制轮廓并表示质心
- 三、测试结果
- 四、工程代码
一、项目简介
昨天一个同学来问我一个如何利用OpenCV确定图像上标记点坐标的问题。先大概介绍一下光学管道测速吧,主要是利用openmv对运动的管材拍照,同时舵机对管材进行打点,最后面通过计算一系列的计算测定生产线上管材的移动速度。这里面需要解决的是标记点在单位时间内的移动距离,而我们需要确定标记点的坐标,比较两张照片同一个标记点的坐标差即可。
二、思考步骤
1. 图像二值化
首先我们需要对初步采集到的图片进行处理。这里面我们先对其二值化处理,得到二值化输出的图像。接着我们再将管材上面对应的打点区域给截取出来,得到一段合理的可供计算图像
2. 滤波去噪
噪声的存在会影响到边缘的检测,接着将图片进行高斯去噪,高斯去噪其实就是一个低通滤波器,滤除高频噪声
3. Canny算法检测边缘
噪声的存在会影响到边缘的检测,接着将图片进行高斯去噪,高斯去噪其实就是一个低通滤波器,滤除高频噪声C++版:
void Canny(
const CvArr* image,
CvArr* edges,
double threshold1,double threshold2,
int aperture_size=3,
bool L2gradient=false
);
函数说明:
第一个参数表示输入图像,必须为单通道灰度图。
第二个参数表示输出的边缘图像,为单通道黑白图。
第三个参数和第四个参数表示阈值,分别为为滞后性低阈值和滞后性高阈值。这二个阈值中当中的小阈值用来控制边缘连接,大的阈值用来控制强边缘的初始分割。即如果一个像素的梯度大与上限值,则被认为是边缘像素,如果小于下限阈值,则被抛弃。如果该点的梯度在两者之间则当这个点与高于上限值的像素点连接时我们才保留,否则删除。
第五个参数表示Sobel 算子大小,默认为3即表示一个3*3的矩阵。Sobel 算子与高斯拉普拉斯算子都是常用的边缘算子。
第六个参数为计算图像梯度幅值的标识,默认值为false。
这里面我们将低阈值设为30,高阈值设为75。一般推荐的高低阈值的比在2:1~3:1之间。
4. 查找轮廓并计算
先使用findContours()函数找出轮廓,接着利用Moments类计算轮廓的质心
5. 绘制轮廓并表示质心
注意需使用sprintf_s函数,避免内存溢出
三、测试结果
测试平台为 VS2017+ OpenCV3.30
处理图:
轮廓图:
坐标图:
实践如下:
四、工程代码
全部代码如下
#include "pch.h"
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv/cv.hpp>
#include <opencv2/highgui/highgui.hpp>
using namespace cv;
using namespace std;
Mat src;
Mat src_gray;
int min_thresh = 30;
int max_thresh = 75;
const char* filename = "Grad70.bmp"; //图片路径,可以采用相对路径
int main()
{
//图片读取
src = imread(filename , CV_LOAD_IMAGE_COLOR);
//图片预处理
cvtColor(src, src_gray, CV_BGR2GRAY);//灰度化
GaussianBlur(src, src, Size(3, 3), 0.1, 0, BORDER_DEFAULT);
blur(src_gray, src_gray, Size(3, 3)); //滤波
namedWindow("处理图", CV_WINDOW_AUTOSIZE);
imshow("处理图", src);
moveWindow("处理图", 20, 20);
imwrite("处理图.png",src);
//定义Canny边缘检测图像
Mat canny_output;
vector<vector<Point> > contours;
vector<Vec4i> hierarchy;
//利用canny算法检测边缘
Canny(src_gray, canny_output, min_thresh, max_thresh, 3);
namedWindow("轮廓图", CV_WINDOW_AUTOSIZE);
imshow("轮廓图", canny_output);
moveWindow("轮廓图", 550, 20);
//imwrite("轮廓图.png", canny_output);
//查找轮廓
findContours(canny_output, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point(0, 0));
//计算轮廓矩
vector<Moments> mu(contours.size());
for (int i = 0; i < contours.size(); i++)
{
mu[i] = moments(contours[i], false);
}
//计算轮廓的质心
vector<Point2f> mc(contours.size());
for (int i = 0; i < contours.size(); i++)
{
mc[i] = Point2d(mu[i].m10 / mu[i].m00, mu[i].m01 / mu[i].m00);
}
//绘制轮廓并表示质心
Mat drawing = Mat::zeros(canny_output.size(), CV_8UC3);
for (int i = 0; i < contours.size(); i++)
{
Scalar color = Scalar(255, 0, 0);
drawContours(drawing, contours, i, color, 2, 8, hierarchy, 0, Point());
circle(drawing, mc[i], 5, Scalar(0, 0, 255), -1, 8, 0);
rectangle(drawing, boundingRect(contours.at(i)), cvScalar(0, 255, 0));
char tam[100];
sprintf_s(tam, "(%0.0f,%0.0f)", mc[i].x, mc[i].y);
putText(drawing, tam, Point(mc[i].x, mc[i].y), FONT_HERSHEY_SIMPLEX, 0.4, cvScalar(255, 0, 255), 1);
}
namedWindow("坐标图", CV_WINDOW_AUTOSIZE);
imshow("坐标图", drawing);
moveWindow("坐标图", 1100, 20);
//imwrite("坐标图.png", drawing);
waitKey(0);
src.release();
src_gray.release();
return 0;
}