天天看點

OpenCV + CPP 系列(廿四)圖像的翻轉(鏡像),平移,旋轉,仿射,透視變換

​​官網教程​​

文章目錄

  • ​​一、翻轉(鏡像)​​
  • ​​二、仿射扭曲​​
  • ​​擷取變換矩陣​​
  • ​​仿射扭曲函數 warpAffine​​
  • ​​旋轉​​
  • ​​平移​​
  • ​​三、仿射變換​​
  • ​​四、透視變換​​
  • ​​綜合示例​​

一、翻轉(鏡像)

頭檔案 ​

​quick_opencv.h​

​:聲明類與公共函數

#pragma once
#include <opencv2\opencv.hpp>
using namespace cv;

class QuickDemo {
public:
  ...
  void flip_Demo(Mat& image);
  void rotate_Demo(Mat& image);
  void move_Demo(Mat& image);
  void Affine_Demo(Mat& image);
  void toushi_Demo(Mat& image);
  void perspective_detect(Mat& image);

};      

主函數調用該類的公共成員函數

#include <opencv2\opencv.hpp>
#include <quick_opencv.h>
#include <iostream>
using namespace cv;


int main(int argc, char** argv) {
  Mat src = imread("D:\\Desktop\\pandas.jpg");
  if (src.empty()) {
    printf("Could not load images...\n");
    return -1;
  }
  namedWindow("input", WINDOW_NORMAL);
  imshow("input", src);

  QuickDemo qk;

  ...
  qk.Affine_Demo(src);
  qk.move_Demo(src);
  qk.flip_Demo(src);
  qk.toushi_Demo(src);
  qk.perspective_detect(src);

  waitKey(0);
  destroyAllWindows();
  return 0;
}      

源檔案 ​

​quick_demo.cpp​

​:實作類與公共函數

void QuickDemo::flip_Demo(Mat& image) {
  Mat dst0, dst1, dst2;
  flip(image, dst0, 0);
  flip(image, dst1, 1);
  flip(image, dst2, -1);
  imshow("dst0_上下翻轉", dst0);
  imshow("dst1_左右翻轉", dst1);
  imshow("dst2_對角線翻轉", dst2);  //旋轉180度
}      
OpenCV + CPP 系列(廿四)圖像的翻轉(鏡像),平移,旋轉,仿射,透視變換

二、仿射扭曲

二維圖像一般情況下的變換矩陣(旋轉+平移),當我們隻需要平移的時候,取 的值為0,a和b的值就代表了圖像沿x軸和y軸移動的距離;其中原圖 Scalar=1(原圖大小,不執行縮放)

擷取變換矩陣

變換矩陣計算:

其中:

Point2f center,      源圖像中旋轉的中心

double angle,      角度以度為機關的旋轉角度。正值表示逆時針旋轉(坐标原點假定為左上角)。

double scale     各向同性比例因子。

)

仿射扭曲函數 warpAffine

函數簽名

InputArray

src,              輸入矩陣

OutputArray

dst,            輸出矩陣

InputArray

M,              2×3 變換矩陣

Size

dsize,              輸出圖像大小

int

flags = INTER_LINEAR,       插值方式:預設線性插值

int

borderMode = BORDER_CONSTANT, 邊緣處理方式

const Scalar&

borderValue = Scalar()   邊緣填充值,預設=0

);

保留所有原圖像素的旋轉,原理:

new_h =

OpenCV + CPP 系列(廿四)圖像的翻轉(鏡像),平移,旋轉,仿射,透視變換

旋轉

void QuickDemo::rotate_Demo(Mat& image) {
  Mat dst_0, dst_1, M;
  int h = image.rows;
  int w = image.cols;
  M = getRotationMatrix2D(Point(w / 2, h / 2), 45, 1.0);
  warpAffine(image, dst_0, M, image.size());

  double cos = abs(M.at<double>(0, 0));
  double sin = abs(M.at<double>(0, 1));

  int new_w = cos * w + sin * h;
  int new_h = cos * h + sin * w;
  M.at<double>(0, 2) += (new_w / 2.0 - w / 2);
  M.at<double>(1, 2) += (new_h / 2.0 - h / 2);
  warpAffine(image, dst_1, M, Size(new_w, new_h), INTER_LINEAR, 0, Scalar(255, 255, 0));
  imshow("旋轉示範0", dst_0);
  imshow("旋轉示範1", dst_1);
}      

依次為:原圖,旋轉45度,保留所有原圖像素的旋轉45度

OpenCV + CPP 系列(廿四)圖像的翻轉(鏡像),平移,旋轉,仿射,透視變換

平移

void QuickDemo::move_Demo(Mat& image) {
  Mat dst_move;
  Mat move_mat = (Mat_<double>(2, 3) << 1, 0, 10, 0, 1, 30);//沿x軸移動10沿y軸移動30
  warpAffine(image, dst_move, move_mat, image.size());
  imshow("dst_move", dst_move);

  double angle_ = 3.14159265354 / 16.0;
  cout << "pi=" << cos(angle_) << endl;
  Mat rota_mat = (Mat_<double>(2, 3) << cos(angle_), -sin(angle_), 1, sin(angle_), cos(angle_), 1);
  warpAffine(image, rotate_dst, rota_mat, image.size());
  imshow("rotate_dst", rotate_dst);
}      
OpenCV + CPP 系列(廿四)圖像的翻轉(鏡像),平移,旋轉,仿射,透視變換

三、仿射變換

Mat getAffineTransform(    傳回變換矩陣

const Point2f src[],      變換前三個點的數組

const Point2f dst[]     變換後三個點的數組

);

void QuickDemo::Affine_Demo(Mat& image) {
  Mat warp_dst;
  Mat warp_mat(2, 3, CV_32FC1);

  Point2f srcTri[3];
  Point2f dstTri[3];

  /// 設定源圖像和目标圖像上的三組點以計算仿射變換
  srcTri[0] = Point2f(0, 0);
  srcTri[1] = Point2f(image.cols - 1, 0);
  srcTri[2] = Point2f(0, image.rows - 1);
  for (size_t i = 0; i < 3; i++){
    circle(image, srcTri[i], 2, Scalar(0, 0, 255), 5, 8);
  }
  
  dstTri[0] = Point2f(image.cols * 0.0, image.rows * 0.13);
  dstTri[1] = Point2f(image.cols * 0.95, image.rows * 0.15);
  dstTri[2] = Point2f(image.cols * 0.15, image.rows * 0.9);

  warp_mat = getAffineTransform(srcTri, dstTri);
  warpAffine(image, warp_dst, warp_mat, warp_dst.size());
  imshow("warp_dst", warp_dst);
}      
OpenCV + CPP 系列(廿四)圖像的翻轉(鏡像),平移,旋轉,仿射,透視變換

四、透視變換

擷取透射變換的矩陣:

Mat getPerspectiveTransform(   傳回變換矩陣

const Point2f src[],     透視變換前四個點的

數組

const Point2f dst[],     透視變換後四個點的

數組

int solveMethod = DECOMP_LU

)

透射變換

InputArray src,         原圖像

OutputArray dst,         傳回圖像

InputArray M,           透視變換矩陣

Size dsize,          傳回圖像的大小(寬,高)

int flags = INTER_LINEAR,   插值方法

int borderMode = BORDER_CONSTANT,  邊界處理

const Scalar& borderValue = Scalar()    縮放處理

)

void QuickDemo::toushi_Demo(Mat& image) {
  Mat toushi_dst, toushi_mat;
  Point2f toushi_before[4];
  toushi_before[0] = Point2f(122, 220);
  toushi_before[1] = Point2f(397, 121);
  toushi_before[2] = Point2f(133, 339);
  toushi_before[3] = Point2f(397, 218);

  int width_0  = toushi_before[1].x - toushi_before[0].x;
  int height_0 = toushi_before[1].y - toushi_before[0].y;
  int width_1 = toushi_before[2].x - toushi_before[0].x;
  int height_1 = toushi_before[2].y - toushi_before[0].y;

  int width = (int)sqrt(width_0 * width_0 + height_0 * height_0);
  int height = (int)sqrt(width_1 * width_1 + height_1 * height_1);

  Point2f toushi_after[4];
  toushi_after[0] = Point2f(2, 2);                    // x0, y0
  toushi_after[1] = Point2f(width+2, 2);              // x1, y0
  toushi_after[2] = Point2f(2, height+2);             // x0, y1
  toushi_after[3] = Point2f(width + 2, height + 2);   // x1, y1

  for (size_t i = 0; i < 4; i++){
    cout << toushi_after[i] << endl;
  }

  toushi_mat = getPerspectiveTransform(toushi_before, toushi_after);
  warpPerspective(image, toushi_dst, toushi_mat, Size(width, height));
  imshow("toushi_dst", toushi_dst);
}      
OpenCV + CPP 系列(廿四)圖像的翻轉(鏡像),平移,旋轉,仿射,透視變換

綜合示例

自動化透視矯正圖像:

流程:

  1. 灰階化二值化
  2. 形态學去除噪點
  3. 擷取輪廓
  4. 檢測直線
  5. 計算直線交點
  6. 擷取四個透視頂點
  7. 透視變換
  8. OpenCV + CPP 系列(廿四)圖像的翻轉(鏡像),平移,旋轉,仿射,透視變換
inline void Intersection(Point2i& interPoint, Vec4i& line1, Vec4i& line2) {
  // x1, y1, x2, y2 = line1[0], line1[1], line1[2], line1[3]

  int A1 = line1[3] - line1[1];
  int B1 = line1[0] - line1[2];
  int C1 = line1[1] * line1[2] - line1[0] * line1[3];

  int A2 = line2[3] - line2[1];
  int B2 = line2[0] - line2[2];
  int C2 = line2[1] * line2[2] - line2[0] * line2[3];

  interPoint.x = static_cast<int>((B1 * C2 - B2 * C1) / (A1 * B2 - A2 * B1));
  interPoint.y = static_cast<int>((C1 * A2 - A1 * C2) / (A1 * B2 - A2 * B1));
}



void QuickDemo::perspective_detect(Mat& image) {
  Mat gray_dst, binary_dst, morph_dst;
  // 二值化
  cvtColor(image, gray_dst, COLOR_BGR2GRAY);
  threshold(gray_dst, binary_dst, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);

  //形态學操作
  Mat kernel = getStructuringElement(MORPH_RECT, Size(5, 5));
  morphologyEx(binary_dst, morph_dst, MORPH_CLOSE, kernel, Point(-1, -1), 3);
  bitwise_not(morph_dst, morph_dst);
  imshow("morph_dst2", morph_dst);

  //輪廓查找與可視化
  vector<vector<Point>> contours;
  vector<Vec4i> hierarches;
  int height = image.rows;
  int width = image.cols;
  Mat contours_Img = Mat::zeros(image.size(), CV_8UC3);
  findContours(morph_dst, contours, hierarches, RETR_TREE, CHAIN_APPROX_SIMPLE);
  for (size_t i = 0; i < contours.size(); i++){
    Rect rect = boundingRect(contours[i]);
    if (rect.width > width / 2 && rect.width < width - 5) {
      drawContours(contours_Img, contours, i, Scalar(0, 0, 255), 2, 8, hierarches, 0, Point());
    }
  }
  imshow("contours_Img", contours_Img);

  vector<Vec4i> lines;
  Mat houghImg;
  int accu = min(width * 0.5, height * 0.5);
  cvtColor(contours_Img, houghImg, COLOR_BGR2GRAY);
  HoughLinesP(houghImg, lines, 1, CV_PI / 180, accu, accu*0.6, 0);

  Mat lineImg = Mat::zeros(image.size(), CV_8UC3);
  for (size_t i = 0; i < lines.size(); i++){
    Vec4i ln = lines[i];
    line(lineImg, Point(ln[0], ln[1]), Point(ln[2], ln[3]), Scalar(0, 0, 255), 2, 8, 0);
  }

  // 尋找與定位上下左右四條直線
  int delta = 0;
  Vec4i topline = { 0, 0, 0, 0 };
  Vec4i bottomline;
  Vec4i leftline, rightline;
  for (size_t i = 0; i < lines.size(); i++) {
    Vec4i ln = lines[i];
    delta = abs(ln[3] - ln[1]); // y2-y1

    //topline
    if (ln[3] < height / 2.0 && ln[1] < height / 2.0 && delta < accu - 1) {
      if (topline[3] > ln[3] && topline[3] > 0) {
        topline = lines[i];
      }
      else {
        topline = lines[i];
      }
    }
    if (ln[3] > height / 2.0 && ln[1] > height / 2.0 && delta < accu - 1) {
      bottomline = lines[i];
    }
    if (ln[0] < width / 2.0 && ln[2] < width / 2.0) {
      leftline = lines[i];
    }
    if (ln[0] > width / 2.0 && ln[2] > width / 2.0) {
      rightline = lines[i];
    }
  }

  cout << "topline: " << topline << endl;
  cout << "bottomline: " << bottomline << endl;
  cout << "leftline: " << leftline << endl;
  cout << "rightline: " << rightline << endl;

  // 計算上述四條直線交點(兩條線的交點:依次為左上,右上,左下,右下)
  Point2i p0, p1, p2, p3;
  Intersection(p0, topline, leftline);
  Intersection(p1, topline, rightline);
  Intersection(p2, bottomline, leftline);
  Intersection(p3, bottomline, rightline);

  circle(lineImg, p0, 2, Scalar(255, 0, 0), 2, 8, 0);
  circle(lineImg, p1, 2, Scalar(255, 0, 0), 2, 8, 0);
  circle(lineImg, p2, 2, Scalar(255, 0, 0), 2, 8, 0);
  circle(lineImg, p3, 2, Scalar(255, 0, 0), 2, 8, 0);
  imshow("Intersection", lineImg);

  //透視變換
  vector<Point2f> src_point(4);
  src_point[0] = p0;
  src_point[1] = p1;
  src_point[2] = p2;
  src_point[3] = p3;

  int new_height = max(abs(p2.y - p0.y), abs(p3.y - p1.y));
  int new_width = max(abs(p1.x - p0.x), abs(p3.x - p2.x));
  cout << "new_height = " << new_height << endl;
  cout << "new_width = " << new_width << endl;
  
  vector<Point2f> dst_point(4);
  dst_point[0] = Point(0,0);
  dst_point[1] = Point(new_width, 0);
  dst_point[2] = Point(0, new_height);
  dst_point[3] = Point(new_width, new_height);
  
  Mat resultImg;
  Mat wrap_mat = getPerspectiveTransform(src_point, dst_point);
  warpPerspective(image, resultImg, wrap_mat, Size(new_width, new_height));
  imshow("resultImg", resultImg);
}      

關鍵步驟可視化

OpenCV + CPP 系列(廿四)圖像的翻轉(鏡像),平移,旋轉,仿射,透視變換
OpenCV + CPP 系列(廿四)圖像的翻轉(鏡像),平移,旋轉,仿射,透視變換