opencv实战:餐盘图像矫正
Posted visual_eagle
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了opencv实战:餐盘图像矫正相关的知识,希望对你有一定的参考价值。
这一篇博客我对我写的《使用opencv实现单目尺寸估计(一)》里面透视变换的代码做了一个优化,那篇文章种我直接采用了角点检测。这一篇文章我将带你实现得到直线方程算交点以及交点排序的算法,最终根据盘子实际尺寸还原图像。
摄像头拍到的图像:
实现步骤:
1.转灰度图片以及二值化
2.canny边缘检测
3.直线拟合,求方成,算交点,算四个交点与原点距离得到排序方案
4.根据盘子实际尺寸比例45*35透视变换。
/**
* Filename:main.cpp
* Author:visual_eagle
* Date:2022.01.05
* version:1.0
*/
#include<opencv2/opencv.hpp>
#include<iostream>
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;
//交点
double x[4];
double y[4];
int len = sizeof(x) / sizeof(x[0]);
// 获取交点
void getCross()
int s=0;
for (int i = 0; i <line_number; i++)
for(int j=i+1;j<line_number;j++)
if(int(abs(line_k[i]))==0&&int(abs(line_k[j]))==0)
std::cout<<"i:"<<i<<" j:"<<j<<" is "<<" true"<<std::endl;
else if(int(abs(line_k[i]))!=0&&int(abs(line_k[j]))!=0)
std::cout<<"i:"<<i<<" j:"<<j<<" is "<<" true"<<std::endl;
else
std::cout<<"s:"<<s<<std::endl;
std::cout<<"i:"<<i<<" j:"<<j<<" is "<<" false"<<std::endl;
/*
* L1:A1*x+B1*y+C1=0
* L2:A2*x+B2*y+C2=0
*
* y?
* L1*A2-L2*A1
* A1A2*x+B1A2*y+A2C1=0
* A1A2*x+B2A1*y+A1C2=0
* B1A2*y+A2C1=B2A1*y+A1C2
* y=(A1C2-A2C1)/(B1A2-B2A1)
*
* x?
* L1*B2-L2*B1
* A1B2*x+B1B2*y+B2C1=0
* A2B1*x+B1B2*y+B1C2=0
* A1B2*x+B2C1=A2B1*x+B1C2
* x=(B1C2-B2C1)/(A1B2-A2B1)
*
* L1:y=k1x+b1
* L2:y=k2x+b2
* x=(b2-b1)/(k1-k2)
* y=(k1b2-k2b1)/(k1-k2)
*/
x[s]=(line_b[i]-line_b[j])/(line_k[j]-line_k[i]);
y[s]=(line_k[j]*line_b[i]-line_k[i]*line_b[j])/(line_k[j]-line_k[i]);
std::cout<<s<<".(x,y):"<<x[s]<<","<<y[s]<<std::endl;
s=s+1;
void drawLine(cv::Mat &img, //要标记直线的图像
std::vector<cv::Vec2f> lines, //检测的直线数据
double rows, //原图像的行数(高)
double cols, //原图像的列数(宽)
cv::Scalar scalar, //绘制直线的颜色
int n //绘制直线的线宽
)
int image_channels=img.channels();
cv::Point pt1, pt2;
if(lines.size()>4)
for(int i=0;i<lines.size()-4;i++)
lines.pop_back();
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 = std::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=cv::Scalar(255,0,0);//blue
else if(i==1&&image_channels!=1)
scalar=cv::Scalar(255,255,0);//yellow
else if(i==2&&image_channels!=1)
scalar=cv::Scalar(0,0,255);//red
else if(i==3&&image_channels!=1)
scalar=cv::Scalar(0,255,0);//green
else;
if(image_channels==1)
scalar=cv::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];
std::cout<<i+1<<":"<<"y="<<line_k[i]<<"*x+"<<line_b[i]<<std::endl;
std::cout<<"lines_number:"<<lines.size()<<std::endl;
line_number=lines.size();
if(line_number==4)
getCross();
//2 3 change
double cap_x=0;
double cap_y=0;
cap_x=x[2];
cap_y=y[2];
x[2]=x[3];
y[2]=y[3];
x[3]=cap_x;
y[3]=cap_y;
//和(0,0)两点距离最短确定起始点,左上角点
double point_cup_x=cols;
double point_cup_y=rows;
double p0=cols;
int p0_flag=8;
for (int i = 0; i <line_number; i++)
double p0_m=sqrt(x[i]*x[i]+y[i]*y[i]);
if(p0>p0_m)
p0_flag=i;
p0=p0_m;
std::cout<<"p"<<i<<":"<<p0_m<<std::endl;
if(i==3)
std::cout<<"p0:"<<p0<<" p0_flag:"<<p0_flag<<std::endl;
if(p0_flag==0)
std::cout<<"Point ok"<<std::endl;
else
double temp_x;
double temp_y;
for (int o = 0; o < p0_flag; o++)
temp_x = x[0];
temp_y = y[0];
for (int p = 0; p <len - 1; p++)
x[p] = x[p + 1];
y[p] = y[p + 1];
x[len - 1] = temp_x;
y[len - 1] = temp_y;
for(int i=0;i<4;i++)
std::cout<<"i:"<<i<<"(x,y):"<<x[i]<<","<<y[i]<<std::endl;
//1 3 change
double cap_x2=0;
double cap_y2=0;
cap_x2=x[1];
cap_y2=y[1];
x[1]=x[3];
y[1]=y[3];
x[3]=cap_x2;
y[3]=cap_y2;
std::cout<<"Point deal ok"<<std::endl;
for(int i=0;i<4;i++)
if(i==0)
circle(img, cv::Point(x[i],y[i]), 10, cv::Scalar(0, 0, 0),-1);//black
else if(i==1)
circle(img, cv::Point(x[i],y[i]), 10, cv::Scalar(255, 0, 0),-1);//blue
else if(i==2)
circle(img, cv::Point(x[i],y[i]), 10, cv::Scalar(0, 255, 0),-1);//green
else
circle(img, cv::Point(x[i],y[i]), 10, cv::Scalar(0, 0, 255),-1);//red
int main(int argc, char *argv[])
/*
* Mat cv::imread(const String & filename,int flags=IMREAD_COLOR)
* flags=0: src.channels() is 1
* flags=1: src.channels() is 3
* flags=-1:if src have alpha ,src.channels() is 4
*/
cv::Mat src=cv::imread("D:/program/mycompany/4food2321/food/image/fruit (6).jpg",1);
std::cout<<"src.channels():"<<src.channels()<<std::endl;
std::cout<<"src.size():"<<src.size()<<std::endl;
int r_width=src.cols*0.5;
int r_height=src.rows*0.5;
cv::Mat src_resize;
cv::resize(src,src_resize,cv::Size(r_width,r_height));
std::cout<<"src_resize.size():"<<src_resize.size()<<std::endl;
cv::imshow("src_resize",src_resize);
/*
* void cv::cvtColor(inputArray src,OutputArray dst,int code,int dstCn=0)
* code :https://docs.opencv.org/4.x/d8/d01/group__imgproc__color__conversions.html#ga4e0972be5de079fed4e3a10e24ef5ef0
* dstCn:number of channels in the destination image; if the parameter is 0, the number of the channels is derived automatically from src and code.
*/
cv::Mat src_gray;
cv::cvtColor(src_resize,src_gray,cv::COLOR_BGR2GRAY,0);
//frame_gray(x,y)>90 frame_threshold(x,y)=255 else 0
cv::threshold(src_gray,src_gray,90,255,cv::THRESH_BINARY);
cv::imshow("src_gray",src_gray);
/*
* Canny has two ways to use it, I use the first.
* 1.void cv::Canny(InputArray image,OutputArray edges,double threshold1,double threshold2,int apertureSize = ,3, bool L2gradient = false)
* InputArray image is 8bit
* OutputArray edges is 8bit ,edges.size()=image.size()
* threshold1 and threshold2:低于阈值1的会被认为不是边缘,高于阈值2的像素点会被认为是强边缘,在阈值1和2之间的是弱边缘
* apertureSize is Sober operator size
* L2gradient is 是否采用更精确的方式计算图像梯度
*/
cv::Mat gray_canny;
cv::Canny(src_gray, gray_canny, 200, 250, 3, false);
cv::imshow("gray_canny",gray_canny);
/*
* void HoughLinesP(InputArray image, OutputArray lines, double rho, double theta, int threshold, double minLineLength=0, double maxLineGap=0 )
* OutputArray lines,每条直线有四个元素矢量(x_1,y_1,x_2,y_2)
* double rho为直线搜索的进步尺寸的单位半径
* double theta为直线搜索的进步尺寸的单位角度
* threshold为累加平面的阈值参数,大于这个阈值才是认为是直线
* minLineLength最低线段长度
* maxLineGap同一方向上两线段判断为一条线的最大允许间隔,超过阈值则将两线认为是一条直线
*/
//way1:
// std::vector<cv::Vec4i> lines;
// int threshold=r_height*0.15;
// double minLineLength=r_height*0.2;
// double maxLineGap=threshold*0.2;
// std::cout<<"threshold:"<<threshold<<" minLineLength:"<<minLineLength<<" maxLineGap:"<<maxLineGap<<std::endl;
// HoughLinesP(gray_canny, lines, 1, CV_PI / 180, threshold, minLineLength, maxLineGap);
// cv::Mat frame_HoughLines=cv::Mat::zeros(r_height,r_width,CV_8UC3);
// for( size_t i = 0; i < lines.size(); i++ )
//
// line( frame_HoughLines, cv::Point(lines[i][0], lines[i][1]),cv::Point( lines[i][2], lines[i][3]), cv::Scalar(255,255,255), 1.5, 8 );
//
// imshow("frame_HoughLines",frame_HoughLines);
//way2:
//void HoughLines(InputArray image, OutputArray lines, double rho, double theta, int threshold, double srn=0, double stn=0, double min_theta = 0, double max_theta = CV_PI )
//累加器进行检测直线
cv::Mat frame_HoughLines=cv::Mat::zeros(r_height,r_width,CV_8UC1);
std::vector<cv::Vec2f> lines2;
HoughLines(gray_canny, lines2, 0.85, CV_PI / 180, 120,0,0,0,CV_PI);
cv::Mat frame_HoughLines2=src_resize.clone();
drawLine(frame_HoughLines2, lines2, frame_HoughLines2.rows, frame_HoughLines2.cols, cv::Scalar(255,255,255), 1);
imshow("frame_HoughLines2",frame_HoughLines2);
//透视变换
std::vector<cv::Point2f>dstpoint(4);//存放变换后四顶点
//mm
float a4_width=3500/6;
float a4_height=4500/6;
std::vector<cv::以上是关于opencv实战:餐盘图像矫正的主要内容,如果未能解决你的问题,请参考以下文章