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实战:餐盘图像矫正的主要内容,如果未能解决你的问题,请参考以下文章

实战用OpenCV实现页面扭曲矫正

利用OpenCV实现旋转文本图像矫正的原理及OpenCV代码

Java基于opencv—矫正图像

OpenCV探索之路(十六):图像矫正技术深入探讨

Java基于opencv—透视变换矫正图像

OpenCV 书本视图矫正 + 广告屏幕切换 透视变换图像处理