图像,也就是图像中的点坐标变换,实际就是通过一个2*3的矩阵计算而变换的。前面2行2列做缩放或者旋转,后面2行1列做平移。方程不会打字,拍了以前的笔记:

现在我用了个最简单的平移仿射做测试,就是x+100, y+50

我以(0,0)(100,0)(0,50)三点平移变换到(100,50)(200,50)(100,100)三点,那么正确的矩阵应该是1 0 100,0 1 50,则正确;

再根据这个仿射矩阵计算(50,25)(100,25)(100,50)这三点仿射后的三点位置,如果结果是(150,75)(200,75)(200,100),则正确;

首先,仿射变换的原型cv::Mat cv::getAffineTransform(const cv::Point2f* src, const cv::Point2f* dst);

这里src和dst是包含三个二维点的数组。 这个很好赋值,但是读取2*3仿射矩阵时,要特别小心数据类型,这个矩阵的元素类型是CV_64FC1,可以通过Mat的type或者depth方法查看,也就是说元素里的数据是double型的,一定要这样affineMat.at<double>(i,j)读取,将double错用了float,int等,将得到毫不相关的错误数据!!

Mat affineMat = getAffineTransform(triangleA, triangleB);
    qDebug() << CV_MAT_DEPTH(affineMat.depth())<< "-----depth";
    for (int i = 0; i < affineMat.rows; i++)
    {
        for (int j = 0; j < affineMat.cols; j++) {
            qDebug() << affineMat.at<double>(i,j);  //这里类型写错成float等就会得到错值
        }
    }

depth()返回是基本的数据类型,也就是说每个数据元素的类型,该函数并不关心矩阵cv::Mat的维度,
    #define CV_8U   0
    #define CV_8S   1
    #define CV_16U  2
    #define CV_16S  3
    #define CV_32S  4
    #define CV_32F  5
    #define CV_64F  6
    #define CV_16F  7

接下来,稀疏变换transform方法的参数赋值,就没那么直接明了了,与之对应的稠密映射warpAffine直接输入Mat图像做参数,反而简单的多。稀疏映射,做的是单点映射,也就是根据仿射矩阵对点一个个做草稿中的计算得到变换后的点。 譬如这里,我只想知道某三个点,矩阵变换后的坐标。

这个函数 的原型void cv::transform(cv::InputArray src,  // Input N-by-1 array (Ds channels)

cv::OutputArray dst,  // Output N-by-1 array (Dd channels)

cv::InputArray mtx);  //transform matrix (Ds-by-Dd)

src是具有Ds通道的N*1数组, 其中N是要变换的点数,Ds是这些源点 的维数,函数的矩阵变换作用于数组中每个点的通道索引。

那这里计算3个点,N就是3行了,3行1列三个点,每个点有x,y两个坐标,就是Ds=2维,opencv的矩阵用了通道数来表达这个第三维数据2。那么我们得这样创建一个三行一列二通道的src矩阵如下:

Mat m;

m.create(3,1,CV_64FC2);              //注意这里同样一定要用double型数据CV_64FC2
并且这样初始化赋值:

m.at<cv::Vec2d>(0,0)[0] = 50;           //数据类型double型 Vec2d
    m.at<cv::Vec2d>(0,0)[1] = 25;
    m.at<cv::Vec2d>(1,0)[0] = 100;
    m.at<cv::Vec2d>(1,0)[1] = 25;
    m.at<cv::Vec2d>(2,0)[0] = 100;
    m.at<cv::Vec2d>(2,0)[1] = 50;

实际上这个函数是功能强大的通用变换函数,可以计算任意维度,因为我们这里用的是2*3的变换矩阵,前两行两列做缩放旋转,注定只能计算2维单点。 那如果我们对三维Ds(三通道)的颜色数据做变换,那我们就需要一个3行3列做缩放旋转,一个3行1列做平移,也就是需要一个3行4列的矩阵。后面的依次类推。

   +    如果是这样,就需要创建N行1列3通道输入矩阵src,需要3行4列的变换矩阵,对N个三维单点仿射。

接下来就可以做矩阵变换和读取值了

Mat res;
    res.create(3,1,CV_64FC2);
    transform(m,res,affineMat);

for (int t = 0; t < res.rows; t++) {
        double *pointer = res.ptr<double>(t);
        for (int h = 0; h < res.cols; h += 2) {
            qDebug() << pointer[h] << pointer[h+1];
        }
    }

另外,这里先取的每一行的指针,再用指针读取这一行的数据,会比直接挨个取值快。

指针操作是按3行1列把矩阵看成一个rows * (cols * channels)的二维数组进行操作,其中的每个元素的类型都是double类型。

h+=2一个坐标点的两维占了两个连续地址。这种操作方式虽然复杂一些,但是数据多的时候执行效率会高很多。

挨个取值时这样的:

for (int t = 0; t < m.rows; t++) {
        for (int h = 0; h < m.cols; h++) {
            qDebug() << m.at<Vec2d>(t,h)[0] << m.at<Vec2d>(t,h)[1];
        }
    }

运行结果图

整体源码如下:

#include "opencv2/opencv.hpp"
using namespace cv;
#include <QDebug>int main()
{Point2f triangleA[3];Point2f triangleB[3];triangleA[0] = Point2f(0,0);triangleA[1] = Point2f(100,0);triangleA[2] = Point2f(0,50);triangleB[0] = Point2f(100,50);triangleB[1] = Point2f(200,50);triangleB[2] = Point2f(100,100);Mat affineMat = getAffineTransform(triangleA, triangleB);qDebug() << CV_MAT_DEPTH(affineMat.depth())<< "-----depth";for (int i = 0; i < affineMat.rows; i++){for (int j = 0; j < affineMat.cols; j++) {qDebug() << affineMat.at<double>(i,j);  //这里类型写错成float.double就会得到错值}}qDebug() << "---------------------------";Mat m;m.create(3,1,CV_64FC2);m.at<cv::Vec2d>(0,0)[0] = 50;m.at<cv::Vec2d>(0,0)[1] = 25;m.at<cv::Vec2d>(1,0)[0] = 100;m.at<cv::Vec2d>(1,0)[1] = 25;m.at<cv::Vec2d>(2,0)[0] = 100;m.at<cv::Vec2d>(2,0)[1] = 50;double time1 = static_cast<double>(getTickCount());for (int t = 0; t < m.rows; t++) {for (int h = 0; h < m.cols; h++) {qDebug() << m.at<Vec2d>(t,h)[0] << m.at<Vec2d>(t,h)[1];}}time1 = 1000 * (static_cast<double>(getTickCount()) - time1)/getTickFrequency();qDebug() <<"第二种方法用时: "<< time1;double time2 = static_cast<double>(getTickCount());for (int t = 0; t < m.rows; t++) {double *pointer = m.ptr<double>(t);for (int h = 0; h < m.cols; h += 2) {qDebug() << pointer[h] << pointer[h+1];}}time2 = 1000 * (static_cast<double>(getTickCount()) - time2)/getTickFrequency();qDebug() <<"第二种方法用时: "<< time2;Mat res;res.create(3,1,CV_64FC2);transform(m,res,affineMat);for (int t = 0; t < res.rows; t++) {double *pointer = res.ptr<double>(t);for (int h = 0; h < res.cols; h += 2) {qDebug() << pointer[h] << pointer[h+1];}}system("pause");return 0;
}

参考博客:Opencv中的Mat类使用方法总结

测试opencv的仿射函数getAffineTransform以及稀疏矩阵变换transform函数(单点仿射)相关推荐

  1. 详解OpenCV中的Lucas Kanade稀疏光流单应追踪器

    详解OpenCV中的Lucas Kanade稀疏光流单应追踪器 1. 效果图 2. 源码 参考 这篇博客将详细介绍OpenCV中的Lucas Kanade稀疏光流单应追踪器. 光流是由物体或相机的运动 ...

  2. 在ubuntu14.04中安装及测试OpenCV

    本文记录ubuntu下安装opencv过程,步骤来自 opencv官网 1.安装opencv所需的库(编译器.必须库.可选库) 转载请说明 http://www.cnblogs.com/llxrl/p ...

  3. OpenCV学习笔记(三十六)——Kalman滤波做运动目标跟踪 OpenCV学习笔记(三十七)——实用函数、系统函数、宏core OpenCV学习笔记(三十八)——显示当前FPS OpenC

    OpenCV学习笔记(三十六)--Kalman滤波做运动目标跟踪 kalman滤波大家都很熟悉,其基本思想就是先不考虑输入信号和观测噪声的影响,得到状态变量和输出信号的估计值,再用输出信号的估计误差加 ...

  4. OpenCV笔记11——cvRound()、cvFloor()、 cvCeil()函数讲解

    功能:cvRound(), cvFloor(), cvCeil()函数讲解. 函数cvRound,cvFloor,cvCeil 都是用一种舍入的方法将输入浮点数转换成整数: cvRound():返回跟 ...

  5. OpenCV学习笔记(二十一)——绘图函数core OpenCV学习笔记(二十二)——粒子滤波跟踪方法 OpenCV学习笔记(二十三)——OpenCV的GUI之凤凰涅槃Qt OpenCV学习笔记(二十

    OpenCV学习笔记(二十一)--绘图函数core 在图像中,我们经常想要在图像中做一些标识记号,这就需要绘图函数.OpenCV虽然没有太优秀的GUI,但在绘图方面还是做得很完整的.这里就介绍一下相关 ...

  6. OpenCV 霍夫线变换Hough Line Transform

    OpenCV 霍夫线变换Hough Line Transform 霍夫线变换Hough Line Transform 目标 理论 霍夫线变换 它是如何工作的? 标准概率霍夫线变换 这个程序做什么? 代 ...

  7. Python+OpenCV:Hough圆检测(Hough Circle Transform)

    Python+OpenCV:Hough圆检测(Hough Circle Transform) ##################################################### ...

  8. Python+OpenCV:Hough直线检测(Hough Line Transform)

    Python+OpenCV:Hough直线检测(Hough Line Transform) 理论 A line can be represented as  or in a parametric fo ...

  9. 归一化(Normalization)标准化(Standarlization)tensorflow和opencv区别:opencv之transform函数解析CHW与HWC:图像的线性数据格

    目录 归一化:transforms.Normalize 归一化(Normalization) 标准化(Standarlization) tensorflow和opencv区别:opencv之trans ...

最新文章

  1. 程序员不要去这样的公司
  2. easyUI添加行操作
  3. Thymeleaf页面级联属性
  4. 中高德地图只显示某一城市_干货 | 如何快速制作数据地图?让你的可视化逼格再高一级!...
  5. synchronized【Java】中使用的demo
  6. c++ socket线程池_java 网络编程,Socket编程
  7. 不知道工作组名称怎样加入_剩米饭不知道怎样做?试试泡菜炒饭,再也不用担心米饭做多了...
  8. 【熊猫多模式站群开发日志】流程总览
  9. Android初学第9天
  10. 显微镜下的大明内容_《显微镜下的大明》读后感
  11. 软件需求与分析课堂讨论一
  12. 数字逻辑课上如何制作FPGA游戏?
  13. 推荐3个搜索资源的网站,保存起来,用的时候方便找哦
  14. 两个网段共享打印机_不同ip段共享打印机设置方法
  15. asp.net摄影网站系统VS开发sqlserver数据库web结构c#编程计算机网页源码项目
  16. python调用golang dataframe_用Python获取摄像头并实时控制人脸
  17. k8s各组件的端口说明
  18. 2021年12月电子学会Python等级考试试卷(三级)答案解析
  19. 全球及中国融媒体行业建设发展机遇及运营前景调研报告2021-2027年
  20. caxa工程知识管理服务器配置信息怎么填,CAXA CAPP教程:如何定义与单元格相关联的知识库...

热门文章

  1. 一些有用的在线工具(二)
  2. TcaplusDB君 · 行业新闻汇编(11月22号)
  3. 对接阿里云opensearch
  4. 爱马仕Hermès手表怎么样?
  5. 爬虫(java+jsoup)
  6. 搜索引擎相关站点收集
  7. 怎么用C语言搜索有根区间,C语言 判断一个数字是否在一个指定的区间范围内?求解答。。。。...
  8. 【机器学习】P18 反向传播(导数、微积分、链式法则、前向传播、后向传播流程、神经网络)
  9. linux设备驱动中的module_init
  10. 带视频教程|2.0升级版源码价值18500元的商业版游戏陪玩语音聊天系统源码