zl程序教程

您现在的位置是:首页 >  其他

当前栏目

C++ OpenCV手动截取图像做透视变换

2023-02-18 16:47:31 时间

学更好的别人,

做更好的自己。

——《微卡智享》

本文长度为2683,预计阅读6分钟

前言

以前文章《C++ OpenCV检测并提取数字华容道棋盘》中有部分是用到了透视变换,不过因为在自己适应边缘检测中,有些图片干扰项太多,导致想要的东西提取不出来,于是这篇就是做了一个手动载取位置来做透视变换的小练习。

实现效果

从上图中可以看出,手动点击4个位置点画的蓝色四边形框后,针对这个图像做了透视变换的效果,也是最终想要的结果,接下来就看看怎么实现的。

微卡智享

关键问题的Q&A

实现手动点击截取图像进行透视变换注意点?

A

1. 鼠标事件,每切换图像时需要保证定义的Point2f指针都要初始化清零,这样在点击的时候可以自己判断给哪一个点赋值了。

2. 当4个点都完成后,需要根据点的位置采用欧式距离计算矩形的宽度和高度。

3. 需要注意点击的顺序,现在做的都是从左上顺时针方向开始点击的,如果不是按照这个方案,透视变换会有问题,当时源码中CvUtils类中有一个以前写的排序的函数,不过这里没用到。

代码实现

微卡智享

main.cpp代码

#pragma once
#include <iostream>
#include <opencv2/opencv.hpp>
#include "../../Utils/CvUtils.h"


using namespace std;
using namespace cv;

//设置图片所以路径
String FilePaths = "D:/Business/DemoTEST/CPP/OpenCVDemoCpp/OpenCVSplitImage/pic";
//获取目录下的所有文件
vector<String> files;


//鼠标回调函数
void onMouse(int event, int x, int y, int flags, void* ustc);

Mat src;
Mat srccopy; //用于拷贝出的源图像
string showsrc = "图像";
int imgindex = 0;
//设置透视变换的点
Point2f vertices[4];


//给透视变换点进行赋值,返回值为3时,说明4个点都已经赋值了,可以进行下一步操作
int setPerspectivePoint(Point2f* vts, int x, int y, bool isinit = false);


int main(int argc, char** argv) {
  glob(FilePaths, files);

  if (files.size() <= 0) {
    cout << "找不到图片文件" << endl;
    waitKey(0);
    return -1;
  }

  //初始化透视变换的点
  setPerspectivePoint(vertices, 0, 0, true);

  //关闭所有显示窗口
  destroyAllWindows();

  cout << "srcindex:" << imgindex << endl;
  String file = files[imgindex];
  src = imread(file);
  CvUtils::MatResize(src);
  CvUtils::SetShowWindow(src, showsrc, 50, 20);
  imshow(showsrc, src);
  //复制下源图
  src.copyTo(srccopy);

  //设置鼠标响影事件
  setMouseCallback(showsrc, onMouse);

  waitKey(0);

  return 0;
}


void onMouse(int event, int x, int y, int flags, void* ustc)
{
  //鼠标左键按下
  if (event == EVENT_LBUTTONUP)
  {
    //设置选择的点
    int ptindex = setPerspectivePoint(vertices, x, y);

    //在图像上画出点击位置
    circle(src, Point(x, y), 3, Scalar(255, 0, 0), -1);
    //选中的点进行画线
    if (ptindex > 0 && ptindex <= 3) {
      line(src, vertices[ptindex], vertices[ptindex - 1], Scalar(255, 0, 0), 3);
      //当是最后一个点时和起始点进行画线连接
      if (ptindex == 3) {
        line(src, vertices[ptindex], vertices[0], Scalar(255, 0, 0), 3);
      }
    }

    imshow(showsrc, src);

    if (ptindex == 3) {
      //根据最小矩形和多边形拟合的最大四个点计算透视变换矩阵    
      Point2f rectPoint[4];
      //计算旋转矩形的宽和高
      float rWidth = CvUtils::CalcPointDistance(vertices[0], vertices[1]);
      float rHeight = CvUtils::CalcPointDistance(vertices[1], vertices[2]);
      //计算透视变换的四个顶点
      rectPoint[0] = Point2f(0, 0);
      rectPoint[1] = rectPoint[0] + Point2f(rWidth, 0);
      rectPoint[2] = rectPoint[1] + Point2f(0, rHeight);
      rectPoint[3] = rectPoint[0] + Point2f(0, rHeight);


      //计算透视变换矩阵    
      Mat warpmatrix = getPerspectiveTransform(vertices, rectPoint);
      Mat resultimg;
      //透视变换
      warpPerspective(srccopy, resultimg, warpmatrix, resultimg.size(), INTER_LINEAR);

      //载取透视变换后的图像显示出来
      Rect cutrect = Rect(rectPoint[0], rectPoint[2]);
      Mat cutMat = resultimg(cutrect);

      CvUtils::SetShowWindow(cutMat, "cutMat", 600, 20);
      imshow("cutMat", cutMat);
    }

  }
  else if (event == EVENT_RBUTTONUP) {
    //初始化透视变换的点
    setPerspectivePoint(vertices, 0, 0, true);

    imgindex++;
    if (imgindex < files.size()) {
      //关闭所有显示窗口
      destroyAllWindows();

      cout << "srcindex:" << imgindex << endl;
      String file = files[imgindex];
      src = imread(file);
      CvUtils::MatResize(src);
      CvUtils::SetShowWindow(src, showsrc, 50, 20);
      imshow(showsrc, src);

      //复制下源图
      src.copyTo(srccopy);

      //设置鼠标响影事件
      setMouseCallback(showsrc, onMouse);
    }
    waitKey(0);
  }
}


//给透视变换点进行赋值,返回值为true时,说明4个点都已经赋值了,可以进行下一步操作
int setPerspectivePoint(Point2f* vts, int x, int y, bool isinit)
{
  int res = 0;
  if (isinit) {
    for (int i = 0; i < 4; ++i) {
      vts[i].x = -1.0f;
      vts[i].y = -1.0f;
    }
  }
  else {
    for (int i = 0; i < 4; ++i) {
      if (vts[i].x == -1.0f && vts[i].y == -1.0f) {
        res = i;
        vts[i].x = x;
        vts[i].y = y;
        break;
      }
    }
  }
  return res;
}

01

初始化Point2f点和赋值

这里用一个函数实现了,加了一个isinit的bool项,当为true直接将Point2f的指针全部赋值为-1.0f,如果是鼠标点击时会自动判断给第一个点赋值,并返回当前的位置数。

02

鼠标点击事件

当点击左键时,调用上面的函数获取到当前赋值的点,然后在当前点上画上和上一点的连线,如果是最后一个点,则除了和上一点连线,还要和起始点进行连线。

当ptindex返回值为3时,说明4个点都已经赋值了,这时就进入透视变换的操作。其中CalcPointDistance用于计算矩形的宽和高。

通过欧式距离计算了长度,CvUtils中还有一些别的通过函数,完整源码在文章最后可以看到。

点击鼠标右键后就跳转到指定文件夹下下一张图片,并初始化需要透视变换的选择点。这样一个手动截取图像进行透视变换的小Demo就完成了。

源码地址

https://github.com/Vaccae/OpenCVDemoCpp.git