CV图像基本操作【3】——仿射变换与变形(affine tran
2019-05-11 本文已影响0人
Mr_Relu
编程环境:
VS + OpenCV + C++
完整代码已经更新至GitHub,欢迎fork~GitHub链接
声明:创作不易,未经授权不得复制转载
statement:No reprinting without authorization
内容
-设计一个函数WarpAffine,可以对图像进行任意的二维仿射变换(用2*3矩阵表示);
-采用双线性插值进行重采样;
-可以只考虑输入图像为3通道,8位深度的情况;
-函数接口可以参考OpenCV的warpAffine函数;
-调用WarpAffine,实现绕任意中心的旋转函数Rotate;
原理简介
记[x’, y’]=f([x, y])为像素坐标的一个映射,实现f所表示的图像形变。f的逆映射为:
image.png
image.png
image.png
一、仿射变换具体实现
1. 参考opencv定义函数接口等,如下:
image.png-my_warpAffine,可以对图像进行任意的二维仿射变换(用2*3数组表示矩阵),
-CalcRotationMatrix,函数得到进行指定变换的数组;
//注意二维数组应定义成double类型
2. 对于绕任意中心进行旋转,需要求取正变换的逆矩阵,正变换矩阵如下:
image.pngimage.png
3. 而后设计双线性插值的实现:
image.png由于映射辉源图坐标后可能会发生越界,需注意处理:异常如下:
image.png
处理越界code
if (x_ < 0 || y_ < 0||x_>=src.rows||y_>=src.cols) {
for (int c = 0; c < 3; c++) {
dst.at<Vec3b>(x, y)[c] = saturate_cast<uchar>(0);
}
}
else {
for (int c = 0; c < 3; c++) {
//计算双线性插值
//左上角坐标(X1,Y1)
int X1 = (int)x_;
int Y1 = (int)y_;
//四个顶点像素值
//注意访问越界
if (X1 == (src.rows - 1) || Y1 == (src.cols - 1)||X1==0||Y1==0) {
dst.at<Vec3b>(x, y)[c] = saturate_cast<uchar>(src.at<Vec3b>(X1, Y1)[c]);
}
else {
int aa = src.at<Vec3b>(X1, Y1)[c];
int bb = src.at<Vec3b>(X1, Y1+1)[c];
int cc = src.at<Vec3b>(X1+1, Y1)[c];
int dd = src.at<Vec3b>(X1+1, Y1+1)[c];
double dx = x_ - X1;
double dy = y_ - Y1;
double h1 = aa + dx * (bb - aa);
double h2 = cc + dx * (dd - cc);
dst.at<Vec3b>(x, y)[c] = saturate_cast<uchar>(h1+dy*(h2-h1));
}
}
4. 测试结果如下:
image.pngimage.png
image.png
二、变形具体实现
函数设计:
Mat changeShape(const Mat &src)
注意中心归一化后和还原:
image.png结果测试如下:
image.png
image.png
code
Mat changeShape(const Mat &src) {
Mat imgAffine = Mat::zeros(src.rows, src.cols, src.type());
int row = imgAffine.rows, col = imgAffine.cols;
for (int x = 0; x < row; x++) {
for (int y = 0; y < col; y++) {
double X = x / ((row - 1) / 2.0) - 1.0;
double Y = y / ((col - 1) / 2.0) - 1.0;
double r = sqrt(X * X + Y * Y);
if (r >= 1) {
imgAffine.at<Vec3b>(x, y)[0] = saturate_cast<uchar>(src.at<Vec3b>(x, y)[0]);
imgAffine.at<Vec3b>(x, y)[1] = saturate_cast<uchar>(src.at<Vec3b>(x, y)[1]);
imgAffine.at<Vec3b>(x, y)[2] = saturate_cast<uchar>(src.at<Vec3b>(x, y)[2]);
}
else {
double theta = 1.0 + X * X + Y * Y - 2.0*sqrt(X * X + Y * Y);//修改不用(1-r)*(1-r)
double x_ = cos(theta)*X - sin(theta)*Y;
double y_ = sin(theta)*X + cos(theta)*Y;
x_ = (x_ + 1.0)*((row - 1) / 2.0);
y_ = (y_ + 1.0)*((col - 1) / 2.0);
if (x_ < 0 || y_ < 0||x_>=src.rows||y_>=src.cols) {
for (int c = 0; c < 3; c++) {
imgAffine.at<Vec3b>(x, y)[c] = saturate_cast<uchar>(0);
}
}
else {
//左上角坐标(X1,Y1)
//计算双线性插值
int X1 = (int)x_;
int Y1 = (int)y_;
for (int c = 0; c < 3; c++) {
if (X1 == (src.rows - 1) || Y1 == (src.cols - 1)) {
imgAffine.at<Vec3b>(x, y)[c] = saturate_cast<uchar>(src.at<Vec3b>(X1, Y1)[c]);
}
else {
//四个顶点像素值
//注意访问越界
int aa = src.at<Vec3b>(X1, Y1)[c];
int bb = src.at<Vec3b>(X1, Y1 + 1)[c];
int cc = src.at<Vec3b>(X1 + 1, Y1)[c];
int dd = src.at<Vec3b>(X1 + 1, Y1 + 1)[c];
double dx = x_ - (double)X1;
double dy = y_ - (double)Y1;
double h1 = aa + dx * (bb - aa);
double h2 = cc + dx * (dd - cc);
imgAffine.at<Vec3b>(x, y)[c] = saturate_cast<uchar>(h1 + dy * (h2 - h1));
}
}
}
}
}
}
return imgAffine;
}