一般在高精度测量时需要做以下几个标定,一光学畸变标定(如果您不是用的软件镜头,一般都必须标定),二投影畸变的标定,也就是因为您安装位置误差代表的图像畸变校正,三物像空间的标定,也就是具体算出每个像素对应物空间的尺寸。
前两者应该都可以通过“张正友”标定方法进行解决;对于空间的标定,基本上都是通过获得比对现实中的已经知道长度的物体,获得像素当量到长度的转化。
现实拍摄的物体,或多或少会有噪音干扰,在标定的过程中还需要图像处理算法进行纠正。
比如我获得这样的图片。这幅图片的获取,基于我自己改造的图像处理支架,对于普通的实验来说,已经比较纯净了。由于使用的是钢尺,反光比较厉害。为了便于标定,我将10厘米和20厘米两个地方用绿色胶带缠绕住。
下一步,就是要获得关键区域。通过GOPre的分析,原始图像在YUV域下,比较方便分割
编写代码分割,阈值分析
Mat src = imread("E:\\sandbox\\1.bmp");
Mat dst;
Mat tmp;
vector<Mat> matSplit;
cvtColor(src,tmp,COLOR_BGR2YUV);
split(tmp,matSplit);
dst = matSplit[1];
threshold(dst,dst,100,255,
COLOR_BGR2Lab)
);
cv::waitKey();
这个时候还是有噪音的,但是已经比较理想了。最合适的方法就是投影分析。由于GOCVHelper中的投影分析本来是对字符投影进行分割的,所以需要适当地修正。
void projection4ruler(Mat src, int& down1,int& up2 ,int direction){
Mat tmp = src.clone();
vector<int> vdate;
if (DIRECTION_X == direction){
for (int i=0;i<tmp.cols;i++){
Mat data = tmp.col(i);
int itmp = countNonZero(data);
if (itmp > 10)
{
int jjj=0;
}
vdate.push_back(itmp);
}
}else{
for (int i=0;i<tmp.rows;i++){
Mat data = tmp.row(i);
int itmp = countNonZero(data);
vdate.push_back(itmp);
}
}
//过滤掉所有噪音
//寻找第一个下边沿和第二个上边沿
down1 = 1;
up2 = src.cols - 1;
for (int i=0;i<tmp.cols-1;i++)
{
if (vdate[i] >= 100 && vdate[i+1] < 100)
{
down1 = i;
break;
}
}
for (int i= down1;i<tmp.cols - 1;i++)
{
if (vdate[i] < 100 && vdate[i+1] >= 100)
{
up2 = i;
return;
}
}
}
寻找并且标注出来,非常准确。
图上(1767 - 771 = 996)对于10厘米,那么,那么1个像素相当于0.1毫米(这个整数只是巧合)
多次测量,验证算法的稳定性,并运用于实际项目。
小结
基于算法库和成熟的思路,能够加速解决问题速度。
比较好的视觉环境也很重要。
附全部代码
//GoCvHelper的demo程序
#include "stdafx.h"
#include <iostream>
#include "opencv2/core/core.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "GoCvHelper.h"
using namespace std;
using namespace cv;
using namespace GO;
#define VP vector<cv::Point> //用VP符号代替 vector<point>
void projection4ruler(Mat src, int& down1,int& up2 ,int direction){
Mat tmp = src.clone();
vector<int> vdate;
if (DIRECTION_X == direction){
for (int i=0;i<tmp.cols;i++){
Mat data = tmp.col(i);
int itmp = countNonZero(data);
if (itmp > 10)
{
int jjj=0;
}
vdate.push_back(itmp);
}
}else{
for (int i=0;i<tmp.rows;i++){
Mat data = tmp.row(i);
int itmp = countNonZero(data);
vdate.push_back(itmp);
}
}
//过滤掉所有噪音
//寻找第一个下边沿和第二个上边沿
down1 = 1;
up2 = src.cols - 1;
for (int i=0;i<tmp.cols-1;i++)
{
if (vdate[i] >= 100 && vdate[i+1] < 100)
{
down1 = i;
break;
}
}
for (int i= down1;i<tmp.cols - 1;i++)
{
if (vdate[i] < 100 && vdate[i+1] >= 100)
{
up2 = i;
return;
}
}
}
int _tmain(int argc, _TCHAR* argv[])
{
Mat src = imread("E:\\sandbox\\1.bmp");
Mat dst;
Mat tmp;
int up1 = 0;
int down2 = 0;
vector<Mat> matSplit;
cvtColor(src,tmp,COLOR_BGR2Lab);
split(tmp,matSplit);
dst = matSplit[1];
threshold(dst,dst,100,255,THRESH_OTSU);
threshold(dst,dst,0,255,THRESH_BINARY_INV);//以白色为有数据
projection4ruler(dst,up1,down2,DIRECTION_X);
line(src,Point(up1,0),Point(up1,src.rows-1),Scalar(0,0,255),1);
line(src,Point(down2,0),Point(down2,src.rows-1),Scalar(0,0,255),1);
cv::waitKey();
return 0;
}
附件列表
目前方向:图像拼接融合、图像识别 联系方式:jsxyhelu@foxmail.com