前言
在数字近景摄影测量中,最关键的图像间的匹配。但是由于工业测量现场复杂的背景和被测物体表面没有足够的特征,应用在双目视觉中基于关键点匹配的算法在效率、精度、准确率等方面,大多不能满足需求。于是在工业数字近景测量中需要人工设置的靶标点作为关键点进行匹配。
人工设置的靶标有很多种,目前最主要的点分布型,同心圆环型,非几何特征型。本文利用程序实现了同心圆环型中Schneider C .T设计的靶标的自动生成。
在上一篇文章(https://blog.csdn.net/iamqianrenzhan/article/details/81194791)中介绍了一各自动生成Schneider编码标志的程序,这一篇主要介绍怎么识别相机拍到的编码靶标。
Schneider 编码方案介绍
Schneider 编码方案介绍它由中心圆和同心编码环带组成,编码环带按照角度均分为10份,每份36°,每一份可以称为亮环带或者暗环带,相应的二进制码为1或者0,“1”表示该位上有编码点,“0”表示该位上没有编码点,由于编码环带并没有规定起始点,以任意一个编码点为起始位置,按顺时针方向读取编码环带的码值, 可以组成长度为 10的二进制序列,对这个二进制序列进行循环移位,选取其中数值最小的二进制序列作为该靶标的码值。
识别程序流程图
其中筛选椭圆轮廓一步目前还不能很好实现。只能筛选出圆形的轮廓,所以对大角度投影拍摄的识别率很低。还需要关注的是获取编码值这步,因为要分成8个区域并且不知道区域的起点,所以程序中实现方案是在45度的范围内每隔15度选择一个起点,这样对于拍摄效果较好的图片,会得到3个相同的编码值,那就确定了靶标的编码值;对于拍摄效果不好的图片,会得到2个相同的编码值,也可以确定了靶标的编码值。
识别代码展示
主函数
int main()
{
bool debug = 1;
//读取图片
//string filename = "8/all.jpg";
string filename = "35.bmp";
Mat img = imread(filename);
if (debug)
{
namedWindow("原图", 4);
imshow("原图", img);
waitKey(1);
}
//怎么处理
变量定义
Mat gray_all = img;
vector<vector<cv::Point> > contours_all, contours_circle;
vector<cv::Vec4i> hierarchy_all;
//找到所有轮廓
cvtColor(gray_all, gray_all, CV_BGR2GRAY);
GaussianBlur(gray_all, gray_all, cv::Size(3, 3), 0);
threshold(gray_all, gray_all, 100, 255, THRESH_BINARY);
Mat gray_show = gray_all.clone();
cv::findContours(gray_all, contours_all, hierarchy_all, CV_RETR_TREE, cv::CHAIN_APPROX_NONE, cv::Point(0, 0));
if (debug)
{
//绘制所有轮廓
Mat temp1 = Mat::zeros(gray_all.size(), CV_8UC3);
drawContours(temp1, contours_all, -1, CV_RGB(255, 0, 0), 1);
namedWindow("找到的所有轮廓", 4);
imshow("找到的所有轮廓", temp1);
waitKey(1);
}
//从所有轮廓中找出圆,得到圆心和半径,作为后续处理
//这部分代码可以优化,得到椭圆的中心和长短轴,角度,这样可以处理不是正投影的情况。
vector<Point2f> vec_center;
vector<float> vec_radius;
vector<RotatedRect> vec_rrect;
for (int i = 0; i < contours_all.size(); i++)
{
Point2f center;
float radius;
minEnclosingCircle(contours_all[i], center, radius);//最小外接圆,最小外接椭圆是否可行?
float contourarea = contourArea(contours_all[i]);
float ratio1 = contourarea / (3.1415*radius*radius);
if (ratio1 > 0.9 &&contours_all[i].size() > 50) //把0.85调小可以识别到更多的圆,但会增加误识别概率
{
contours_circle.push_back(contours_all[i]);
cout << "ratio1:" << ratio1 << endl;
vec_center.push_back(center);
vec_radius.push_back(radius);
}
//拟合旋转矩形
if (contours_all[i].size() > 50)
{
RotatedRect fitelliprect = fitEllipse(contours_all[i]);
RotatedRect minarearect = minAreaRect(contours_all[i]);
float fitrectarea = fitelliprect.size.width * fitelliprect.size.height;
float mimarea = minarearect.size.width * minarearect.size.height;
float ratio2 = fitrectarea / mimarea;
if (ratio2 > 0.95 && ratio2 < 1.05) //还要再加条件
{
cout << "ratio2:" << ratio2 << endl;
vec_rrect.push_back(fitelliprect);
}
}
}
//绘制拟合椭圆矩形
if (debug)
{
Mat temp2 = Mat::zeros(gray_all.size(), CV_8UC3);
for (int i = 0; i < vec_rrect.size(); i++)
{
circle(temp2, vec_rrect[i].center, 20, cv::Scalar(255, 0, 0), -1);
}
namedWindow("椭圆矩形", 4);
imshow("椭圆矩形", temp2);
}
//绘制圆轮廓
if (debug)
{
Mat temp3 = Mat::zeros(gray_all.size(), CV_8UC3);
drawContours(temp3, contours_circle, -1, CV_RGB(255, 0, 0), 1);
namedWindow("找到的圆轮廓", 4);
imshow("找到的圆轮廓", temp3);
}
//从圆轮廓中截取出靶标区域
for (int i = 0; i < vec_center.size(); i++)
{
cout << "第" << i << "个特征" << endl;
Rect rect = Rect(vec_center[i].x - 3 * vec_radius[i],
vec_center[i].y - 3 * vec_radius[i],
6 * vec_radius[i],
6 * vec_radius[i]);
//需要做判断是否超出边界
Mat t = gray_show(rect);
//处理靶标区域,从靶标中心顺时针方向隔45度扫描;3种扫描起点,0,15,30
uint8_t codenew = 0;
for (int ScanningPoint = 0; ScanningPoint < 3; ScanningPoint++)
{
uint8_t code = 0;
//每隔45度算一个区域,共8个区域
for (int range = 0; range < 8; range++)
{
int count = 0;
int StartPoint = range * 45 + ScanningPoint * 15;
int EndPoint = (range+1) * 45 + ScanningPoint * 15;
for (int angle = StartPoint; angle < EndPoint; angle = angle + 3)
{
Point2f p = Point2f(3 * vec_radius[i] + (vec_radius[i] / 150 * 350)*cos(angle / 180.0*3.1415),
3 * vec_radius[i] + (vec_radius[i] / 150 * 350)*sin(angle / 180.0*3.1415));
uchar x = t.at<uchar>((int)p.x, (int)p.y);
if ((int)x == 255)
count++;
}
if (count < 8) //说明该位是1 if (count > 7) //取决于是否反色(可以根据中心像素判断)
{
code += pow(2, range);
}
}
codenew = findmincode(code);
cout << (int)codenew << endl;
cout << "转9度" << endl << endl;
}
//在t上写数字
Point origin;
string str0 = to_string((int)codenew);
const char* text0 = str0.c_str();
//文本框位置
origin.x = 3 * vec_radius[i]-10;
origin.y = 3 * vec_radius[i]-10;
putTextZH(t, text0, origin, Scalar(255, 255, 255), 20, "Arial", 0, 0);
//显示靶标区域
if (debug)
{
string title = to_string(i);
namedWindow(title);
imshow(title, t);
waitKey(1);
}
cout << endl << endl;
}
waitKey();
while (1);
return 0;
}
找最小编码子函数
//靶标原始编号遍历左移找到最小的编号,这是该编码靶标的编码规则。
uint8_t findmincode(uint8_t code)
{
uint8_t minresult = 255;
for (int i = 0; i < 8; i++)
{
uint8_t a = code;
uint8_t left = 0;//存储左移之后的结果
uint8_t right = 0;//存储右移之后的结果
right = (a >> i);
left = a << (8 - i);
uint8_t result;
result = left | right;
if (result < minresult)
{
minresult = result;
}
}
return minresult;
}
opencv图片插入中文函数
void GetStringSize(HDC hDC, const char* str, int* w, int* h)
{
SIZE size;
GetTextExtentPoint32A(hDC, str, strlen(str), &size);
if (w != 0) *w = size.cx;
if (h != 0) *h = size.cy;
}
void putTextZH(Mat &dst, const char* str, Point org, Scalar color, int fontSize, const char* fn, bool italic, bool underline)
{
CV_Assert(dst.data != 0 && (dst.channels() == 1 || dst.channels() == 3));
int x, y, r, b;
if (org.x > dst.cols || org.y > dst.rows) return;
x = org.x < 0 ? -org.x : 0;
y = org.y < 0 ? -org.y : 0;
LOGFONTA lf;
lf.lfHeight = -fontSize;
lf.lfWidth = 0;
lf.lfEscapement = 0;
lf.lfOrientation = 0;
lf.lfWeight = 5;
lf.lfItalic = italic; //斜体
lf.lfUnderline = underline; //下划线
lf.lfStrikeOut = 0;
lf.lfCharSet = DEFAULT_CHARSET;
lf.lfOutPrecision = 0;
lf.lfClipPrecision = 0;
lf.lfQuality = PROOF_QUALITY;
lf.lfPitchAndFamily = 0;
strcpy_s(lf.lfFaceName, fn);
HFONT hf = CreateFontIndirectA(&lf);
HDC hDC = CreateCompatibleDC(0);
HFONT hOldFont = (HFONT)SelectObject(hDC, hf);
int strBaseW = 0, strBaseH = 0;
int singleRow = 0;
char buf[1 << 12];
strcpy_s(buf, str);
char *bufT[1 << 12]; // 这个用于分隔字符串后剩余的字符,可能会超出。
//处理多行
{
int nnh = 0;
int cw, ch;
const char* ln = strtok_s(buf, "\n", bufT);
while (ln != 0)
{
GetStringSize(hDC, ln, &cw, &ch);
strBaseW = max(strBaseW, cw);
strBaseH = max(strBaseH, ch);
ln = strtok_s(0, "\n", bufT);
nnh++;
}
singleRow = strBaseH;
strBaseH *= nnh;
}
if (org.x + strBaseW < 0 || org.y + strBaseH < 0)
{
SelectObject(hDC, hOldFont);
DeleteObject(hf);
DeleteObject(hDC);
return;
}
r = org.x + strBaseW > dst.cols ? dst.cols - org.x - 1 : strBaseW - 1;
b = org.y + strBaseH > dst.rows ? dst.rows - org.y - 1 : strBaseH - 1;
org.x = org.x < 0 ? 0 : org.x;
org.y = org.y < 0 ? 0 : org.y;
BITMAPINFO bmp = { 0 };
BITMAPINFOHEADER& bih = bmp.bmiHeader;
int strDrawLineStep = strBaseW * 3 % 4 == 0 ? strBaseW * 3 : (strBaseW * 3 + 4 - ((strBaseW * 3) % 4));
bih.biSize = sizeof(BITMAPINFOHEADER);
bih.biWidth = strBaseW;
bih.biHeight = strBaseH;
bih.biPlanes = 1;
bih.biBitCount = 24;
bih.biCompression = BI_RGB;
bih.biSizeImage = strBaseH * strDrawLineStep;
bih.biClrUsed = 0;
bih.biClrImportant = 0;
void* pDibData = 0;
HBITMAP hBmp = CreateDIBSection(hDC, &bmp, DIB_RGB_COLORS, &pDibData, 0, 0);
CV_Assert(pDibData != 0);
HBITMAP hOldBmp = (HBITMAP)SelectObject(hDC, hBmp);
//color.val[2], color.val[1], color.val[0]
SetTextColor(hDC, RGB(255, 255, 255));
SetBkColor(hDC, 0);
//SetStretchBltMode(hDC, COLORONCOLOR);
strcpy_s(buf, str);
const char* ln = strtok_s(buf, "\n", bufT);
int outTextY = 0;
while (ln != 0)
{
TextOutA(hDC, 0, outTextY, ln, strlen(ln));
outTextY += singleRow;
ln = strtok_s(0, "\n", bufT);
}
uchar* dstData = (uchar*)dst.data;
int dstStep = dst.step / sizeof(dstData[0]);
unsigned char* pImg = (unsigned char*)dst.data + org.x * dst.channels() + org.y * dstStep;
unsigned char* pStr = (unsigned char*)pDibData + x * 3;
for (int tty = y; tty <= b; ++tty)
{
unsigned char* subImg = pImg + (tty - y) * dstStep;
unsigned char* subStr = pStr + (strBaseH - tty - 1) * strDrawLineStep;
for (int ttx = x; ttx <= r; ++ttx)
{
for (int n = 0; n < dst.channels(); ++n) {
double vtxt = subStr[n] / 255.0;
int cvv = vtxt * color.val[n] + (1 - vtxt) * subImg[n];
subImg[n] = cvv > 255 ? 255 : (cvv < 0 ? 0 : cvv);
}
subStr += 3;
subImg += dst.channels();
}
}
SelectObject(hDC, hOldBmp);
SelectObject(hDC, hOldFont);
DeleteObject(hf);
DeleteObject(hBmp);
DeleteDC(hDC);
}
识别效果
注:由于屏幕大小限制,这里的截图并没有把所有识别到的靶标展示出来。