常常需要最图像进行仿射变换,仿射变换后,我们可能需要将原来图像中的特征点坐标进行重新计算,获得原来图像中例如眼睛瞳孔坐标的新的位置,用于在新得到图像中继续利用瞳孔位置坐标。

仿射变换在:http://blog.csdn.net/xiaowei_cqu/article/details/7616044 这位大牛的博客中已经介绍的非常清楚。

关于仿射变换的详细介绍,请见上面链接的博客。

我这里主要介绍如何在已经知道原图像中若干特征点的坐标之后,计算这些特征点进行放射变换之后的坐标,然后做一些补充。

** 在原文中,很多功能函数都是使用的cvXXX,例如cv2DRotationMatrix( center, degree,1, &M);  这些都是老版本的函数,在opencv2以后,应该尽量的使用全新的函数,所以在我的代码中,都是使用的最新的函数,不再使用 cvMat, 而是全部使用 Mat 类型。 **

1. 特征点对应的新的坐标计算

假设已经有一个原图像中的特征点的坐标 CvPoint point;  那么计算这个point的对应的仿射变换之后在新的图像中的坐标位置,使用的方法如下函数:

// 获取指定像素点放射变换后的新的坐标位置
CvPoint getPointAffinedPos(const CvPoint &src, const CvPoint ¢er, double angle)
{
CvPoint dst;
int x = src.x - center.x;
int y = src.y - center.y; dst.x = cvRound(x * cos(angle) + y * sin(angle) + center.x);
dst.y = cvRound(-x * sin(angle) + y * cos(angle) + center.y);
return dst;
}

要特别注意的是,在对一个原图像中的像素的坐标进行计算仿射变换之后的坐标的时候,一定要按照仿射变换的基本原理,将原来的坐标减去仿射变换的旋转中心的坐标,这样仿射变换之后得到的坐标再加上仿射变换旋转中心坐标才是原坐标在新的仿射变换之后的图像中的正确坐标。

下面给出计算对应瞳孔坐标旋转之后的坐标位置的示例代码:

// AffineTransformation.cpp : Defines the entry point for the console application.
// #include "stdafx.h"
#include "stdio.h"
#include "iostream" #include "opencv2/opencv.hpp" using namespace std;
using namespace cv; // 获取指定像素点放射变换后的新的坐标位置
CvPoint getPointAffinedPos(const CvPoint &src, const CvPoint ¢er, double angle);
Mat ImageRotate(Mat & src, const CvPoint &_center, double angle);
Mat ImageRotate2NewSize(Mat& src, const CvPoint &_center, double angle); int _tmain(int argc, _TCHAR* argv[])
{
string image_path = "D:/lena.jpg";
Mat img = imread(image_path);
cvtColor(img, img, CV_BGR2GRAY); Mat src;
img.copyTo(src); CvPoint Leye;
Leye.x = 265;
Leye.y = 265;
CvPoint Reye;
Reye.x = 328;
Reye.y = 265; // draw pupil
src.at<unsigned char>(Leye.y, Leye.x) = 255;
src.at<unsigned char>(Reye.y, Reye.x) = 255; imshow("src", src); //
CvPoint center;
center.x = img.cols / 2;
center.y = img.rows / 2; double angle = 15L; Mat dst = ImageRotate(img, center, angle); // 计算原特征点在旋转后图像中的对应的坐标
CvPoint l2 = getPointAffinedPos(Leye, center, angle * CV_PI / 180);
CvPoint r2 = getPointAffinedPos(Reye, center, angle * CV_PI / 180); // draw pupil
dst.at<unsigned char>(l2.y, l2.x) = 255;
dst.at<unsigned char>(r2.y, r2.x) = 255; //Mat dst = ImageRotate2NewSize(img, center, angle);
imshow("dst", dst); waitKey(0);
return 0;
} Mat ImageRotate(Mat & src, const CvPoint &_center, double angle)
{
CvPoint2D32f center;
center.x = float(_center.x);
center.y = float(_center.y); //计算二维旋转的仿射变换矩阵
Mat M = getRotationMatrix2D(center, angle, 1); // rotate
Mat dst;
warpAffine(src, dst, M, cvSize(src.cols, src.rows), CV_INTER_LINEAR);
return dst;
} // 获取指定像素点放射变换后的新的坐标位置
CvPoint getPointAffinedPos(const CvPoint &src, const CvPoint ¢er, double angle)
{
CvPoint dst;
int x = src.x - center.x;
int y = src.y - center.y; dst.x = cvRound(x * cos(angle) + y * sin(angle) + center.x);
dst.y = cvRound(-x * sin(angle) + y * cos(angle) + center.y);
return dst;
}

这里,我们先通过手工找到瞳孔坐标,然后计算在图像旋转之后瞳孔的坐标。

运行结果如图:

原图像

旋转之后的图像:

2. 旋转中心对于旋转的影响

然后我们看看仿射变换旋转点的选择对于旋转之后的图像的影响,一般情况下,我们选择图像的中心点作为仿射变换的旋转中心,获得的旋转之后的图像与原图像大小一样。

计算代码:

int _tmain(int argc, _TCHAR* argv[])
{
string image_path = "D:/lena.jpg";
Mat img = imread(image_path);
cvtColor(img, img, CV_BGR2GRAY); Mat src;
img.copyTo(src); CvPoint Leye;
Leye.x = 265;
Leye.y = 265;
CvPoint Reye;
Reye.x = 328;
Reye.y = 265; // draw pupil
src.at<unsigned char>(Leye.y, Leye.x) = 255;
src.at<unsigned char>(Reye.y, Reye.x) = 255; imshow("src", src); //
/*CvPoint center;
center.x = img.cols / 2;
center.y = img.rows / 2;*/
CvPoint center;
center.x = 0;
center.y = 0; double angle = 15L; Mat dst = ImageRotate(img, center, angle); // 计算原特征点在旋转后图像中的对应的坐标
CvPoint l2 = getPointAffinedPos(Leye, center, angle * CV_PI / 180);
CvPoint r2 = getPointAffinedPos(Reye, center, angle * CV_PI / 180); // draw pupil
dst.at<unsigned char>(l2.y, l2.x) = 255;
dst.at<unsigned char>(r2.y, r2.x) = 255; //Mat dst = ImageRotate2NewSize(img, center, angle);
imshow("dst", dst); waitKey(0);
return 0;
}

这里绕着(0,0)点进行旋转,旋转之后的图像:

绕着左下角旋转:

	CvPoint center;
center.x = 0;
center.y = img.rows;

旋转之后的图像:

3. 缩放因子对于旋转图像的影响

上面我们的代码都没有添加缩放信息,现在对上面的代码进行稍加修改,添加缩放参数,然后看一下如何计算对应的新的坐标。

#include "stdafx.h"
#include "stdio.h"
#include "iostream" #include "opencv2/opencv.hpp" using namespace std;
using namespace cv; // 获取指定像素点放射变换后的新的坐标位置
CvPoint getPointAffinedPos(const CvPoint &src, const CvPoint ¢er, double angle, double scale);
Mat ImageRotate(Mat & src, const CvPoint &_center, double angle, double scale);
Mat ImageRotate2NewSize(Mat& src, const CvPoint &_center, double angle, double scale); int _tmain(int argc, _TCHAR* argv[])
{
string image_path = "D:/lena.jpg";
Mat img = imread(image_path);
cvtColor(img, img, CV_BGR2GRAY);
double scale = 0.5; Mat src;
img.copyTo(src); CvPoint Leye;
Leye.x = 265;
Leye.y = 265;
CvPoint Reye;
Reye.x = 328;
Reye.y = 265; // draw pupil
src.at<unsigned char>(Leye.y, Leye.x) = 255;
src.at<unsigned char>(Reye.y, Reye.x) = 255; imshow("src", src); //
CvPoint center;
center.x = img.cols / 2;
center.y = img.rows / 2; double angle = 15L; Mat dst = ImageRotate(img, center, angle, scale); // 计算原特征点在旋转后图像中的对应的坐标
CvPoint l2 = getPointAffinedPos(Leye, center, angle * CV_PI / 180, scale);
CvPoint r2 = getPointAffinedPos(Reye, center, angle * CV_PI / 180, scale); // draw pupil
dst.at<unsigned char>(l2.y, l2.x) = 255;
dst.at<unsigned char>(r2.y, r2.x) = 255; //Mat dst = ImageRotate2NewSize(img, center, angle);
imshow("dst", dst); waitKey(0);
return 0;
} Mat ImageRotate(Mat & src, const CvPoint &_center, double angle, double scale)
{
CvPoint2D32f center;
center.x = float(_center.x);
center.y = float(_center.y); //计算二维旋转的仿射变换矩阵
Mat M = getRotationMatrix2D(center, angle, scale); // rotate
Mat dst;
warpAffine(src, dst, M, cvSize(src.cols, src.rows), CV_INTER_LINEAR);
return dst;
} // 获取指定像素点放射变换后的新的坐标位置
CvPoint getPointAffinedPos(const CvPoint &src, const CvPoint ¢er, double angle, double scale)
{
CvPoint dst;
int x = src.x - center.x;
int y = src.y - center.y; dst.x = cvRound(x * cos(angle) * scale + y * sin(angle) * scale + center.x);
dst.y = cvRound(-x * sin(angle) * scale + y * cos(angle) * scale + center.y);
return dst;
}

当缩放尺度为0.5的时候,程序的运行结果如图:

4.  根据旋转与缩放尺度获得与原始图像大小不同的图像大小(新的合适的大小)

上面的计算中,一直都是放射变换之后计算得到的图像和原始图像一样大,但是因为旋转、缩放之后图像可能会变大或者变小,我们再次对上面的代码进行修改,这样在获得仿射变换之后的图像前,需要重新计算生成的图像的大小。

计算方法:

	double angle2 = angle * CV_PI / 180;
int width = src.cols;
int height = src.rows; double alpha = cos(angle2) * scale;
double beta = sin(angle2) * scale; int new_width = (int)(width * fabs(alpha) + height * fabs(beta));
int new_height = (int)(width * fabs(beta) + height * fabs(alpha));

另外,因为我们的图像旋转是按照原图像的中心,所以当获取到图像的仿射变换矩阵之后,我们需要根据新生成的图像的大小,给仿射变换矩阵添加平移信息。

或者可以这么说,我们新计算得到的图像的大小,让原始图像绕着新的图像大小的中心进行旋转。

	//计算二维旋转的仿射变换矩阵
Mat M = getRotationMatrix2D(center, angle, scale); // 给计算得到的旋转矩阵添加平移 M.at<double>(0, 2) += (int)((new_width - width )/2);
M.at<double>(1, 2) += (int)((new_height - height )/2);

然后另外需要注意的是,如果你在原始图像中有一些特征点的坐标,这些特征点的坐标映射到新的图像上的时候,需要在以前的方法的基础上增加平移信息。

// 获取指定像素点放射变换后的新的坐标位置
CvPoint getPointAffinedPos(Mat & src, Mat & dst, const CvPoint &src_p, const CvPoint ¢er, double angle, double scale)
{
double alpha = cos(angle) * scale;
double beta = sin(angle) * scale; int width = src.cols;
int height = src.rows; CvPoint dst_p;
int x = src_p.x - center.x;
int y = src_p.y - center.y; dst_p.x = cvRound(x * alpha + y * beta + center.x);
dst_p.y = cvRound(-x * beta + y * alpha + center.y); int new_width = dst.cols;
int new_height = dst.rows; int movx = (int)((new_width - width)/2);
int movy = (int)((new_height - height)/2); dst_p.x += movx;
dst_p.y += movy; return dst_p;
}

我们仿射变换函数代码:

Mat ImageRotate2NewSize(Mat& src, const CvPoint &_center, double angle, double scale)
{
double angle2 = angle * CV_PI / 180;
int width = src.cols;
int height = src.rows; double alpha = cos(angle2) * scale;
double beta = sin(angle2) * scale; int new_width = (int)(width * fabs(alpha) + height * fabs(beta));
int new_height = (int)(width * fabs(beta) + height * fabs(alpha)); CvPoint2D32f center;
center.x = float(width / 2);
center.y = float(height / 2);
//计算二维旋转的仿射变换矩阵
Mat M = getRotationMatrix2D(center, angle, scale); // 给计算得到的旋转矩阵添加平移 M.at<double>(0, 2) += (int)((new_width - width )/2);
M.at<double>(1, 2) += (int)((new_height - height )/2); // rotate
Mat dst;
warpAffine(src, dst, M, cvSize(new_width, new_height), CV_INTER_LINEAR);
return dst;
}

主函数:

int _tmain(int argc, _TCHAR* argv[])
{
string image_path = "D:/lena.jpg";
Mat img = imread(image_path);
cvtColor(img, img, CV_BGR2GRAY);
double scale = 0.5; Mat src;
img.copyTo(src); CvPoint Leye;
Leye.x = 265;
Leye.y = 265;
CvPoint Reye;
Reye.x = 328;
Reye.y = 265; // draw pupil
src.at<unsigned char>(Leye.y, Leye.x) = 255;
src.at<unsigned char>(Reye.y, Reye.x) = 255; imshow("src", src); //
CvPoint center;
center.x = img.cols / 2;
center.y = img.rows / 2; double angle = 15L; //Mat dst = ImageRotate(img, center, angle, scale);
Mat dst = ImageRotate2NewSize(img, center, angle, scale); // 计算原特征点在旋转后图像中的对应的坐标
CvPoint l2 = getPointAffinedPos(src, dst, Leye, center, angle * CV_PI / 180, scale);
CvPoint r2 = getPointAffinedPos(src, dst, Reye, center, angle * CV_PI / 180, scale); // draw pupil
dst.at<unsigned char>(l2.y, l2.x) = 255;
dst.at<unsigned char>(r2.y, r2.x) = 255; imshow("dst", dst); waitKey(0);
return 0;
}

仿射变换结果以及瞳孔重新坐标计算结果:

5. 根据三个点进行仿射变换


根据给点的三个点,由这三个点之前的坐标以及变换之后的坐标,对原图像进行仿射变换,不过需要事先知道三个点仿射变换的坐标位置。
int _tmain(int argc, _TCHAR* argv[])
{
string image_path = "D:/lena.jpg";
Mat img = imread(image_path); Point2f src_points[3];
src_points[0] = Point2f(100, 100);
src_points[1] = Point2f(400, 100);
src_points[2] = Point2f(250, 300); Point2f dst_points[3];
dst_points[0] = Point2f(100, 100);
dst_points[1] = Point2f(400, 300);
dst_points[2] = Point2f(100, 300); Mat M1 = getAffineTransform(src_points, dst_points); Mat dst;
warpAffine(img, dst, M1, cvSize(img.cols, img.rows), INTER_LINEAR); imshow("dst", dst); //cvtColor(img, img, CV_BGR2GRAY);
//double scale = 1.5; //Mat src;
//img.copyTo(src); //CvPoint Leye;
//Leye.x = 265;
//Leye.y = 265;
//CvPoint Reye;
//Reye.x = 328;
//Reye.y = 265; //// draw pupil
//src.at<unsigned char>(Leye.y, Leye.x) = 255;
//src.at<unsigned char>(Reye.y, Reye.x) = 255; //imshow("src", src); ////
//CvPoint center;
//center.x = img.cols / 2;
//center.y = img.rows / 2; //double angle = 15L; ////Mat dst = ImageRotate(img, center, angle, scale);
//Mat dst = ImageRotate2NewSize(img, center, angle, scale); //// 计算原特征点在旋转后图像中的对应的坐标
//CvPoint l2 = getPointAffinedPos(src, dst, Leye, center, angle * CV_PI / 180, scale);
//CvPoint r2 = getPointAffinedPos(src, dst, Reye, center, angle * CV_PI / 180, scale); //// draw pupil
//dst.at<unsigned char>(l2.y, l2.x) = 255;
//dst.at<unsigned char>(r2.y, r2.x) = 255;
//imshow("dst", dst); waitKey(0);
return 0;
}

结果:





opencv 图像仿射变换 计算仿射变换后对应特征点的新坐标 图像旋转、缩放、平移的更多相关文章

  1. 图像配准建立仿射变换模型并用RANSAC算法评估

    当初选方向时就由于从小几何就不好.缺乏空间想像能力才没有选择摄影測量方向而是选择了GIS. 昨天同学找我帮他做图像匹配.这我哪里懂啊,无奈我是一个别人有求于我,总是不好意思开口拒绝的人.于是乎就看着他 ...

  2. opencv 用户文档 错误更正 仿射变换

    今天在看opencv官方给出的仿射变换计算仿射变换矩阵的文档的时候,发现官方文档中有个很明显的错误,再次给大家提个醒. 官方文档连接: http://opencv.willowgarage.com/d ...

  3. 【OpenCV学习】计算两幅图像的重叠区域

    问题描述:已知两幅图像Image1和Image2,计算出两幅图像的重叠区域,并在Image1和Image2标识出重叠区域. 算法思想: 若两幅图像存在重叠区域,则进行图像匹配后,会得到一张完整的全景图 ...

  4. OpenCV 学习(计算图像的直方图)

    OpenCV 计算图像的直方图 计算图像的直方图是图像处理领域一个非经常见的基本操作. OpenCV 中提供了 calcHist 函数来计算图像直方图.只是这个函数说实话挺难用的,研究了好久才掌握了些 ...

  5. Opencv中integral计算积分图

    Paul Viola和Michael Jones在2001年首次将积分图应用在图像特征提取上,在他们的论文"Rapid Object Detection using a Boosted Ca ...

  6. 图像特征提取三大法宝:HOG特征,LBP特征,Haar特征(转载)

    (一)HOG特征 1.HOG特征: 方向梯度直方图(Histogram of Oriented Gradient, HOG)特征是一种在计算机视觉和图像处理中用来进行物体检测的特征描述子.它通过计算和 ...

  7. 图像特征提取三大法宝:HOG特征,LBP特征,Haar特征

    (一)HOG特征 1.HOG特征: 方向梯度直方图(Histogram of Oriented Gradient, HOG)特征是一种在计算机视觉和图像处理中用来进行物体检测的特征描述子.它通过计算和 ...

  8. opencv使用convexityDefects计算轮廓凸缺陷

    引自:http://www.xuebuyuan.com/1684976.html http://blog.csdn.net/lichengyu/article/details/38392473 htt ...

  9. [CV笔记]图像特征提取三大法宝:HOG特征,LBP特征,Haar特征

    (一)HOG特征 1.HOG特征: 方向梯度直方图(Histogram of Oriented Gradient, HOG)特征是一种在计算机视觉和图像处理中用来进行物体检测的特征描述子.它通过计算和 ...

随机推荐

  1. 左移运算符<<

    在许多计算机编程语言(例如:C语言.C++语言.Java语言.JavaScript语言,Pascal语言等)中,“<<”代表左移运算符(就相当于'shl').该运算符为双目运算符,结合方向 ...

  2. 关于Android Canvas.drawText方法中的坐标参数的正确解释

    canvas.drawText("www.jcodecraeer.com", x, y, paint);  x和y参数是指定字符串中心的坐标吗?还是左上角的坐标?这个问题的直观印象 ...

  3. Android BaseAdapter ListView (SD卡中文件目录显示出来)

    首先搭建activity_main.xml布局 搭建ListView中显示的布局 创建适配器 将File数据和UI适配 MainActivity中将ListView设置适配器,并设置监听 //获取SD ...

  4. 云平台编程与开发(七)-使用X5Cloud云平台开发网络彩讯

    云平台编程与开发(七)-使用X5Cloud云平台开发网络彩讯 博客分类: 云平台 云计算 Java Android Android 云平台 Java 网络彩讯定义以及工作大概流程  下载试用地址:ap ...

  5. [RxJS] Returning subscriptions from the subscribe function

    So far, when writing these subscribe functions, we haven't returned anything. It is possible return ...

  6. linux常见设备类型及文件系统

    As you can see in  Table   14.3   , all disk device names end with the letter a. That is because it ...

  7. spring-data-mongodb一个系统xml文件里面配置两个数据源

    spring-data-mongodb一个系统xml文件里面配置两个数据源 参考文档如下: http://www.iteye.com/problems/92789 http://stackoverfl ...

  8. (转)ASP.NET里面简单的记住用户名和密码

    using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Web; 5 using S ...

  9. libthrift0.9.0解析(三)之TProtocol&TTransport

       以上是transport和protocol的类图和结构图. transport封装了底层的传输通道,主要有read/write.open/close等基本的读写方法,而且都是对于二进制数据. p ...

  10. HTTP Content-type 对照表

    文件扩展名 Content-Type(Mime-Type) 文件扩展名 Content-Type(Mime-Type) .*( 二进制流) application/octet-stream .tif ...