图像仿射变换
要求:
–设计一个函数WarpAffine,可以对图像进行任意的二维仿射变换(用2*3矩阵表示):
–采用双线性插值进行重采样;
–可以只考虑输入图像为3通道,8位深度的情况;
函数接口可以参考OpenCV的warpAffine函数
调用WarpAffine,实现绕任意中心的旋转函数Rotate
图像变形
记[x’, y’]=f([x, y])为像素坐标的一个映射,实现f所表示的图像形变。f的逆映射为:
1.1双线性插值
对于4个相邻像素,分别进行水平和垂直方向进行线性插值
找到给定(i,j)点的周围四个顶点,对齐进行插值。
float billinear(float a, float b, float c, float d, float dx, float dy)
{
float h1 = a + dx * (b - a);
float h2 = c + dx * (d - c);
return h1 + dy * (h2 - h1);
}
float aa = rimg1.at(i / scale, j / scale);
float bb = rimg1.at(i / scale, j / scale+1);
float cc = rimg1.at(i / scale+1, j / scale);
float dd = rimg1.at(i / scale + 1, j / scale+1);
double ee = (double(j) / scale) - (j / scale);
double ff = (double(i) / scale) - (i / scale);
float xx = billinear(aa, bb, cc, dd, ff, ff);
rimg2.at(i, j) = saturate_cast(xx);
原始图像
以放大2倍为例:
原像素点的映射(有1/4的点可以直接通过映射找到对应原图像的点,这部分直接填充颜色即可)
对在正方形边界处的点进行另外处理,就会出现“点连成边”的效果
正方形内部的点通过双线性插值来填充填充:
当然,放大肯定会失真
接下来,尝试取更少的像素点,下面第一张是长宽按照1/10取点。
(1/100取点连边)
(1/100取点插值)
尝试对双线性插值的billinear函数进行修改:
a=b,即正方形内部的点用3个点,而不是4个来填充,或者dx=dy,会产生棱角的美感。
float xx = billinear(aa, aa, cc, dd, ee, ff);
float xx = billinear(aa, bb, cc, dd, ee, ee);
(初始图像)
(1/50插值)
(A=B)
(dx=dy)
1.2旋转的实现
原理:构造旋转矩阵,建立初始图像与新图像之间每一个像素点之间的映射关系。
逆矩阵:将变换矩阵函数中的自变量编程新图像,这个方便遍历新图像的每一个像素点通过逆矩阵找到对应的初始像素点进行操作。
void getmatrix(int degree,int dx,int dy)
{
double t1 = -dx * cos(degree) + dy * sin(degree) + dx;
double t2 = -dx * sin(degree) - dy * cos(degree) + dy;
rot3.at(0, 0) = cos(degree);
rot3.at(0, 1) = sin(degree);
rot3.at(0, 2) = -sin(degree)*t2-cos(degree)*t1;
rot3.at(1, 1) = cos(degree);
rot3.at(1, 0) = -sin(degree);
rot3.at(1, 2) = -cos(degree) * t2 +sin(degree) * t1;
rot3.at(0, 0) = rot3.at(0, 0) ;
rot3.at(1, 1) = rot3.at(1, 1) ;
}
Void warpaffine()//部分核心代码{
int xx = rot3.at(0, 0) * i + rot3.at(0, 1) * j + rot3.at(0, 2);
int yy = rot3.at(1, 0) * i + rot3.at(1, 1) * j + rot3.at(1, 2);
if (xx >= 0 && xx < rimg1.rows && yy >= 0 && yy < rimg1.cols)
rimg2.at(i, j)[z] = rimg1.at(xx, yy);
}
旋转后
那么如果我们对仿射矩阵稍作变化,就会得到一些不同的效果。
1.3不同重采样方法之间的对比
(1)初始图像:(100100)(200200),分辨率96.
(2)
最近邻Nearest:
找到最近的像素,输出颜色:
(3)双线性差值Liner
对于4个相邻像素,分别进行水平和
垂直方向进行线性插值
float bilinear(float a, float b, float c,
float d, float dx, float dy)
{
float h1=a+dx*(b-a);
// = (1-dx)a + dxb
float h2=c+dx*(d-c);
return h1+dy*(h2-h1);
}
(4)双三次插值
比较发现:最近邻的效果是明显较差的,双三次相比双线性在算法效果上可能更有优势,但是在图像最后的放大呈现上的差距并不是很大,且双三次会有相对更高的算大复杂度。
在对Photoshop等图像处理软件的最后放大效果同各种算法进行比对后发现,Photoshop的放大效果要比最近邻要好,但大图像的处理上比起双线性插值略差,放大倍数不是很大的话二者差距很小。
其他方法:
Lanczos photoshop
但即便使用4x4像素邻域的双三次插值或者8x8像素邻域的Lanczos插值,还是可以很明显的看到图像会有失真效果,这种失真现象在反打4倍以后更加明显。
8x8像素邻域的Lanczos插值
实验二 图像变形
1.记[x’, y’]=f([x, y])为像素坐标的一个映射,实现f所表示的图像形变。f的逆映射为:
这个逆映射是归一化中心坐标之后的映射,即原图像和新图像都要进行中心归一化坐标,才有上面的对应关系
坐标归一化:
逆变换要求的意为:在给定椭圆(r限制)外不进行图像的变形,在椭圆内进行给定的正弦余弦变换
double X = x / ((row - 1) / 2.0) - 1.0;//归一化
double Y = y / ((col - 1) / 2.0) - 1.0;
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);
color(X,Y)=color(x_,y_);
按照要求得到的形变效果如下所示:
修改参数theta,可以修改形变角度,产生不同程度的形变。
Theta=1-r
Theta=(1-r)3
稍加修改逆矩阵,就可以看到形变的具体范围
问题:
- 一组namedWindow,imshow函数中出现两个窗口:
描述:我在debug模式下运行只有一个窗口,但是到release模式下就变成两个窗口,一个图片,一个灰色区域
解释:这是因为在配置cv时,为了保证可用,添加了两个版本lib,opencv_world341d.lib和opencv_world341.lib(链接器-输入-附加依赖项),带d为debug版,不带d为release版。release有两个debug没有,是两个的lib添加顺序问题。
解决:删除多余的lib或者换一下模式即可。
2. 在旋转操作中会出现旋转后的图像只能呈现一部分的问题
这是因为原始图像上的部分点对应过来新建的图像会有部分越界,这部分越界的会被丢弃
解决方法:可以先对原图像缩小一下再进行旋转,或者对应之后进行一下等比例变化。
大多数分析和体会都在上面实验内容写完了,这里补充一下吧。
(1) Opencv的warpAffine函数
void cv::warpAffine(InputArray src,OutputArray dst,InputArray M,Size dsize,int flags = INTER_LINEAR, int borderMode = BORDER_CONSTANT, const Scalar & borderValue = Scalar() )
src: 输入图像
dst: 输出图像,尺寸由dsize指定,图像类型与原图像一致
M: 2X3的变换矩阵
dsize: 指定图像输出尺寸
flags: 插值算法标识符,默认值INTER_LINEAR,
(2)OpenCV图像缩放使用的函数是:resize
void resize(InputArray src, OutputArray dst, Size dsize, double fx=0, double fy=0, int interpolation=INTER_LINEAR )
InputArray src -原图像
OutputArray dst -输出图像
Size dsize -目标图像的大小
double fx=0 -在x轴上的缩放比例
double fy=0 -在y轴上的缩放比例
int interpolation -插值方式,有以下四种方式
INTER_NN -最近邻插值
INTER_LINEAR -双线性插值 (缺省使用)
INTER_AREA -使用象素关系重采样,当图像缩小时候,该方法可以避免波纹出现。当图像放大时,类似于 INTER_NN 方法。
INTER_CUBIC -立方插值。
(3)getRotationMatrix2D(Point2f center, double angle, dooule scale)
给定中心,角度和比例,得到旋转矩阵。
(4)逆矩阵
对于绕任意中心进行旋转,需要求取正变换的逆矩阵,正变换矩阵如下:
`
//不同重采样方法之间的比较
#include<opencv2/core/core.hpp>
#include<opencv2/imgcodecs.hpp>
#include<opencv2/core/saturate.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<iostream>
#include <imgproc.hpp>
#include <shape.hpp>
#include <filesystem>
using namespace std;
using namespace cv;
int main()
{
Mat img = imread("D:\\picture\\flower.jpg");
if (img.empty())
cerr << "can not load image" << endl;
Mat largeimg1;
Mat largeimg2;
Mat largeimg3;
Mat largeimg4;
Size dsize;
resize(img,largeimg1,dsize,2, 2, INTER_NEAREST);
resize(img,largeimg2, dsize, 2, 2, INTER_LINEAR);
resize(img, largeimg3, dsize, 2, 2, INTER_CUBIC);
resize(img, largeimg4, dsize, 4, 4, INTER_LANCZOS4);
//imwrite("D:\\picture\\flowernerst.jpg", largeimg1);
//imwrite("D:\\picture\\flowerliner.jpg", largeimg2);
//imwrite("D:\\picture\\flowerlanczos4.jpg", largeimg4);
//imwrite("D:\\picture\\flowerthree.jpg", largeimg3);
namedWindow("nearest", 1);
imshow("nearest", largeimg1);
namedWindow("linear", 1);
imshow("linear",largeimg2);
namedWindow("three", 1);
imshow("linear", largeimg3);
namedWindow("lanczos4", 1);
imshow("lanczos4", largeimg4);
waitKey();
} ``
//图像旋转和双线性插值
#include<opencv2/core/core.hpp>
#include<opencv2/imgcodecs.hpp>
#include<opencv2/core/saturate.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<iostream>
#