个人博客:http://www.chenjianqu.com/
原文链接:http://www.chenjianqu.com/show-4.html
图像的几何变换
为了达到某种事件效果,需要变换输入图像的像素位置,通过把图像的像素位置映射到一个新的位置达到改变原图像显示效果的目的,就是图像的仿射变换。
1. 图像平移
将一幅图像中像素按照指定的x方向和y方法移动deltaX和deltaY方向,平移后的图像对应原图。
代码实现:
Mat TranslateTransform(Mat& src, int x, int y)
{
Mat dst(src.rows, src.cols, src.type(), Scalar(0));
if (src.channels() == 1)
{
for (int i = 0; i < src.rows; i++) {
uchar *srcRow = src.ptr(i);
int indexI = i + y;
if (indexI >= src.rows)
break;
if (indexI < 0)
continue;
uchar *dstRow = dst.ptr(indexI);
for (int j = 0; j < src.cols; j++)
{
int indexJ = j + x;
if (indexJ >= src.cols)
break;
if (indexJ < 0)
continue;
dstRow[indexJ] = srcRow[j];
}
}
}
return dst;
}
效果图:x和y方向别平移-100个像素
2. 镜像变换
图像的镜像变换不改变图像的形状,分为图像的水平镜像和垂直镜像变换两种。
图像的水平镜像:x=x0, y=N-y0+1 实际使用时-1
图像的垂直镜像:y=y0, x=M-x0+1 实际使用时-1
代码实现如下:
//图像水平镜像变换
Mat HorizontalMirrorTransform(Mat& src)
{
Mat dst(src.rows, src.cols, src.type(), Scalar(0));
if (src.channels() == 1)
{
for (int i = 0; i < src.rows; i++) {
uchar *srcRow = src.ptr(i);
uchar *dstRow = dst.ptr(i);
for (int j = 0; j < src.cols; j++)
dstRow[j] = srcRow[src.cols-j-1];
}
}
return dst;
}
//图像垂直镜像变换
Mat VerticalMirrorTransform(Mat& src)
{
Mat dst(src.rows, src.cols, src.type(), Scalar(0));
if (src.channels() == 1)
{
for (int i = 0; i < src.rows; i++) {
uchar *srcRow = src.ptr(src.rows-i-1);
uchar *dstRow = dst.ptr(i);
for (int j = 0; j < src.cols; j++)
dstRow[j] = srcRow[j];
}
}
return dst;
}
程序运行结果:
图像垂直镜像翻转:
图像水平镜像翻转:
3. 图像转置
图像转置变换就是将图像显示坐标的x y轴对换,相当于图像逆时针旋转了90度
Mat TransposingTransform(Mat& src)
{
//转置图像的size和原图相反
Mat dst(src.cols, src.rows, src.type(), Scalar(0));
if (src.channels() == 1)
{
for (int i = 0; i < dst.rows; i++) {
for (int j = 0; j < dst.cols; j++)
dst.at<uchar>(i, j) = src.at<uchar>(j, i);
}
}
return dst;
}
结果:
4. 图像的旋转变换
图像旋转的原理请看博客:https://blog.csdn.net/liyuan02/article/details/6750828
这里是根据反向映射的方式实现的,参考博文:
https://blog.csdn.net/lkj345/article/details/50555870
对于旋转以后图像大小不变的情况,旋转前后图像的中心点坐标都是(a,b),那么旋转的变换矩阵就是:
代码实现:
//参数:原图,旋转中心,旋转角度
Mat RotationTransform(Mat& src,double angle)
{
Mat dst(src.rows, src.cols, src.type(), Scalar(0));
//弧度
double sita = angle * CV_PI / 180;
double a = (src.cols - 1) / 2.0 + 0.5;
double b = (src.rows - 1) / 2.0 + 0.5;
int nRowNum = src.rows;
int nColNum = src.cols;
double f1 = -a * cos(sita) + b * sin(sita) + a;
double f2 = -a * sin(sita) - b * cos(sita) + b;
if (src.channels() == 3)
{
for (int i = 0; i < nRowNum; i++)
{
for (int j = 0; j < nColNum; j++)
{
int x = cvRound(j * cos(sita) - i * sin(sita) + f1);
int y = cvRound(j * sin(sita) + i * cos(sita) + f2);
if (x > 0 && x < nColNum && y > 0 && y < nRowNum)
{
dst.at<Vec3b>(i, j) = src.at<Vec3b>(y, x);
}
}
}
}
return dst;
}
旋转120度的效果:
5. 错切变换
图像错切变换在图像几何形变方面非常有用,常见的错切变换分为X方向与Y方向的
错切变换。水平错切(或平行于X轴的错切)是一个将任一点(x,y)映射到另一个点(x+m,y)的操作,m是固定参数,称为错切因子。竖直错切的操作类似,就是将x和y互换位置。
水平错切的效果是将每一点水平移动,移动的长度和该点的纵坐标成比例。若m>0则x轴上方的所有点都向右移动,否则x轴上方的所有点都向左移动,x轴下方点移动的方向对应相反,而x坐标轴上的点位置不变。平行于x轴的直线保持不变,其他所有线绕与x轴交点转动不同的角度;原来竖直的线则变成斜率1/m的斜线,如此参数m=cot(theta),theta即竖直线倾斜后的倾角,称为错切角。
垂直错切类似。
对应的数学矩阵分别如下:
根据上述矩阵假设P(x1, y1)为错切变换之前的像素点,则错切变换以后对应的像素
P’(x2, y2)当X方向错切变换时:
当Y方向错切变换时:
错切图像输出大小确定
由于错切变换会导致像素位置的移动,所以若不裁剪图片,输出图像的大小会改变。输出图像的大小可推导出来。
以下是我的第一次推导的过程,假设x、y方向同时错切:
根据这个代码实现的程序如下:
Mat TangentTransformation(Mat& src, double angleX, double angleY)
{
double b = tan(angleX*3.1415926/180);
double d = tan(angleY*3.1415926/180);
double den = 1 + b * d;
//错切变换输出图像与原图不同
int w = src.cols + b * src.rows;
int h = src.rows + d * w;
Mat dst(h,w , src.type(), Scalar(0));
if (src.channels() == 3)
{
for (int i = 0; i < dst.rows; i++) {
for (int j = 0; j < dst.cols; j++)
{
int y = int((i - d * j) / den);
int x = int(j - b * y);
if(x>0&&x<src.cols&&y>0&&y<src.rows)
dst.at<Vec3b>(i, j) = src.at<Vec3b>(y, x);
}
}
}
return dst;
}
你会发现长宽明明没错,但是映射后的图像确总是超出理论的高度。后来经过检查,我发现其实我的推导是有问题的,正确的推导如下:
代码实现:
Mat TangentTransformation(Mat& src, double angleX, double angleY)
{
double b = tan(angleX*3.1415926/180);
double d = tan(angleY*3.1415926/180);
//错切变换输出图像与原图不同
int w = src.cols + b * src.rows;
int h = src.rows + d * w;
Mat dst(h,w , src.type(), Scalar(0));
if (src.channels() == 3)
{
for (int i = 0; i < dst.rows; i++) {
for (int j = 0; j < dst.cols; j++)
{
int x_ = j;
int y_ = i;
int x = x_ - b * y_ + b * d*x_;
int y = y_ - d * x_;
if(x>0&&x<src.cols&&y>0&&y<src.rows)
dst.at<Vec3b>(i, j) = src.at<Vec3b>(y, x);
}
}
}
return dst;
}
此时映射正确:
美滋滋。