激光光条的提取
视觉测量在工业生产过程中应用广泛,针对复杂的环境下仅仅依靠相机对实际工件图像的处理和特征点的提取难以达到理想测量结果的现象,基于线结构光的视觉测量快速发展。在测量过程中,对于投射在物体表面的线结构光条纹中心的提取是整个测量过程的关键。
在opencv中存在一些已有的API能够对投射在工件表面的激光进行提取,但并不准确。例如常用的一阶微分边缘检测算子:(1)Roberts算子,(2)Sobel算子,(3)Prewitt算子,(4)Kirsch算子,还有被称为最好的边缘提取算子Canny算子;二阶微分边缘检测算子包括:(1)拉普拉斯算子,(2)Log算子,子像素级别的包括:Hessian矩阵法等方式。
但是基于边缘检测的激光光条提取只是将图像中激光条纹两条边缘线分割出来,选取其中一条作为激光的中心线,改进后的算法也只是寻找两条边缘线的中线作为激光条纹的中心,精度并不理想。
图中所示a为原图像,b为减小曝光时间后的图像,c-j所示结果为x方向Roberts提取、y方向Roberts提取、x方向sobel提取、Y方向sobel提取、x方向prewitt提取、y方向prewitt提取、拉普拉斯算子提取、Canny算子提取。通过边缘检测算法能够较快的得到复杂图像中的边缘信息,尤其是Canny算法,可以准确的检测到激光条纹的边缘,并且能够抑制噪声对提取结果的影响。但是基于边缘检测的图像分割激光条纹中心算法存在严重的不足是不能得到连续的单个像素点的边缘,在实际应用中所提取到的激光条纹会丢失部分信息,致使影响到工件检测的精度。
代码
int t1_value = 50;
int max_value = 255;
void canny_demo(int, void*);
Mat src;
Mat gray2, dst5;
int main(int argc, char**argv) {
src = imread("1.jpg");
if (!src.data) {
printf("coule not find image......\n");
return -1;
}
/////////////roberts算子//////////////////
Mat Gx= (Mat_<float>(2, 2) << 1, 0, 0, -1);
Mat Gy = (Mat_<float>(2, 2) << 0, -1, 1, 0);
Mat dst;
Mat dsty;
filter2D(src, dst, -1, Gx, Point(-1, -1), 0.0);
filter2D(src, dsty, -1, Gy, Point(-1, -1), 0.0);
/////sobel 算子////////////////
Mat Sx = (Mat_<float>(3, 3) << -1, 0, 1, -2, 0, 2, -1, 0, 1);
Mat Sy = (Mat_<float>(3, 3) << 1, 2, 1, 0, 0, 0, -1, -2, -1);
Mat dst2x, dst2y;
filter2D(src, dst2x, -1, Sx, Point(-1, -1), 0.0);
filter2D(src, dst2y, -1, Sy, Point(-1, -1), 0.0);
////////////////////////Prewitt////////////////
Mat Px = (Mat_<float>(3, 3) << -1, 0, 1, -1, 0, 1, -1, 0, 1);
Mat Py = (Mat_<float>(3, 3) << 1, 1, 1, 0, 0, 0, -1, -1, -1);
Mat dst3x,dst3y;
filter2D(src, dst3x, -1, Sx, Point(-1, -1), 0.0);
filter2D(src, dst3y, -1, Sx, Point(-1, -1), 0.0);
///////////////////拉普拉斯算子////////////////
Mat gray, dst4;
cvtColor(src, gray, CV_BGR2GRAY);
Laplacian(gray, dst4, CV_16S, 3);
convertScaleAbs(dst4, dst4);
///////////////////canny/////////
char CANNY[] = "canny out put";
namedWindow(CANNY, CV_WINDOW_AUTOSIZE);
cvtColor(src, gray2, CV_BGR2BGRA);
createTrackbar("treshold value", CANNY, &t1_value, max_value, canny_demo);
canny_demo(0,0);
//Canny(gray2,dst5,)
//输出图像
imshow("原图像", src);
imshow("X方向Roberts边缘检测", dst);
imshow("Y方向Roberts边缘检测", dsty);
imshow("X方向sobel算子边缘检测", dst2x);
imshow("Y方向sobel算子边缘检测", dst2y);
imshow("X方向prewitt算子边缘检测", dst3x);
imshow("Y方向prewitt算子边缘检测", dst3y);
imshow("laplacian", dst4);
waitKey(0);
return 0;
}
void canny_demo(int,void*) {
//Mat gray2, dst5;
//cvtColor(src, gray2, CV_BGR2BGRA);
Canny(gray2, dst5, t1_value, t1_value * 2, 3, false);
imshow("canny", dst5);
}
极值法
该方法的原理是利用激光条纹图像中每一列的最大点作为这一列的中心,以列遍历整幅图像,将所有的最大值点最为光条的能量中心,拟合后作为激光条纹的中心线。由下图可以看出,灰度值最大的点大约处于光条中心。
该方法计算量小,速度快,当激光条纹的灰度分布成理想高斯分布的时候有很好的提取效果。但是由于受到环境噪声的影响,工件材料表面上的激光条纹灰度分布不能完全构成理想的高斯分布曲线,因此该方法难以适用于信噪比较小的图像。
极值法
Mat src = imread("a7.jpg", 1);
Mat dst = src.clone();
cvtColor(dst, dst, CV_BGR2GRAY);
unsigned char max = 0;
unsigned int y = 0;
for (unsigned int i = 0;i<dst.cols;i++)
{
for (unsigned int j = 0;j<dst.rows;j++)
{
unsigned char current = dst.at<uchar>(j, i);
if (current>max)
{
max = current;
y = j;
}
}
if (max>100)
{
circle(src, Point(i, y), 1, Scalar(0, 0, 255));
}
max = 0;
y = 0;
imshow("1", src);
}
imshow("1", src);
灰度重心法
利用数学上质心的定义,将灰度值作为质量处理,计算沿坐标轴方向的灰度重心点来代表该截面的激光条纹的中心点位置,按照行或者列遍历图像,拟合得到的点作为激光条纹的中心线。
该方法减少了由于激光条纹分布不均匀、同一行或者列有较多灰度饱和点而引起的误差,提高了提取精度。并且相对于极值法,该方法达到了亚像素级别提取,提取的光条中心更加连续。但是由于环境噪声的随机性和较难消除性,在进行提取时通常因为激光条纹截面像素点参与计算数量不同、干扰点灰度值有较大随机性,导致得到的中心点会出现沿坐标轴方向的偏移误差。并且当激光条纹斜率较大时,会出现较为明显的间断现象。
灰度重心法
Mat srcimg;
srcimg = imread("2.jpg");
Mat grayimg;
cvtColor(srcimg, grayimg, CV_BGR2GRAY);
GaussianBlur(grayimg, grayimg, Size(0, 0), 6, 6);
//遍历每一列
float x0 = 0;
for (int i = 0;i < grayimg.cols;i++) {
float sum_value = 0;
float sum_valuecoor = 0;
vector<float>current_value;
vector<float>current_coordinat;
for (int j = 0;j < grayimg.rows;j++) {
float current = grayimg.at<uchar>(j, i);
//将符合阈值的点灰度值和坐标存入数组
if (current > 30) {
current_value.push_back(current);
current_coordinat.push_back(j);
}
}
//计算灰度重心
for (int k = 0;k < current_value.size();k++) {
sum_valuecoor += current_value[k]*current_coordinat[k];
sum_value += current_value[k];
}
float x = sum_valuecoor / sum_value;
x0 = x;
circle(srcimg, Point(i, x), 1, Scalar(0, 0, 255), -1, 8);
current_value.clear();
current_coordinat.clear();
}
imshow("srcimg", srcimg);
后记
笔者在做实验的时候,采用的是改进过的灰度重心法和基于hessian矩阵的steger方法,两种方式个又有略,改进的灰度重心算法也能达到亚像素级别的提取,均比上述方式要好很多。Steger方法进度很高,但是由于Hessian矩阵运算量较大,不适用于实时测量,仅用于对比验证。
对于改进的灰度重心,其实是基于极值发的基础之上进行相应的灰度中心提取,较为简单,我就不献丑放代码了。
如有错误,欢迎指正