学更好的别人,
做更好的自己。
——《微卡智享》
本文长度为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
点击阅读原文可以看到“码云”的代码地址
完
往期精彩回顾
使用OpenCV做个简单的颜色提取器
趣玩算法--OpenCV华容道AI自动解题
整活!我是如何用OpenCV做了数字华容道游戏!(附源码)