一个摄像头无法获取深度信息,两个摄像头双目标定可以实现双目测距。
但是我现在只有一个摄像头,如果想实现测量尺寸,我的思路:选一张固定尺寸的背景,例如一张A4纸,从图像中提取A4纸并进行透视变换进行图像矫正,A4纸的尺寸我可以确定,那么也可以确定图像中的物体长宽信息(高度忽略不计的情况,例如:卡片)。当摄像头距离目标物距离L,此时像素所占面积为S,那么理论上来说,目标物图像变化后的面积为S1,则距离L1=(L/S)*S1,假定目标物上面贴有很多个面积为1平方厘米的正方形贴纸,那么获取四个角点和四条边的信息通过算法可以获取出物体在深度方向上的偏移量。有想法就实践。
1.在地板上放一张A4纸随便放一些物体。利用opencv打开摄像头获取图像并显示。
2.转灰度图像
3.如果直接使用canny的画周围地板的线条不好去除,所以先二值化分割。
4.观察图像中存在噪点,使用中值滤波处理
5.使用canny进行边缘检测
6.使用累加器方法进行直线拟合
7.得到了四条线段,此时可以求交点,但是我这里为了方便直接角点检测
8.得到角点排序后进行透视变换
实现1-8效果代码:
#include
using namespace cv;
using namespace std;
double x_1[4];
double y_1[4];
double x_2[4];
double y_2[4];
double line_k[4];
double line_b[4];
int line_number=0;
// 获取交点
//void getCross()
//{
// for (int i = 0; i
// {
// for(int j=i+1;j
// {
// if(int(abs(line_k[i]))==0&&int(abs(line_k[j]))==0)
// {
// cout<<"i:"<
// }
// }
// }
//}
void drawLine(Mat &img, //要标记直线的图像
vector<Vec2f> lines, //检测的直线数据
double rows, //原图像的行数(高)
double cols, //原图像的列数(宽)
Scalar scalar, //绘制直线的颜色
int n //绘制直线的线宽
)
{
int image_channels=img.channels();
Point pt1, pt2;
for (size_t i = 0; i < lines.size(); i++)
{
float rho = lines[i][0]; //直线距离坐标原点的距离
float theta = lines[i][1]; //直线过坐标原点垂线与x轴夹角
double a = cos(theta); //夹角的余弦值
double b = sin(theta); //夹角的正弦值
double x0 = a*rho, y0 = b*rho; //直线与过坐标原点的垂线的交点
double length = max(rows, cols); //图像高宽的最大值
//计算直线上的一点
pt1.x = cvRound(x0 + length * (-b));
pt1.y = cvRound(y0 + length * (a));
//计算直线上另一点
pt2.x = cvRound(x0 - length * (-b));
pt2.y = cvRound(y0 - length * (a));
//两点绘制一条直线
if(i==0&&image_channels!=1)
{
scalar=Scalar(255,0,0);//blue
}
else if(i==1&&image_channels!=1)
{
scalar=Scalar(255,255,0);//yellow
}
else if(i==2&&image_channels!=1)
{
scalar=Scalar(0,0,255);//red
}
else if(i==3&&image_channels!=1)
{
scalar=Scalar(0,255,0);//green
}
else;
if(image_channels==1)
{
scalar=Scalar(255,255,255);
}
line(img, pt1, pt2, scalar, n);
//计算直线方程
x_1[i]=pt1.x;
y_1[i]=pt1.y;
x_2[i]=pt2.x;
y_2[i]=pt2.y;
line_k[i]=(y_2[i]-y_1[i])/(x_2[i]-x_1[i]);
line_b[i]=y_1[i]-line_k[i]*x_1[i];
cout<<i+1<<":"<<"y="<<line_k[i]<<"*x+"<<line_b[i]<<endl;
}
cout<<"lines_number:"<<lines.size()<<endl;
line_number=lines.size();
// getCross();
}
int main(int argc, char *argv[])
{
VideoCapture cap;
cap.open(0);
Mat frame;
Mat src;
while(line_number!=4)
{
cap>>frame;
src=frame;
imshow("frame",frame);
Mat frame_gray;
cvtColor(frame,frame_gray,COLOR_BGR2GRAY);
imshow("frame_gray",frame_gray);
Mat frame_threshold;
threshold(frame_gray,frame_threshold,160,255,THRESH_BINARY);//frame_gray(x,y)>160 frame_threshold(x,y)=255 else 0
imshow("frame_threshold",frame_threshold);
Mat frame_medianBlur;
medianBlur(frame_threshold, frame_medianBlur, 3);
imshow("frame_medianBlur",frame_medianBlur);
Mat frame_Canny;
Canny(frame_medianBlur, frame_Canny, 10, 180, 3, false);
imshow("frame_Canny",frame_Canny);
//累加器进行检测直线
vector<Vec2f> lines;
HoughLines(frame_Canny, lines, 1, CV_PI / 180, 100, 0, 0);
Mat frame_HoughLines=frame;
drawLine(frame_HoughLines, lines, frame_HoughLines.rows, frame_HoughLines.cols, Scalar(0,0,0), 1);
imshow("frame_HoughLines",frame_HoughLines);
Mat frame_zeros = Mat::zeros(frame_HoughLines.rows, frame_HoughLines.cols, CV_8UC1);
drawLine(frame_zeros, lines, frame_HoughLines.rows, frame_HoughLines.cols, Scalar(0,0,0), 1);
imshow("frame_zeros",frame_zeros);
vector<Point2f> conners;//检测到的角点
int maxConers = 4;//检测角点上限
double qualityLevel = 0.1;//最小特征值
double minDistance = 20;//最小距离
Mat frame_ShiTomasi=frame;
goodFeaturesToTrack(frame_zeros, conners, maxConers, qualityLevel, minDistance);
cout<<"Shi-Tomasi(x,y):"<<conners<<endl;
//角点绘制
for (int i = 0; i < conners.size(); i++)
{
// string text=to_string(i)+"(x,y):"+"("+to_string((int)conners[i].x)+","+to_string((int)conners[i].y)+")";
// cv::putText(frame_ShiTomasi, text, conners[i], FONT_HERSHEY_COMPLEX, 0.6, cv::Scalar(0, 0, 0), 1.4, 8, 0);
circle(frame_ShiTomasi, conners[i], 3, Scalar(0,255,0), 2, 8, 0);
}
//分割四个坐标
int width_flag=frame_HoughLines.cols/2;
int height_flag=frame_HoughLines.rows/2;
vector<Point2f>srcpoint(4);//存放变换前四顶点
for (int i = 0; i < conners.size(); i++)
{
if(conners[i].x<width_flag&&conners[i].y<height_flag)
{
// cv::putText(frame_ShiTomasi, "left1", conners[i], FONT_HERSHEY_COMPLEX, 0.6, cv::Scalar(0, 0, 0), 1.4, 8, 0);
srcpoint[0]=conners[i];
}
else if(conners[i].x>width_flag&&conners[i].y<height_flag)
{
// cv::putText(frame_ShiTomasi, "right1", conners[i], FONT_HERSHEY_COMPLEX, 0.6, cv::Scalar(0, 0, 0), 1.4, 8, 0);
srcpoint[1]=conners[i];
}
else if(conners[i].x<width_flag&&conners[i].y>height_flag)
{
// cv::putText(frame_ShiTomasi, "left2", conners[i], FONT_HERSHEY_COMPLEX, 0.6, cv::Scalar(0, 0, 0), 1.4, 8, 0);
srcpoint[2]=conners[i];
}
else if(conners[i].x>width_flag&&conners[i].y>height_flag)
{
// cv::putText(frame_ShiTomasi, "right2", conners[i], FONT_HERSHEY_COMPLEX, 0.6, cv::Scalar(0, 0, 0), 1.4, 8, 0);
srcpoint[3]=conners[i];
}
else;
}
imshow("frame_ShiTomasi",frame_ShiTomasi);
waitKey(30);
//透视变换
vector<Point2f>dstpoint(4);//存放变换后四顶点
//mm
float a4_width=2100/4;
float a4_height=2970/4;
Mat result = Mat::zeros(a4_width, a4_height,frame.type());
//定义矫正后四顶点
dstpoint[0] = Point2f(0, result.rows);
dstpoint[1] = Point2f(0, 0);
dstpoint[2] = Point2f(result.cols, result.rows);
dstpoint[3] = Point2f(result.cols, 0);
Mat M = getPerspectiveTransform(srcpoint, dstpoint);
Mat frame_result=src;
imshow("1",frame_result);
warpPerspective(frame_result, result, M, result.size());
imshow("result", result);
}
cap.release();
waitKey(0);
return 0;
}
9.进行尺寸估计(将矫正后图像传入,最小外接矩形,然后阈值划分,取出区域求长宽,按照比例关系估计最后的长宽比)下面代码仅仅实现了找出最小矩形和输出一些点信息。由于时间有限,计算距离算法部分后续更新。
void get_dist(Mat src)
{
cvtColor(src,src,COLOR_BGR2GRAY);
threshold(src,src,160,255,THRESH_BINARY);//frame_gray(x,y)>160 frame_threshold(x,y)=255 else 0
medianBlur(src, src, 3);
Canny(src, src, 10, 180, 3, false);
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(src, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_NONE, Point());
vector<Rect> boundRect(contours.size());
Mat drawingPicture = Mat::zeros(src.size(), CV_8UC1); //最小外接矩形画布
int width_i=2100/8;
int height_i=2970/8;
for (int i = 0; i < contours.size(); i++)
{
//绘制轮廓的最小外结矩形
RotatedRect rect = minAreaRect(contours[i]);
Point2f P[4];
rect.points(P);
for (int j = 0; j <= 3; j++)
{
line(src, P[j], P[(j + 1) % 4], Scalar(255), 1);
cout<<"P[j],P[(j + 1) % 4]:"<<P[j]<<","<< P[(j + 1) % 4]<<endl;
}
}
imshow("dist",src);
}