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


    学更好的别人,

    做更好的自己。

    ——《微卡智享》

    8b933d58b171e062191aa215afdc9166.jpeg

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

    前言

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

    c9ffabadc9976e407f0cd15e69d05703.png

    实现效果

    3b1be889834e70a4144c3dd24be483b2.gif

    d35ab7e473e8a25502ebecb904ae0102.jpeg

    18f974808b2e665b665f4cb2609ff369.jpeg

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

    微卡智享

    关键问题的Q&A

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

    A

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

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

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

    代码实现

    d247b86a0cf21d84c3e69a4ca9bc690d.png

    微卡智享

    main.cpp代码

    1. #pragma once
    2. #include
    3. #include
    4. #include "../../Utils/CvUtils.h"
    5. using namespace std;
    6. using namespace cv;
    7. //设置图片所以路径
    8. String FilePaths = "D:/Business/DemoTEST/CPP/OpenCVDemoCpp/OpenCVSplitImage/pic";
    9. //获取目录下的所有文件
    10. vector files;
    11. //鼠标回调函数
    12. void onMouse(int event, int x, int y, int flags, void* ustc);
    13. Mat src;
    14. Mat srccopy; //用于拷贝出的源图像
    15. string showsrc = "图像";
    16. int imgindex = 0;
    17. //设置透视变换的点
    18. Point2f vertices[4];
    19. //给透视变换点进行赋值,返回值为3时,说明4个点都已经赋值了,可以进行下一步操作
    20. int setPerspectivePoint(Point2f* vts, int x, int y, bool isinit = false);
    21. int main(int argc, char** argv) {
    22. glob(FilePaths, files);
    23. if (files.size() <= 0) {
    24. cout << "找不到图片文件" << endl;
    25. waitKey(0);
    26. return -1;
    27. }
    28. //初始化透视变换的点
    29. setPerspectivePoint(vertices, 0, 0, true);
    30. //关闭所有显示窗口
    31. destroyAllWindows();
    32. cout << "srcindex:" << imgindex << endl;
    33. String file = files[imgindex];
    34. src = imread(file);
    35. CvUtils::MatResize(src);
    36. CvUtils::SetShowWindow(src, showsrc, 50, 20);
    37. imshow(showsrc, src);
    38. //复制下源图
    39. src.copyTo(srccopy);
    40. //设置鼠标响影事件
    41. setMouseCallback(showsrc, onMouse);
    42. waitKey(0);
    43. return 0;
    44. }
    45. void onMouse(int event, int x, int y, int flags, void* ustc)
    46. {
    47. //鼠标左键按下
    48. if (event == EVENT_LBUTTONUP)
    49. {
    50. //设置选择的点
    51. int ptindex = setPerspectivePoint(vertices, x, y);
    52. //在图像上画出点击位置
    53. circle(src, Point(x, y), 3, Scalar(255, 0, 0), -1);
    54. //选中的点进行画线
    55. if (ptindex > 0 && ptindex <= 3) {
    56. line(src, vertices[ptindex], vertices[ptindex - 1], Scalar(255, 0, 0), 3);
    57. //当是最后一个点时和起始点进行画线连接
    58. if (ptindex == 3) {
    59. line(src, vertices[ptindex], vertices[0], Scalar(255, 0, 0), 3);
    60. }
    61. }
    62. imshow(showsrc, src);
    63. if (ptindex == 3) {
    64. //根据最小矩形和多边形拟合的最大四个点计算透视变换矩阵
    65. Point2f rectPoint[4];
    66. //计算旋转矩形的宽和高
    67. float rWidth = CvUtils::CalcPointDistance(vertices[0], vertices[1]);
    68. float rHeight = CvUtils::CalcPointDistance(vertices[1], vertices[2]);
    69. //计算透视变换的四个顶点
    70. rectPoint[0] = Point2f(0, 0);
    71. rectPoint[1] = rectPoint[0] + Point2f(rWidth, 0);
    72. rectPoint[2] = rectPoint[1] + Point2f(0, rHeight);
    73. rectPoint[3] = rectPoint[0] + Point2f(0, rHeight);
    74. //计算透视变换矩阵
    75. Mat warpmatrix = getPerspectiveTransform(vertices, rectPoint);
    76. Mat resultimg;
    77. //透视变换
    78. warpPerspective(srccopy, resultimg, warpmatrix, resultimg.size(), INTER_LINEAR);
    79. //载取透视变换后的图像显示出来
    80. Rect cutrect = Rect(rectPoint[0], rectPoint[2]);
    81. Mat cutMat = resultimg(cutrect);
    82. CvUtils::SetShowWindow(cutMat, "cutMat", 600, 20);
    83. imshow("cutMat", cutMat);
    84. }
    85. }
    86. else if (event == EVENT_RBUTTONUP) {
    87. //初始化透视变换的点
    88. setPerspectivePoint(vertices, 0, 0, true);
    89. imgindex++;
    90. if (imgindex < files.size()) {
    91. //关闭所有显示窗口
    92. destroyAllWindows();
    93. cout << "srcindex:" << imgindex << endl;
    94. String file = files[imgindex];
    95. src = imread(file);
    96. CvUtils::MatResize(src);
    97. CvUtils::SetShowWindow(src, showsrc, 50, 20);
    98. imshow(showsrc, src);
    99. //复制下源图
    100. src.copyTo(srccopy);
    101. //设置鼠标响影事件
    102. setMouseCallback(showsrc, onMouse);
    103. }
    104. waitKey(0);
    105. }
    106. }
    107. //给透视变换点进行赋值,返回值为true时,说明4个点都已经赋值了,可以进行下一步操作
    108. int setPerspectivePoint(Point2f* vts, int x, int y, bool isinit)
    109. {
    110. int res = 0;
    111. if (isinit) {
    112. for (int i = 0; i < 4; ++i) {
    113. vts[i].x = -1.0f;
    114. vts[i].y = -1.0f;
    115. }
    116. }
    117. else {
    118. for (int i = 0; i < 4; ++i) {
    119. if (vts[i].x == -1.0f && vts[i].y == -1.0f) {
    120. res = i;
    121. vts[i].x = x;
    122. vts[i].y = y;
    123. break;
    124. }
    125. }
    126. }
    127. return res;
    128. }

    01

    初始化Point2f点和赋值

    b42477ad606378a4f6a5340d0efff668.png

    2d197725add37516524ac2026a463e33.png

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

    02

    鼠标点击事件

    43958a5d451516f66fde3cc73418d929.png

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

    727b7f937454dd3dbaa6ef7e566b3b77.png

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

    0a820d2a66645e650c79aa344a5e53ab.png

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

    7571341da5c9f24f7e2f480b178f3cd7.png

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

    源码地址

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

    点击阅读原文可以看到“码云”的代码地址

    7b94daf77fc3e03b0651fd22a8ef5b21.png

    4e748fc5855b2d54ed08634c6d9d534e.png

    往期精彩回顾

     

    04b88d7a7911ddfe01f71ef58e07e5c7.jpeg

    使用OpenCV做个简单的颜色提取器

     

     

    604ff5ca402b1c4a7157d91792435aa2.jpeg

    趣玩算法--OpenCV华容道AI自动解题

     

     

    7449a91b96ca954df470ae9228c97b20.jpeg

    整活!我是如何用OpenCV做了数字华容道游戏!(附源码)

     

  • 相关阅读:
    算法提升 (三)基础数据结构
    工程管理系统简介 工程管理系统源码 java工程管理系统 工程管理系统功能设计
    全国电费接口文档分享
    Vue语法
    Linux常用命令——常用网络命令【二】
    使用正则表达式判断日期字符串格式是否合法遇到的问题(解决)
    批处理的应用和源码分析
    【Linux】《Linux命令行与shell脚本编程大全 (第4版) 》笔记-Chapter24-编写简单的脚本实用工具
    电力电子的一些知识
    4款Windows上鲜为人知的黑马软件,内存不足也舍不得删除
  • 原文地址:https://blog.csdn.net/Vaccae/article/details/126944941