学更好的别人,
做更好的自己。
——《微卡智享》

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

实现效果



从上图中可以看出,手动点击4个位置点画的蓝色四边形框后,针对这个图像做了透视变换的效果,也是最终想要的结果,接下来就看看怎么实现的。
微卡智享
关键问题的Q&A
实现手动点击截取图像进行透视变换注意点?
A
1. 鼠标事件,每切换图像时需要保证定义的Point2f指针都要初始化清零,这样在点击的时候可以自己判断给哪一个点赋值了。
2. 当4个点都完成后,需要根据点的位置采用欧式距离计算矩形的宽度和高度。
3. 需要注意点击的顺序,现在做的都是从左上顺时针方向开始点击的,如果不是按照这个方案,透视变换会有问题,当时源码中CvUtils类中有一个以前写的排序的函数,不过这里没用到。
代码实现
微卡智享
- #pragma once
- #include
- #include
- #include "../../Utils/CvUtils.h"
-
-
-
-
- using namespace std;
- using namespace cv;
-
-
- //设置图片所以路径
- String FilePaths = "D:/Business/DemoTEST/CPP/OpenCVDemoCpp/OpenCVSplitImage/pic";
- //获取目录下的所有文件
- vector
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
点击阅读原文可以看到“码云”的代码地址
完


往期精彩回顾