OpenCV图片校正

OpenCV图片校正

  • 背景
  • 几种校正方法
  • 1.傅里叶变换 + 霍夫变换+ 直线 + 角度 + 旋转
  • 3.四点透视 + 角度 + 旋转
  • 4.检测矩形轮廓 + 角度 + 旋转
  • 参考

背景

遇到偏的图片想要校正成水平或者垂直的。

几种校正方法

对于倾斜的图片通过矫正可以得到水平的图片。一般有如下几种基于opencv的组合方式进行图片矫正。

  • 1、傅里叶变换 + 霍夫变换+ 直线 + 角度 + 旋转
  • 2、边缘检测 + 霍夫变换 + 直线+角度 + 旋转
  • 3、四点透视 + 角度 + 旋转
  • 4、检测矩形轮廓 + 角度 + 旋转

1.傅里叶变换 + 霍夫变换+ 直线 + 角度 + 旋转

#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <iostream>

using namespace cv;
using namespace std;

// 二值化阈值
#define GRAY_THRESH 150

// 直线上点的个数
#define HOUGH_VOTE 50

int main(int argc, char **argv)
{
    //Read a single-channel image
    const char* filename = "31.png";
    Mat srcImg = imread(filename, CV_LOAD_IMAGE_GRAYSCALE);
    if (srcImg.empty())
        return -1;
    imshow("source", srcImg);

    Point center(srcImg.cols / 2, srcImg.rows / 2);

    //Expand image to an optimal size, for faster processing speed
    //Set widths of borders in four directions
    //If borderType==BORDER_CONSTANT, fill the borders with (0,0,0)
    Mat padded;
    int opWidth = getOptimalDFTSize(srcImg.rows);
    int opHeight = getOptimalDFTSize(srcImg.cols);
    copyMakeBorder(srcImg, padded, 0, opWidth - srcImg.rows, 0, opHeight - srcImg.cols, BORDER_CONSTANT, Scalar::all(0));

    Mat planes[] = { Mat_<float>(padded), Mat::zeros(padded.size(), CV_32F) };
    Mat comImg;
    //Merge into a double-channel image
    merge(planes, 2, comImg);

    //Use the same image as input and output,
    //so that the results can fit in Mat well
    dft(comImg, comImg);

    //Compute the magnitude
    //planes[0]=Re(DFT(I)), planes[1]=Im(DFT(I))
    //magnitude=sqrt(Re^2+Im^2)
    split(comImg, planes);
    magnitude(planes[0], planes[1], planes[0]);

    //Switch to logarithmic scale, for better visual results
    //M2=log(1+M1)
    Mat magMat = planes[0];
    magMat += Scalar::all(1);
    log(magMat, magMat);

    //Crop the spectrum
    //Width and height of magMat should be even, so that they can be divided by 2
    //-2 is 11111110 in binary system, operator & make sure width and height are always even
    magMat = magMat(Rect(0, 0, magMat.cols & -2, magMat.rows & -2));

    //Rearrange the quadrants of Fourier image,
    //so that the origin is at the center of image,
    //and move the high frequency to the corners
    int cx = magMat.cols / 2;
    int cy = magMat.rows / 2;

    Mat q0(magMat, Rect(0, 0, cx, cy));
    Mat q1(magMat, Rect(0, cy, cx, cy));
    Mat q2(magMat, Rect(cx, cy, cx, cy));
    Mat q3(magMat, Rect(cx, 0, cx, cy));

    Mat tmp;
    q0.copyTo(tmp);
    q2.copyTo(q0);
    tmp.copyTo(q2);

    q1.copyTo(tmp);
    q3.copyTo(q1);
    tmp.copyTo(q3);

    //Normalize the magnitude to [0,1], then to[0,255]
    normalize(magMat, magMat, 0, 1, CV_MINMAX);
    Mat magImg(magMat.size(), CV_8UC1);
    magMat.convertTo(magImg, CV_8UC1, 255, 0);
    imshow("magnitude", magImg);
    //imwrite("imageText_mag.jpg",magImg);

    //Turn into binary image
    threshold(magImg, magImg, GRAY_THRESH, 255, CV_THRESH_BINARY);
    imshow("mag_binary", magImg);
    //imwrite("imageText_bin.jpg",magImg);

    //Find lines with Hough Transformation
    vector<Vec2f> lines;
    float pi180 = (float)CV_PI / 180;
    Mat linImg(magImg.size(), CV_8UC3);
    HoughLines(magImg, lines, 1, pi180, HOUGH_VOTE, 0, 0);
    int numLines = lines.size();
    for (int l = 0; l<numLines; l++)
    {
        float rho = lines[l][0], theta = lines[l][1];
        Point pt1, pt2;
        double a = cos(theta), b = sin(theta);
        double x0 = a*rho, y0 = b*rho;
        pt1.x = cvRound(x0 + 1000 * (-b));
        pt1.y = cvRound(y0 + 1000 * (a));
        pt2.x = cvRound(x0 - 1000 * (-b));
        pt2.y = cvRound(y0 - 1000 * (a));
        line(linImg, pt1, pt2, Scalar(255, 0, 0), 3, 8, 0);
    }
    imshow("lines", linImg);
    //imwrite("imageText_line.jpg",linImg);
    if (lines.size() == 3){
        cout << "found three angels:" << endl;
        cout << lines[0][1] * 180 / CV_PI << endl << lines[1][1] * 180 / CV_PI << endl << lines[2][1] * 180 / CV_PI << endl << endl;
    }

    //Find the proper angel from the three found angels
    float angel = 0;
    float piThresh = (float)CV_PI / 90;
    float pi2 = CV_PI / 2;
    for (int l = 0; l<numLines; l++)
    {
        float theta = lines[l][1];
        if (abs(theta) < piThresh || abs(theta - pi2) < piThresh)
            continue;
        else{
            angel = theta;
            break;
        }
    }

    //Calculate the rotation angel
    //The image has to be square,
    //so that the rotation angel can be calculate right
    angel = angel<pi2 ? angel : angel - CV_PI;
    if (angel != pi2){
        float angelT = srcImg.rows*tan(angel) / srcImg.cols;
        angel = atan(angelT);
    }
    float angelD = angel * 180 / (float)CV_PI;
    cout << "the rotation angel to be applied:" << endl << angelD << endl << endl;

    //Rotate the image to recover
    Mat rotMat = getRotationMatrix2D(center, angelD, 1.0);
    Mat dstImg = Mat::ones(srcImg.size(), CV_8UC3);
    warpAffine(srcImg, dstImg, rotMat, srcImg.size(), 1, 0, Scalar(255, 255, 255));
    imshow("result", dstImg);
    //imwrite("imageText_D.jpg",dstImg);

    waitKey(0);

    return 0;
}

opencv4x

#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgcodecs/legacy/constants_c.h> 
#include <iostream>

using namespace cv;
using namespace std;

// 二值化阈值
#define GRAY_THRESH 150

// 直线上点的个数
#define HOUGH_VOTE 50

int main(int argc, char **argv)
{
    //Read a single-channel image
    const char* filename = argv[1];
    Mat srcImg = imread(filename, CV_LOAD_IMAGE_GRAYSCALE);
    if (srcImg.empty())
        return -1;
    imshow("source", srcImg);

    Point center(srcImg.cols / 2, srcImg.rows / 2);

    //Expand image to an optimal size, for faster processing speed
    //Set widths of borders in four directions
    //If borderType==BORDER_CONSTANT, fill the borders with (0,0,0)
    Mat padded;
    int opWidth = getOptimalDFTSize(srcImg.rows);
    int opHeight = getOptimalDFTSize(srcImg.cols);
    copyMakeBorder(srcImg, padded, 0, opWidth - srcImg.rows, 0, opHeight - srcImg.cols, BORDER_CONSTANT, Scalar::all(0));

    Mat planes[] = { Mat_<float>(padded), Mat::zeros(padded.size(), CV_32F) };
    Mat comImg;
    //Merge into a double-channel image
    merge(planes, 2, comImg);

    //Use the same image as input and output,
    //so that the results can fit in Mat well
    dft(comImg, comImg);

    //Compute the magnitude
    //planes[0]=Re(DFT(I)), planes[1]=Im(DFT(I))
    //magnitude=sqrt(Re^2+Im^2)
    split(comImg, planes);
    magnitude(planes[0], planes[1], planes[0]);

    //Switch to logarithmic scale, for better visual results
    //M2=log(1+M1)
    Mat magMat = planes[0];
    magMat += Scalar::all(1);
    log(magMat, magMat);

    //Crop the spectrum
    //Width and height of magMat should be even, so that they can be divided by 2
    //-2 is 11111110 in binary system, operator & make sure width and height are always even
    magMat = magMat(Rect(0, 0, magMat.cols & -2, magMat.rows & -2));

    //Rearrange the quadrants of Fourier image,
    //so that the origin is at the center of image,
    //and move the high frequency to the corners
    int cx = magMat.cols / 2;
    int cy = magMat.rows / 2;

    Mat q0(magMat, Rect(0, 0, cx, cy));
    Mat q1(magMat, Rect(0, cy, cx, cy));
    Mat q2(magMat, Rect(cx, cy, cx, cy));
    Mat q3(magMat, Rect(cx, 0, cx, cy));

    Mat tmp;
    q0.copyTo(tmp);
    q2.copyTo(q0);
    tmp.copyTo(q2);

    q1.copyTo(tmp);
    q3.copyTo(q1);
    tmp.copyTo(q3);

    //Normalize the magnitude to [0,1], then to[0,255]
    //normalize(magMat, magMat, 0, 1, CV_MINMAX);
    normalize(magMat, magMat, 0, 1, NORM_MINMAX);
    Mat magImg(magMat.size(), CV_8UC1);
    magMat.convertTo(magImg, CV_8UC1, 255, 0);
    imshow("magnitude", magImg);
    //imwrite("imageText_mag.jpg",magImg);

    //Turn into binary image
    threshold(magImg, magImg, GRAY_THRESH, 255, cv::THRESH_BINARY);
    imshow("mag_binary", magImg);
    //imwrite("imageText_bin.jpg",magImg);

    //Find lines with Hough Transformation
    vector<Vec2f> lines;
    float pi180 = (float)CV_PI / 180;
    Mat linImg(magImg.size(), CV_8UC3);
    HoughLines(magImg, lines, 1, pi180, HOUGH_VOTE, 0, 0);
    int numLines = lines.size();
    for (int l = 0; l<numLines; l++)
    {
        float rho = lines[l][0], theta = lines[l][1];
        Point pt1, pt2;
        double a = cos(theta), b = sin(theta);
        double x0 = a*rho, y0 = b*rho;
        pt1.x = cvRound(x0 + 1000 * (-b));
        pt1.y = cvRound(y0 + 1000 * (a));
        pt2.x = cvRound(x0 - 1000 * (-b));
        pt2.y = cvRound(y0 - 1000 * (a));
        line(linImg, pt1, pt2, Scalar(255, 0, 0), 3, 8, 0);
    }
    imshow("lines", linImg);
    //imwrite("imageText_line.jpg",linImg);
    if (lines.size() == 3){
        cout << "found three angels:" << endl;
        cout << lines[0][1] * 180 / CV_PI << endl << lines[1][1] * 180 / CV_PI << endl << lines[2][1] * 180 / CV_PI << endl << endl;
    }

    //Find the proper angel from the three found angels
    float angel = 0;
    float piThresh = (float)CV_PI / 90;
    float pi2 = CV_PI / 2;
    for (int l = 0; l<numLines; l++)
    {
        float theta = lines[l][1];
        if (abs(theta) < piThresh || abs(theta - pi2) < piThresh)
            continue;
        else{
            angel = theta;
            break;
        }
    }

    //Calculate the rotation angel
    //The image has to be square,
    //so that the rotation angel can be calculate right
    angel = angel<pi2 ? angel : angel - CV_PI;
    if (angel != pi2){
        float angelT = srcImg.rows*tan(angel) / srcImg.cols;
        angel = atan(angelT);
    }
    //float angelD = angel * 180 / (float)CV_PI;
    float angelD = angel * 180 / (float)CV_PI;
    cout << "the rotation angel to be applied: "<< angelD << endl << endl;

    //Rotate the image to recover
    Mat rotMat = getRotationMatrix2D(center, angelD, 1.0);
    Mat dstImg = Mat::ones(srcImg.size(), CV_8UC3);
    warpAffine(srcImg, dstImg, rotMat, srcImg.size(), 1, 0, Scalar(255, 255, 255));
    imshow("result", dstImg);
    imwrite("imageText_D.jpg",dstImg);

    waitKey(0);

    return 0;
}

CMakeLists.txt

project( main )
cmake_minimum_required(VERSION 3.10) 
#添加头文件路径
include_directories(/usr/local/include /usr/local/include/opencv4 /usr/local/include/opencv4/opencv2)
#添加库文件路径
link_directories(/usr/local/lib)
 
add_executable(main test.cpp)
target_link_libraries( main -lopencv_core  -lopencv_highgui -lopencv_imgproc -lopencv_imgcodecs)

在这里插入图片描述
在这里插入图片描述

3.四点透视 + 角度 + 旋转

#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"
#include <iostream>
using namespace cv;
using namespace std;


// 直线上点的个数
#define HOUGH_VOTE 50

//度数转换
double DegreeTrans(double theta)
{
    double res = theta / CV_PI * 180;
    return res;
}


//逆时针旋转图像degree角度(原尺寸)    
void rotateImage(Mat src, Mat& img_rotate, double degree)
{
    //旋转中心为图像中心    
    Point2f center;
    center.x = float(src.cols / 2.0);
    center.y = float(src.rows / 2.0);
    int length = 0;
    length = sqrt(src.cols*src.cols + src.rows*src.rows);
    //计算二维旋转的仿射变换矩阵  
    Mat M = getRotationMatrix2D(center, degree, 1);
    warpAffine(src, img_rotate, M, Size(length, length), 1, 0, Scalar(255, 255, 255));//仿射变换,背景色填充为白色  
}

//通过霍夫变换计算角度
double CalcDegree(const Mat &srcImage, Mat &dst)
{
    Mat midImage, dstImage;

    Canny(srcImage, midImage, 50, 200, 3);
    cvtColor(midImage, dstImage, CV_GRAY2BGR);

    //通过霍夫变换检测直线
    vector<Vec2f> lines;
    HoughLines(midImage, lines, 1, CV_PI / 180, HOUGH_VOTE);//第5个参数就是阈值,阈值越大,检测精度越高
    //cout << lines.size() << endl;

    //由于图像不同,阈值不好设定,因为阈值设定过高导致无法检测直线,阈值过低直线太多,速度很慢
    //所以根据阈值由大到小设置了三个阈值,如果经过大量试验后,可以固定一个适合的阈值。
    float sum = 0;
    //依次画出每条线段
    for (size_t i = 0; i < lines.size(); i++)
    {
        float rho = lines[i][0];
        float theta = lines[i][1];
        Point pt1, pt2;
        //cout << theta << endl;
        double a = cos(theta), b = sin(theta);
        double x0 = a*rho, y0 = b*rho;
        pt1.x = cvRound(x0 + 1000 * (-b));
        pt1.y = cvRound(y0 + 1000 * (a));
        pt2.x = cvRound(x0 - 1000 * (-b));
        pt2.y = cvRound(y0 - 1000 * (a));
        //只选角度最小的作为旋转角度
        sum += theta;

        line(dstImage, pt1, pt2, Scalar(55, 100, 195), 1, LINE_AA); //Scalar函数用于调节线段颜色

        imshow("直线探测效果图", dstImage);
    }
    float average = sum / lines.size(); //对所有角度求平均,这样做旋转效果会更好

    cout << "average theta:" << average << endl;

    double angle = DegreeTrans(average) - 90;

    rotateImage(dstImage, dst, angle);
    //imshow("直线探测效果图2", dstImage);
    return angle;
}


void ImageRecify(const char* pInFileName, const char* pOutFileName)
{
    double degree;
    Mat src = imread(pInFileName);
    imshow("原始图", src);
    Mat dst;
    //倾斜角度矫正
    degree = CalcDegree(src, dst);
    rotateImage(src, dst, degree);
    cout << "angle:" << degree << endl;
    imshow("旋转调整后", dst);

    Mat resulyImage = dst(Rect(0, 0, dst.cols, 500)); //根据先验知识,估计好文本的长宽,再裁剪下来
    imshow("裁剪之后", resulyImage);
    imwrite("recified.jpg", resulyImage);
}


int main()
{
    ImageRecify("31.png", "FinalImage.jpg");
    waitKey();
    return 0;
}

opencv4.x

#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"
#include <iostream>
using namespace cv;
using namespace std;


// 直线上点的个数
#define HOUGH_VOTE 50

//度数转换
double DegreeTrans(double theta)
{
    double res = theta / CV_PI * 180;
    return res;
}


//逆时针旋转图像degree角度(原尺寸)    
void rotateImage(Mat src, Mat& img_rotate, double degree)
{
    //旋转中心为图像中心    
    Point2f center;
    center.x = float(src.cols / 2.0);
    center.y = float(src.rows / 2.0);
    int length = 0;
    length = sqrt(src.cols*src.cols + src.rows*src.rows);
    //计算二维旋转的仿射变换矩阵  
    Mat M = getRotationMatrix2D(center, degree, 1);
    warpAffine(src, img_rotate, M, Size(length, length), 1, 0, Scalar(255, 255, 255));//仿射变换,背景色填充为白色  
}

//通过霍夫变换计算角度
double CalcDegree(const Mat &srcImage, Mat &dst)
{
    Mat midImage, dstImage;

    Canny(srcImage, midImage, 50, 200, 3);
    cvtColor(midImage, dstImage, COLOR_GRAY2BGR);

    //通过霍夫变换检测直线
    vector<Vec2f> lines;
    HoughLines(midImage, lines, 1, CV_PI / 180, HOUGH_VOTE);//第5个参数就是阈值,阈值越大,检测精度越高
    //cout << lines.size() << endl;

    //由于图像不同,阈值不好设定,因为阈值设定过高导致无法检测直线,阈值过低直线太多,速度很慢
    //所以根据阈值由大到小设置了三个阈值,如果经过大量试验后,可以固定一个适合的阈值。
    float sum = 0;
    //依次画出每条线段
    for (size_t i = 0; i < lines.size(); i++)
    {
        float rho = lines[i][0];
        float theta = lines[i][1];
        Point pt1, pt2;
        //cout << theta << endl;
        double a = cos(theta), b = sin(theta);
        double x0 = a*rho, y0 = b*rho;
        pt1.x = cvRound(x0 + 1000 * (-b));
        pt1.y = cvRound(y0 + 1000 * (a));
        pt2.x = cvRound(x0 - 1000 * (-b));
        pt2.y = cvRound(y0 - 1000 * (a));
        //只选角度最小的作为旋转角度
        sum += theta;

        line(dstImage, pt1, pt2, Scalar(55, 100, 195), 1, LINE_AA); //Scalar函数用于调节线段颜色

        imshow("直线探测效果图", dstImage);
    }
    float average = sum / lines.size(); //对所有角度求平均,这样做旋转效果会更好

    cout << "average theta:" << average << endl;

    double angle = DegreeTrans(average) - 90;

    rotateImage(dstImage, dst, angle);
    //imshow("直线探测效果图2", dstImage);
    return angle;
}


void ImageRecify(const char* pInFileName, const char* pOutFileName)
{
    double degree;
    Mat src = imread(pInFileName);
    imshow("原始图", src);
    Mat dst;
    //倾斜角度矫正
    degree = CalcDegree(src, dst);
    rotateImage(src, dst, degree);
    cout << "angle:" << degree << endl;
    imshow("旋转调整后", dst);

    Mat resulyImage = dst(Rect(0, 0, dst.cols, 1000)); //根据先验知识,估计好文本的长宽,再裁剪下来
    imshow("裁剪之后", resulyImage);
    imwrite("recified.jpg", resulyImage);
}


int main()
{
    ImageRecify("test.jpg", "FinalImage.jpg");
    waitKey();
    return 0;
}

4.检测矩形轮廓 + 角度 + 旋转

#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"
#include <iostream>
using namespace cv;
using namespace std;
#include <algorithm>

bool x_sort(const Point2f & m1, const Point2f & m2)
{
    return m1.x < m2.x;
}


//第一个参数:输入图片名称;第二个参数:输出图片名称
void GetContoursPic(const char* pSrcFileName, const char* pDstFileName)
{
    Mat srcImg = imread(pSrcFileName);
    imshow("原始图", srcImg);
    Mat gray, binImg;
    //灰度化
    cvtColor(srcImg, gray, COLOR_RGB2GRAY);
    imshow("灰度图", gray);
    //二值化
    threshold(gray, binImg, 150, 200, CV_THRESH_BINARY);
    imshow("二值化", binImg);

    vector<Point>  contours;
    vector<vector<Point> > f_contours;
    
    //注意第5个参数为CV_RETR_EXTERNAL,只检索外框  
    findContours(binImg, f_contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE); //找轮廓

    int max_area = 0;
    int index;
    for (int i = 0; i < f_contours.size(); i++)
    {
        double tmparea = fabs(contourArea(f_contours[i]));
        if (tmparea > max_area)
        {
            index = i;
            max_area = tmparea;
        }

    }

    contours = f_contours[index];
    CvBox2D rect = minAreaRect(Mat(contours));


    float angle = rect.angle;
    cout << "before angle : " << angle << endl;
    if (angle < -45)
        angle = (90 + angle);
    else
        angle = -angle;
    cout << "after angle : " << angle << endl;
    
    //新建一个感兴趣的区域图,大小跟原图一样大  
    Mat RoiSrcImg(srcImg.rows, srcImg.cols, CV_8UC3); //注意这里必须选CV_8UC3
    RoiSrcImg.setTo(0); //颜色都设置为黑色  
    //imshow("新建的ROI", RoiSrcImg);
    //对得到的轮廓填充一下  
    drawContours(binImg, f_contours, 0, Scalar(255), CV_FILLED);

    //抠图到RoiSrcImg
    srcImg.copyTo(RoiSrcImg, gray);


    //再显示一下看看,除了感兴趣的区域,其他部分都是黑色的了  
    namedWindow("RoiSrcImg", 1);
    imshow("RoiSrcImg", RoiSrcImg);

    //创建一个旋转后的图像  
    Mat RatationedImg(RoiSrcImg.rows, RoiSrcImg.cols, CV_8UC1);
    RatationedImg.setTo(0);
    //对RoiSrcImg进行旋转  
    Point2f center = rect.center;  //中心点  
    Mat M2 = getRotationMatrix2D(center, angle, 1);//计算旋转加缩放的变换矩阵 
    warpAffine(RoiSrcImg, RatationedImg, M2, RoiSrcImg.size(), 1, 0, Scalar(0));//仿射变换 
    imshow("旋转之后", RatationedImg);
}

void main()
{
    GetContoursPic("34.png", "FinalImage.jpg");
    waitKey();
}

opencv4.x

#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"
#include <opencv2/imgproc/types_c.h>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <iostream>

using namespace cv;
using namespace std;
#include <algorithm>

bool x_sort(const Point2f & m1, const Point2f & m2)
{
    return m1.x < m2.x;
}


//第一个参数:输入图片名称;第二个参数:输出图片名称
void GetContoursPic(const char* pSrcFileName, const char* pDstFileName)
{
    Mat srcImg = imread(pSrcFileName);
    imshow("原始图", srcImg);
    Mat gray, binImg;
    //灰度化
    cvtColor(srcImg, gray, COLOR_RGB2GRAY);
    imshow("灰度图", gray);
    //二值化
    threshold(gray, binImg, 150, 200, cv::THRESH_BINARY);
    imshow("二值化", binImg);

    vector<Point>  contours;
    vector<vector<Point> > f_contours;
    
    //注意第5个参数为CV_RETR_EXTERNAL,只检索外框  
    findContours(binImg, f_contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE); //找轮廓

    int max_area = 0;
    int index;
    for (int i = 0; i < f_contours.size(); i++)
    {
        double tmparea = fabs(contourArea(f_contours[i]));
        if (tmparea > max_area)
        {
            index = i;
            max_area = tmparea;
        }

    }

    contours = f_contours[index];
    RotatedRect rect = minAreaRect(Mat(contours));


    float angle = rect.angle;
    cout << "before angle : " << angle << endl;
    if (angle < -45)
        angle = (90 + angle);
    else
        angle = -angle;
    cout << "after angle : " << angle << endl;
    
    //新建一个感兴趣的区域图,大小跟原图一样大  
    Mat RoiSrcImg(srcImg.rows, srcImg.cols, CV_8UC3); //注意这里必须选CV_8UC3
    RoiSrcImg.setTo(0); //颜色都设置为黑色  
    //imshow("新建的ROI", RoiSrcImg);
    //对得到的轮廓填充一下  
    drawContours(binImg, f_contours, 0, Scalar(255), cv::FILLED);

    //抠图到RoiSrcImg
    srcImg.copyTo(RoiSrcImg, gray);


    //再显示一下看看,除了感兴趣的区域,其他部分都是黑色的了  
    namedWindow("RoiSrcImg", 1);
    imshow("RoiSrcImg", RoiSrcImg);

    //创建一个旋转后的图像  
    Mat RatationedImg(RoiSrcImg.rows, RoiSrcImg.cols, CV_8UC1);
    RatationedImg.setTo(0);
    //对RoiSrcImg进行旋转  
    Point2f center = rect.center;  //中心点  
    Mat M2 = getRotationMatrix2D(center, angle, 1);//计算旋转加缩放的变换矩阵 
    warpAffine(RoiSrcImg, RatationedImg, M2, RoiSrcImg.size(), 1, 0, Scalar(0));//仿射变换 
    imshow("旋转之后", RatationedImg);
    imwrite("recified.jpg", RatationedImg);
}

int main()
{
    GetContoursPic("test.jpg", "FinalImage.jpg");
    waitKey();
    return 0;
}

参考

  • 榴莲小怪兽 opencv-图片矫正
  • OpenCV利用透视变换矫正图像

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/82625.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【前端】vscode javascript 代码片段失效问题解决

1. 文件--首选项--用户代码片段-vue.json : 添加 // { // // Place your global snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and // // description. Add comma separated ids of the languages where the snippet is app…

C语言:分支语句和循环语句(超详解)

目录 ​编辑 什么是语句&#xff1f; 分支语句&#xff08;选择结构&#xff09; if语句&#xff1a; 应该注意的是&#xff1a; switch语句&#xff1a; 运用练习&#xff1a; 循环语句 while循环&#xff1a; for循环&#xff1a; break和continue在for循环中&…

Linux(入门篇)

Linux&#xff08;入门篇&#xff09; Linux概述Linux是什么Linux的诞生Linux和Unix的渊源GNU/LinuxLinux的发行版Linux VS Windows Linux概述 Linux是什么 Linux是一个操作系统(OS) Linux的诞生 作者&#xff1a;李纳斯托瓦兹&#xff08;git也是他开发的&#x1f602;&am…

Excel/PowerPoint柱状图条形图负值设置补色

原始数据&#xff1a; 列1系列 1类别 14.3类别 2-2.5类别 33.5类别 44.5 默认作图 解决方案 1、选中柱子&#xff0c;双击&#xff0c;按如下顺序操作 2、这时候颜色会由一个变成两个 3、对第二个颜色进行设置&#xff0c;即为负值的颜色 条形图的设置方法相同

Unity C# 引用池 ReferencePool

Unity C# 引用池 ReferencePool 1.目的 对于多次创建的数据使用new 关键字是十分消耗性能的&#xff0c;使用完成后由GC去自动释放&#xff0c;当一个类型的数据频繁创建可以使用引用池进行管理。 2.实现 项目目录 IReference 接口 要放入引用池的数据只需要继承这个接口…

图床项目进度(一)——UI首页

1. 前言 前面我不是说了要做一个图床吗&#xff0c;现在在做ui。 我vue水平不够高&#xff0c;大部分参考b站项目照猫画虎。 vue实战后台 我使用ts&#xff0c;vite&#xff0c;vue3进行了重构。 当然&#xff0c;我对这些理解并不深刻&#xff0c;许多代码都是游离于表面&am…

经验分享:企业数据仓库建设方案总结!

导读 在企业的数字化转型浪潮中&#xff0c;数据被誉为“新时代的石油”&#xff0c;而数据仓库作为数据管理与分析的核心基础设施&#xff0c;在企业的信息化建设中扮演着重要的角色。本文将深入探讨企业数据仓库建设过程中所遇到的问题以及解决经验&#xff0c;为正在筹备或…

shell脚本——文件三剑客之sed

目录 一.sed基本用法及选项 ​二.sed脚本语法及命令 三.sed的查找替换使用 四.后向引用 五.变量 一.sed基本用法及选项 sed [选项]... {自身脚本语法};.... [input file...] seq 10 |sed #生成1-10数字传给sed #该格式报错&#xff0c;基本格式中的{自身脚本语法}不…

Python之Qt输出UI

安装PySide2 输入pip install PySide2安装Qt for Python&#xff0c;如果安装过慢需要翻墙&#xff0c;则可以使用国内清华镜像下载&#xff0c;输入命令pip install --user -i https://pypi.tuna.tsinghua.edu.cn/simple PySide2&#xff0c;如下图&#xff0c; 示例Demo i…

移远通信推出一站式Matter解决方案,构建智能家居开放新生态

近日&#xff0c;全球领先的S物联网整体解决方案供应商移远通信宣布&#xff0c;正式推出全新Matter解决方案&#xff0c;从模组、APP、平台、认证、生产五大层面为客户提供一站式服务&#xff0c;赋能智能家居行业加快融合发展。 过去十年&#xff0c;得益于物联网生态的发展&…

多线程实现与管理

进程与线程 进程 &#xff1a; 进程是操作系统进行资源分配的最小单位&#xff0c;每执行一个程序、一条命令操作系统都会启动一个进程&#xff0c;进程是一个程序的执行过程&#xff0c;当程序启动时&#xff0c;操作系统会把进程的代码加载到内存中&#xff0c;并为新进程分配…

SpringBoot+微信小程序奶茶在线点单小程序系统 附带详细运行指导视频

文章目录 一、项目演示二、项目介绍三、运行截图四、主要代码 一、项目演示 项目演示地址&#xff1a; 视频地址 二、项目介绍 项目描述&#xff1a;这是一个基于SpringBoot微信小程序框架开发的奶茶在线点单小程序系统。首先&#xff0c;这是一个前后端分离的项目&#xff…

stm32单片机开关控制LED灯(中断方式)(proteus电路图)

注意了&#xff1a;一般人都是用按键button实现这个功能&#xff0c;但是我就是喜欢用Switch&#xff0c;然后我就用了Switch&#xff0c;喜欢的朋友欢迎看一看 不同地方在于&#xff1a;这里是interrupt 函数 void EXTI0_IRQHandler(void) {/* USER CODE BEGIN EXTI0_IRQn 0…

excel 核心快捷键用法

1、wps怎样只复制公示计算出来的数据 1.1、按下快捷键“CtrlC”&#xff0c;复制该单元格。 1.2、按下快捷键“ShiftCtrlV”&#xff0c;即“粘贴为数值”&#xff0c;即可只复制数字而不复制该单元格的公式 1.3、wps怎样只复制公示计算出来的数据_百度知道https://zhidao.baid…

ubuntu20搭建环境使用的一下指令

1.更新源 sudo vim etc/apt/sources.listdeb http://mirrors.aliyun.com/ubuntu/ xenial main deb-src http://mirrors.aliyun.com/ubuntu/ xenial maindeb http://mirrors.aliyun.com/ubuntu/ xenial-updates main deb-src http://mirrors.aliyun.com/ubuntu/ xenial-updates…

sh 脚本循环语句和正则表达式

目录 1、循环语句 1、for 2、while 3、until 2、正则表达式 1、元字符 2、表示次数 3、位置锚定 4、分组 5、扩展正则表达式 1、循环语句 循环含义 将某代码段重复运行多次&#xff0c;通常有进入循环的条件和退出循环的条件 重复运行次数 循环次数事先已知 循环次…

美团——城市低空物流无人机的设计挑战与应对

城市低空物流无人机的设计挑战与应对 强度分析 振动影响 动力设计 噪声设计 冗余备份更加性价比&#xff0c;便宜好实现 航电系统 动力系统的冗余 电池系统的冗余 通讯系统等冗余 降落冗余 安全降落 计算高效 产线标定 底层基础库 离线系统 行业公开测评 未来展望 – 导航定…

线性代数的学习和整理8: 方阵和行列式相关(草稿-----未完成)

1.4.1 方阵 矩阵里&#xff0c;行数列数的矩阵叫做方阵方阵有很多很好的特殊属性 1.4.2 行列式 行列式是方阵的一种特殊运算如果矩阵行数列数相等&#xff0c;那么这个矩阵是方阵。行列数的计算方式和矩阵的不同只有方阵才有行列式行列式其实是&#xff0c;矩阵变化的一个面…

SSM框架的学习与应用(Spring + Spring MVC + MyBatis)-Java EE企业级应用开发学习记录(第一天)Mybatis的学习

SSM框架的学习与应用(Spring Spring MVC MyBatis)-Java EE企业级应用开发学习记录&#xff08;第一天&#xff09;Mybatis的学习 一、当前的主流框架介绍(这就是后期我会发出来的框架学习) Spring框架 ​ Spring是一个开源框架&#xff0c;是为了解决企业应用程序开发复杂…

五、Spring MVC 接收请求参数以及数据回显、乱码问题

文章目录 一、Spring MVC 接收请求参数二、Spring MVC 数据回显三、SpringMVC 返回中文乱码问题 一、Spring MVC 接收请求参数 客户端或者前端通过 URL 请求传递过来的参数&#xff0c;在控制器中如何接收&#xff1f; 1、当参数和 Controller 中的方法参数一致时&#xff0c;无…