一般在高精度測量時需要做以下幾個標定,一光學畸變標定(如果您不是用的軟件鏡頭,一般都必須標定),二投影畸變的標定,也就是因為您安裝位置誤差代表的圖像畸變校正,三物像空間的標定,也就是具體算出每個像素對應物空間的尺寸。
前兩者應該都可以通過“張正友”標定方法進行解決;對於空間的標定,基本上都是通過獲得比對現實中的已經知道長度的物體,獲得像素當量到長度的轉化。
現實拍攝的物體,或多或少會有噪音干擾,在標定的過程中還需要圖像處理算法進行糾正。

比如我獲得這樣的圖片。這幅圖片的獲取,基於我自己改造的圖像處理支架,對於普通的實驗來說,已經比較純凈了。由於使用的是鋼尺,反光比較厲害。為了便於標定,我將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;
}
附件列表