计算机视觉——几何变换与变形

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/weixin_43943977/article/details/102511052

实验要求:
2.1 图像仿射变换
–设计一个函数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
在这里插入图片描述
稍加修改逆矩阵,就可以看到形变的具体范围
在这里插入图片描述
问题:

  1. 一组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> 
#include <imgproc.hpp>
#include <shape.hpp>
#include <filesystem>


using namespace std;
using namespace cv;
Mat rot3(2, 3, CV_64FC1, cv::Scalar(0, 0, 0));
Mat rot4(2, 3, CV_64FC1, cv::Scalar(0, 0, 0));

void rotation(double scale)
{
	rot4.at<double>(0, 0) = scale;
	rot4.at<double>(1,1) = scale;
	rot4.at<double>(0, 1) = 0;
	rot4.at<double>(0, 2) = 0;
	rot4.at<double>(1, 0) = 0;
	rot4.at<double>(1, 2) = 0;
}
//双线性插值
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);
}
void Liner(Mat& rimg2, Mat rimg1, int scale, int cols, int rows)
{
	for (int i = 0; i < rows; ++i)
	{
		for (int j = 0; j < cols; ++j)
		{
			if(i%scale==0&&j%scale==0)//说明在定点处
				for (int z = 0; z < 3; ++z)
				{
					rimg2.at<Vec3b>(i, j)[z] = rimg1.at<Vec3b>(i / scale, j / scale)[z];//像素值对应
				}
			else if(i % scale == 0)
			{

				for (int z = 0; z < 3; ++z)
				{
					float aa = rimg1.at<Vec3b>(i / scale, j / scale)[z];
					float bb = rimg1.at<Vec3b>(i / scale, j / scale+1)[z];
					double ee = (double(j) / scale)- (j / scale); double ff = 0;
					float xx = billinear(aa, bb, aa, bb, ee, ff);
					rimg2.at<Vec3b>(i, j)[z] = saturate_cast<uchar>(xx);
				}
			}
			else if(j % scale == 0)
			{
				for (int z = 0; z < 3; ++z)
				{
					float aa = rimg1.at<Vec3b>(i / scale, j / scale)[z];
					float bb = rimg1.at<Vec3b>(i / scale+1 , j / scale)[z];
					double ee = 0; double ff = (double(i) / scale) - (i / scale);
					float xx = billinear(aa, aa, bb, bb, ee, ff);
					rimg2.at<Vec3b>(i, j)[z] = saturate_cast<uchar>(xx);
				}
			}
			else
			{
				for (int z = 0; z < 3; ++z)
				{
					
					float aa = rimg1.at<Vec3b>(i / scale, j / scale)[z];
					float bb = rimg1.at<Vec3b>(i / scale, j / scale+1)[z];
					float cc = rimg1.at<Vec3b>(i / scale+1, j / scale)[z];
					float dd = rimg1.at<Vec3b>(i / scale + 1, j / scale+1)[z];
					double ee = (double(j) / scale) - (j / scale); double ff = (double(i) / scale) - (i / scale);
					float xx = billinear(aa, bb, cc, dd, ee, ff);
					rimg2.at<Vec3b>(i, j)[z] = saturate_cast<uchar>(xx);
				}
			}
		}
	}
	return;
}
//只描绘定点 
void Liner2(Mat& rimg2, Mat rimg1, int scale, int cols, int rows)
{
	for (int i = 0; i < rows; ++i)
	{
		for (int j = 0; j < cols; ++j)
		{
			if (i % scale == 0 && j % scale == 0)//说明在定点处
				for (int z = 0; z < 3; ++z)
				{
					//	if(rimg1.at<Vec3b>(xx, yy))
					rimg2.at<Vec3b>(i, j)[z] = rimg1.at<Vec3b>(i / scale, j / scale)[z];//像素值对应
					//else 
					//rimg2.at<Vec3b>(i, j)[z] = saturate_cast<uchar>(0);
				}
			else if (i % scale == 0)
			{

				for (int z = 0; z < 3; ++z)
				{
			
					rimg2.at<Vec3b>(i, j)[z] = saturate_cast<uchar>(0);
				}
			}
			else if (j % scale == 0)
			{
				for (int z = 0; z < 3; ++z)
				{

					rimg2.at<Vec3b>(i, j)[z] = saturate_cast<uchar>(0);
				}
			}
			else
			{
				for (int z = 0; z < 3; ++z)
				{
					
					rimg2.at<Vec3b>(i, j)[z] = saturate_cast<uchar>(0);
				}
			}
		}
	}
	return;
}
//WarpAffine函数
void WarpAffine(Mat&rimg2, Mat rimg1, Mat rot3, int cols,int rows)
{
	for (int i = 0; i < rows; ++i)
	{
		for (int j = 0; j < cols; ++j)
		{
			int xx = rot3.at<double>(0, 0) * i + rot3.at<double>(0, 1) * j + rot3.at<double>(0, 2);
			int yy = rot3.at<double>(1, 0) * i + rot3.at<double>(1, 1) * j + rot3.at<double>(1, 2);
			if (xx >= 0 && xx < rimg1.rows && yy >= 0 && yy < rimg1.cols)
			{
				for (int z = 0; z < 3; ++z)
				{
					rimg2.at<Vec3b>(i, j)[z] = rimg1.at<Vec3b>(xx, yy)[z];
	
				}
			}
			else
			{
				for (int z = 0; z < 3; ++z)
				{

					rimg2.at<Vec3b>(i, j)[z] = saturate_cast<uchar>(0);
				}
			}
		}
	}
	return;
}
//得到变换矩阵函数 
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<double>(0, 0) = cos(degree) * 2;
	rot3.at<double>(0, 1) = sin(degree);
	rot3.at<double>(0, 2) = (-sin(degree) * t2 - cos(degree) * t1) * 2-200 ;
	rot3.at<double>(1, 1) = cos(degree);
	rot3.at<double>(1, 0) = -sin(degree);
	rot3.at<double>(1, 2) = -cos(degree) * t2 + sin(degree) * t1 ;

}
int main()
{
	Mat img1 = imread("D:\\picture\\girl.jpg");
	if (img1.empty())
		cerr << "can not load image" << endl;
	Point2f center = Point2f(img1.cols / 2.0, img1.rows / 2.0);
	double degree = 1;
	double scale = 0.5;//缩小一下,使得整个屏幕都可以看到
	cout << img1.cols / 2.0 << img1.rows / 2.0 << endl;
	// 输出图像的中心坐标
	//Mat rot = getRotationMatrix2D(center, degree, scale);
	//直接调用线程的库函数得到变换矩阵
	double aa = 0.5000000000000001;
	getmatrix(degree, img1.cols / 3.0, img1.rows / 3.0);
	//这里是使用自己写的函数来得到矩阵
	rotation(scale);
	//这是一个缩放比例尺,调用的rot4矩阵
	//cout << rot  <<endl;
	//cout << rot3 << endl;
	//cout << rot4 << endl;
	Mat rimg1 = Mat::zeros(img1.size(), CV_8UC3);
	warpAffine(img1, rimg1, rot4, img1.size());
	//这一步是缩小
	//Mat rimg2 = Mat::zeros(img.size(), img.type());//获取图像,建立新的像素矩阵
	Mat rimg2(img1.rows*2 , img1.cols*2 , CV_8UC3);
	warpAffine(rimg1, rimg2, rot3, rimg2.size());
	//WarpAffine(rimg2, rimg1, rot3, rimg2.cols,rimg2.rows);
	//这一步是旋转
	namedWindow("img", 0);
	resizeWindow("img", 400, 400);
	imshow("img", img1);
	Size dsize;
	//Liner(rimg2, rimg1, 100, img1.cols, img1.rows);
	namedWindow("rimg", 1);
	//resizeWindow("rimg", 400, 400);
	imshow("rimg", rimg2);	waitKey();

} 
//中心归一化和变形 
#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;
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]);
			}//像素点颜色等于原来的额像素点颜色就像ok 
			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];//这是,,找到原图像周围的4个点 

							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;
}
int main()
{
	Mat img1 = imread("D:\\picture\\monalisa.jpg");
	Mat img2 = changeShape(img1);
	namedWindow("mona", 1);
	imshow("mona", img2);
	waitKey();
	return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_43943977/article/details/102511052