图像处理中缩放因子

2018-01-14 01:14:54 u013165921 阅读数 4132
  • SIFT特征提取2

    了解Hough变换基本思想及直线检测步骤; 掌握Harris角点检测的基本思想及实现步骤; 了解尺度空间概念及SIFT特征提取的基本思想 了解ORB快速特征检测及BRIEF特征建立方法 掌握前述算子的OpenCV实现

    2606人学习 CSDN就业班
    免费试看

实验要求

(1.a) 编写一个以2 的幂次方将给定图像的灰度级数从256 减少到2 的程序。图像的灰度级数以参数变量的形式传递到所编写的程序中。

(1.b) 使用图2.21(a) 以(1.a)中编写的程序生成图2.21 所示的各个结果。

(2.a) 编写一个基于像素复制方式进行图像缩放的程序,假设缩放因子为整数。忽略混叠效应。

(2.b) 用编写的程序将图2.19 (a)从1024 x 1024 缩小到 256 x 256。

(2.c) 用编写的程序将(2.b)中的结果图像放大到1024 x 1024. 并解释与原图产生差异的原因。

(3.a) 编写一个以双线性插值技术进行图像缩放的程序,程序的输入参数为结果图像的水平和垂直方向的像素数。忽略混叠效应。

(3.b) 用编写的程序将图2.19 (a)从1024 x 1024 缩小到 256 x 256。

(3.c) 用编写的程序将(3.b)中的结果图像放大到1024 x 1024. 并解释与原图产生差异的原因。


技术论述

1、 图像灰度级变化
  灰度图像是一种具有从黑到白256级灰度色阶或等级的单色图像。该图像中的每个像素用8位数据表示,因此像素点值介于黑白间的256种灰度中的一种。该图像只有灰度等级,而没有颜色的变化。
  灰度即色阶或灰阶,又称中间色调,是指亮度的明暗程度。图像灰度级指图像中的色度分量亮度的最大值与最小值之差的级别,级数越多,图像亮度范围就越丰富,图像质量越好,反之,级数越少时图像质量越差。当图像只有2个灰度色阶时,称之为二值图像。
  本实验中,将给定图像的灰度级数以2的幂次方从256减少到2,所采用量化方法为:

灰度级数=(原灰度级数 / 量化级数)* 量化级数

2、 像素复制方法
  图像缩放中,像素复制方法的原理是对原来输入图像的整行或是整列像素进行简单的复制与删除,达到改变图像大小的目的。该方法计算量小、运算速度快,但容易产生失真,不适合处理具有大量细节的图像。

3、双线性插值技术
  双线性插值又称双线性内插,当图像放大时,结果图像中新的像素值,是由原图像像素位置的临近点像素值通过加权平均计算得出的。
  当对相邻四个像素点采用双线性插值时,所得表面在邻域处是吻合的,但斜率不吻合,并且双线性灰度插值的平滑作用可能使得图像的细节产生退化,这种现象在进行图像放大时尤其明显。
  双线性插值的运算量比像素复制方法大,但是其处理结果更接近于原图像的细节。


实验结果

这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述

这里写图片描述


实验程序

% 主函数

% 灰度级数降低
f0 = imread('Fig2.21(a).jpg');                           % CT投影图像
subplot(2,4,1);imshow(f0);title('Fig2.21(a)原图像');      % 显示原图像
for i = 1:7                 
    subplot(2,4,i+1);
    imshow(grayleveldec(f0,i));                          % for循环显示灰度级降低后的图像
    title(sprintf("%d级灰度图像", 2^(8-i)));                
end


% 像素复制
f = imread('Fig2.19(a).jpg');                            % 玫瑰图像
f_shrink = pixel_duplication(f,-4);                      % 图像缩小:像素复制
f_zoom = pixel_duplication(f_shrink ,4);                 % 图像放大:像素复制
figure;imshow(f);title('Fig2.19(a)原图像');
figure;imshow(f_shrink);title('像素复制缩小');
figure;imshow(f_zoom);title('像素复制放大');


% 双线性插值
f1_shrink = bilinear(f,256,256);                        % 图像缩小:双线性插值
f1_zoom = bilinear(f1_shrink,1024,1024);                % 图像放大:双线性插值
figure;imshow(f1_shrink);title('双线性插值缩小');
figure;imshow(f1_zoom);title('双线性插值放大');
function imt = grayleveldec(ima,factor)
%功能: 将原图像的灰度级按照2的foctor次幂减少

if factor < 0 
    factor = 0;
end
if factor > 8
    factor = 8
end      
dfact = uint8(power(2, factor));   
imt = (ima / dfact) * dfact;                           % 取整量化
return;
function img_duplication = pixel_duplication(img_input,factor)
% 像素复制方法

[M,N] = size(img_input);
if factor == 0                                        % 图像不进行缩放
    img_duplication  = img_input;
elseif factor > 0                                     % 图像放大
    for i = 1 : M * factor
        for j = 1 : N * factor
            img_duplication(i,j) = img_input(ceil(i/factor),ceil(j/factor));
            % 像素坐标下标从1开始而非0,故需要向上取整即使用ceil(x)函数
        end
    end
else                                                  % 图像缩小
    for i = 1 : M / abs(factor)
        for j = 1 : N / abs(factor)
            img_duplication(i,j) = img_input(i*abs(factor),j*abs(factor));
        end
    end
end

end
function img_bilinear=bilinear(img_input,x_pixel,y_pixel)  
% 双线性插值

[M,N]=size(img_input);                               % 图像的行数列数  
img_input=double(img_input);                         % 转换为双精度类型  
n = x_pixel/M;                                       % 缩放因子

% 为解决边界溢出问题,扩大原图像的四边边缘
img_temp= zeros(M+2,N+2);
img_temp(2:M+1,1) = img_input(:,1);                  % 左边,长度M
img_temp(2:M+1,N+2) = img_input(:,N);                % 右边,长度M
img_temp(2:M+1,2:N+1) = img_input(:,:);              % 中间,图像复制
img_temp(1,:) = img_temp(2,:);                       % 上边,长度N+2
img_temp(M+2,:) = img_temp(M+1,:);                   % 下边,长度N+2

M1 = round((M+2)*n);                                 % 计算加边后缩放的图像的行数 
N1 = round((N+2)*n);                                 % 计算加边后缩放的图像的列数     
img_res = zeros(M1,N1);     

for i=round(n+1):round(x_pixel+n)  
    for j=round(n+1):round(y_pixel+n)  
        x=i/n;                                       % 缩放后的图像坐标在原图像处的位置  
        y=j/n;  
        u=x-floor(x);                     
        v=y-floor(y);  
        img_res(i,j)=u*v*img_temp(x-u,y-v)+(1-u)*v*img_temp(x-u,y-v+1)+...
            u*(1-v)*img_temp(x-u+1,y-v)+(1-v)*(1-u)*img_temp(x-u+1,y-v+1);  
    end  
end  
img_bilinear=img_res(n+1:x_pixel+n,n+1:y_pixel+n);        
img_bilinear=uint8(img_bilinear);  
end 
2016-11-27 00:05:15 qq_24894159 阅读数 4483
  • SIFT特征提取2

    了解Hough变换基本思想及直线检测步骤; 掌握Harris角点检测的基本思想及实现步骤; 了解尺度空间概念及SIFT特征提取的基本思想 了解ORB快速特征检测及BRIEF特征建立方法 掌握前述算子的OpenCV实现

    2606人学习 CSDN就业班
    免费试看

在模式识别及计算机视觉中,要经常进行图像的变化。

例如:在识别手写数字中,我们可能在广泛应用中要求所有的图片都是20*20这么好的规格。所以,我们就需要进行缩放来达到目的。

今天来总结下学到的图像的基本变换。

首先我们计(w,v)为源图像的像素点位置,(x,y)为目标像素点的位置。我们当前有一个变化因子记为T,这三者之前存在着这样的映射关系:

(x,y,1)=(w,v,1)T(0)

T是一个3×3的矩阵
T=100010001(1)

相信用过java&android中图像变化的对Matrix这个对象有所了解。想再了解下可以看这位博主写的博客,非常详细!
http://www.cnblogs.com/qiengo/archive/2012/06/30/2570874.html

这里我介绍octave以此来实现图像变化的方法。

我们以尺度变化为例

变换名称 仿射矩阵 坐标公式
恒等变换 100010001 x=wy=v
尺度变换 Cx000Cy0001 x=Cxwy=Cyv
旋转变换 cosθsinθ0sinθcosθ0001 x=wcosθvsinθy=vsinθ+cosθ
平移变换 10tx01ty001 x=w+txy=v+ty
偏移变换 1sv0sh10001 x=wsv+vy=wsh+v

实际上可以按照两种方式使用上表格:
- 前向映射
按照上述0式直接进行计算,不过存在一个问题:输入图像中的两个和多个像素可被映射到同一个位置;还可能产生某些像素根本没有赋值的情况
- 反响映射
定义:扫描输出像素的位置,并在每一个位置(x,y)使用(w,v)=T1(x,y)计算输入图像的相应位置,然后内插法
以下为代码实现大小变化函数

function [Image] = scale (img, scal)
T = [1,0,0;0,1,0;0,0,1];
tT = T.*scal;
tT(3,3) = 1;
flag = sign(scal);
[x,y,z] = size(img);
tx = ceil(x*scal);
%ceil为向上取整函数
ty = ceil(y*scal);
Image = zeros(tx,ty);
for i=1:tx
  for j=1:ty
    temp = ceil((tT^-1)*[i;j;1]);
    Image(i,j) = img(temp(1),temp(2));
end;
end;

endfunction

结果图片不太明显,不过,可以看红框,变化成功,图像基本没有失真。

2017-03-08 16:46:57 chaipp0607 阅读数 3317
  • SIFT特征提取2

    了解Hough变换基本思想及直线检测步骤; 掌握Harris角点检测的基本思想及实现步骤; 了解尺度空间概念及SIFT特征提取的基本思想 了解ORB快速特征检测及BRIEF特征建立方法 掌握前述算子的OpenCV实现

    2606人学习 CSDN就业班
    免费试看

图像的缩放主要用于改变图像的大小,缩放后图像的图像的宽度和高度会发生变化。在图像处理中是一种很基础的几何变换,但是具有很重要的作用,比如:当输入图片尺寸过大时,处理速度会很慢,适当的缩小图像可以在不影响处理效果的同时有效提高代码执行速度。
opencv提供了resize函数实现图片缩放功能,函数原型为:

CV_EXPORTS_W void resize( 
InputArray src, 
OutputArray dst,
Size dsize, 
double fx=0, 
double fy=0,
int interpolation=INTER_LINEAR );

其中:
第一个参数为输入图像
第二个参数为输出图像
第三个参数为输出图像和输入图像尺寸(包含长宽)
第四个参数为输出图像和输入图像水平方向上的比例
第五个参数为输出图像和输入图像垂直方向上的比例
第六个参数为插值方法:
CV_INTER_NN - 最近邻插值,
CV_INTER_LINEAR - 双线性插值 (缺省使用)
CV_INTER_AREA - 使用象素关系重采样。当图像缩小时候,该方法可以避免波纹出现。当图像放大时,类似于 CV_INTER_NN 方法..
CV_INTER_CUBIC - 立方插值.

需要注意的是:
dsize是一个Size类型的数据,它包含图像的长和宽,而fx和fy为double类型,值反应图像的长或宽的比例。所以dsize和fx,fy必须不能同时为零,也就是说要么dsize不为零而fx与fy同时可以为0,要么dsize为0而fx与fy不同时为0;resize函数的目标大小可以是任意的大小,可以不保持长宽比率,删除的像素或者新增的像素值通过interpolation(内插)控制;

如果dsize不为零,fx,fy会自动计算:

fx=dsize.width/src.cols;
fy=dsize.height/src.rows;

所以我们可以这样写:

Mat  sizeimage;
Size dsize = Size(srcimage.cols*0.5,srcimage.rows*0.5);
resize(srcimage, sizeimage,dsize);

我们定义比例因子是0.5,手动计算出图像缩放后的尺寸,然后把这个尺寸放在Size中。当然在Size里面也是可以直接输入数值的,这样的话可以做固定尺寸的操作,而不发生裁剪。

Size dsize = Size(100,100);

也可以这样:

Mat  sizeimage;
resize(srcimage, sizeimage,Size(0,0),0.5,0.5);

此时我们直接把比例给入resize函数,效果是相同的。

这里写图片描述

这里写代码片

但是如果出现Size和fx,fy同时都不为0的情况呢?比如:

    Mat sizeimage;
    Size dsize = Size(srcimage.cols*0.2,srcimage.rows*0.2);
    resize(srcimage, sizeimage,dsize,0.5,0.5);

这里写图片描述

这里写图片描述

显然Size的优先级要大于fx和fy

2020-01-17 09:28:22 Trent1985 阅读数 5781
  • SIFT特征提取2

    了解Hough变换基本思想及直线检测步骤; 掌握Harris角点检测的基本思想及实现步骤; 了解尺度空间概念及SIFT特征提取的基本思想 了解ORB快速特征检测及BRIEF特征建立方法 掌握前述算子的OpenCV实现

    2606人学习 CSDN就业班
    免费试看

        本篇作为新年到来前的最后一篇,提前祝大家新年快乐!

        图像几何变换又叫做图像基本变换,主要包括图像平移、图像缩放和图像旋转几个部分,当然还有图像镜像等简单的内容。图像基本变换是图像处理的基本内容,是学习以后复杂的仿射变换、透视变换以及更高级的MLS网格变形等内容的基础,意义重大。本篇将从平移、缩放和旋转三个方面来讲解如何单纯使用C语言来轻松实现这三个算法。

图像平移变换

[定义与算法]

        图像平移变换可以表示为水平方向和垂直方向的位移,如果把图像坐标系的原点(0,0)点平移到(x0,y0),则图像内任意一点(x,y)平移后坐标(x’,y’)用公式表示如下:

 

        我们对测试图进行水平和垂直正方向平移100像素,效果图如图Fig.1所示。

        注意,黑色区域是我们默认填充的颜色,平移变换会出现图像跑到原图画布外面的情况,此时,原来的区域可以填充任意颜色,图像平移变换就这么简单。

[绘制与代码]

        我们用C语言来实现图像平移变换算法,定义f_Transform.h文件,在文件中定义如下接口:

/*********************************************************
*Function:图像平移变换
*Params:
*          srcData:32bgra图像数据
*          width:图像宽度
*          height:图像高度
*          stride:图像幅度,对于32bgra格式而言,stride=width*4
*          xoffset:水平方向平移量
*          yoffset:垂直方向平移量
*Return:  0-成功,其他失败
*********************************************************/
int f_XYOfffset(unsigned char* srcData, int width, int height, int stride, int xoffset, int yofffset);

        在这个接口中,定义了两个参数xoffset和yoffset分别用来表示水平和垂直的偏移向量,注意,如果xoffset和yoffset都为正数,则表示的是向水平和垂直的正方向偏移了xoffset和yoffset个像素距离,表现在结果图中,即图像向左偏移,反之,图像向右边偏移。

        完整接口代码如下:

/*********************************************************
*Function:图像平移变换
*Params:
*          srcData:32bgra图像数据
*          width:图像宽度
*          height:图像高度
*          stride:图像幅度,对于32bgra格式而言,stride=width*4
*          xoffset:水平方向平移向量
*          yoffset:垂直方向平移向量
*Return:  0-成功,其他失败
*********************************************************/
int f_XYOfffset(unsigned char* srcData, int width, int height, int stride, int xoffset, int yoffset)
{
	int ret = 0;
	unsigned char* tempData = (unsigned char*)malloc(sizeof(unsigned char) * height * stride);
	memcpy(tempData, srcData, sizeof(unsigned char) * height * stride);
	unsigned char* pSrc = srcData;
	for(int j = 0; j < height; j++)
	{
		for(int i = 0; i < width; i++)
		{
			int cx = i + xoffset;
			int cy = j + yoffset;
			if(cx >= 0 && cx < width && cy >= 0 && cy < height)
			{
				int pos = cx * 4 + cy * stride;
			    pSrc[0] = tempData[pos];
				pSrc[1] = tempData[pos + 1];
				pSrc[2] = tempData[pos + 2];
			}
			else
			{
				pSrc[0] = 0;
				pSrc[1] = 0;
				pSrc[2] = 0;
			}
			pSrc += 4;			
		}
	}
	free(tempData);
	return ret;
};

        接口到这里就写完了,仅仅30行左右,下面我们来写个测试代码:

#include "stdafx.h"
#include"imgRW\f_SF_ImgBase_RW.h"
#include"f_Transform.h"
int _tmain(int argc, _TCHAR* argv[])
{
	//定义输入图像路径
	char* inputImgPath = "Test.png";
	//定义输出图像路径
	char* outputImgPath = "res_offset.jpg";
	//定义图像宽高信息
	int width = 0, height = 0, component = 0, stride = 0;
	//图像读取(得到32位bgra格式图像数据)
	unsigned char* bgraData = Trent_ImgBase_ImageLoad(inputImgPath, &width, &height, &component);
	stride = width * 4;
	int ret = 0;
	//其他图像处理操作(这里以32位彩色图像灰度化为例)
	//////////////////////////IMAGE PROCESS/////////////////////////////
	//图像平移
	int xoffset = -100;//图像向右平移
	int yoffset = -100;//图像向左平移
     //调用图像平移变换接口
	ret = f_XYOfffset(bgraData, width, height, stride,xoffset, yoffset);
     //保存平移变换结果图
	ret = Trent_ImgBase_ImageSave(outputImgPath, width, height, bgraData, JPG);
	printf("f_XYOffset is finished!");
////////////////////////////////////////////////////////////////////
	free(bgraData);
}
	return 0;

        可以看到,在这段代码中,我们使用的都是C语言标准库,对于初学者而言,非常方便,通俗易懂。

图像缩放变换

[定义与算法]

        图像缩放即图像缩小与放大,是图像处理中最常用的操作,可以说图像处理离不开图像缩放,好的图像缩放算法可以高清还原图像信息,对于各种复杂的图像应用而言意义重大。

        假设图像中任意一点(x,y),按照水平方向缩放比例a和垂直方向缩放比例b进行缩放,则缩放后点坐标(x’,y’)的计算如下:

                                                                                            x{}'=ax

                                                                                            y{}'=by

        当a和b小于1时,表现为缩小,大于1时表现为图像放大,等于1时不缩放;既然有了放大与缩小,就存在图像信息的删除与填充,如何进行精确计算缩放后的坐标位置?这里引入一个必需要讲解的内容---图像插值算法。

        图像插值算法有很多,从最邻近插值,到二次插值、三次插值、卷积插值等等以及超分辨率算法等高级插值,可以说,图像插值是一门学问,单独成书的也很多很多。这里,我们讲解两种最常用也是最基础的插值算法:最邻近插值和双线性二次插值。

        最邻近插值不明思义就是用距离它最近的点来代替它。如下图Fig.2所示,有A和B两个像素点,A的值为100,B的值为20,要计算AB之间的C点像素值,其中,C点距离A的距离为0.4,距离B的距离为0.6,那么,C点距离A点像素最近,则C=100,这就是最邻近插值。

        最邻近插值计算量最小,但效果较差,往往会出现锯齿问题,即缩放后的图像边缘会出现锯齿毛刺,如图Fig.3所示,左边为原图,右边为最邻近插值放大两倍的结果,可以看到字母的边缘出现了锯齿状,非常不平滑。

 

        双线性二次插值也称为一阶插值,这个算法说复杂也复杂,说简单也简单,如果你要从数学上讲明白,那要从拉格朗日插值多项式说起。

        拉格朗日插值:

        对于给定的n+1个节点,x0,x1,..xn,如果能够找到n+1个多项式l1(x),l2(x),...ln(x),满足如下条件:,

        那么,拉格朗日插值多项式P(x)表示如下:

        其中,l(x)被称作插值基函数。

        上述内容可以参考张铁所著《数值分析》136页部分,上述内容与二次插值有什么关系呢?这里我们一道例题来说明,该例题也来自张铁《数值分析》。

        对于x0和x1,它的一阶拉格朗日插值多项式L(x)求取如上所示,其中l0(x)和l1(x)分别表示对应权重,如果要计算x0到x1之间的任意一点x,那么计算L1(x)即可,看到这里我们明白,如果给定两个点,计算两点之间的任意一点插值,那么,我们只要可以使用上述1阶拉格朗日插值多项式即可,而这个方法就是二次插值的基础。

        对于双线性二次插值,我们假设要计算的插值点(x,y)的值为f(x,y),在它的附近有有f(i,j),f(i+1,j),f(i,j+1)和f(i+1,j+1)四个点,如下图Fig.4所示,f(x,y)的计算方法如下:

        ①我们使用一阶拉格朗日插值计算点(i,j)和(i+1,j)之间的点(x,j)处的插值f(x,j);

        ②我们使用一阶拉格朗日插值计算点(i,j+1)和(i+1,j+1)之间的点(x,j+1)处的插值f(x,j+1);

        ③我们使用一阶拉格朗日插值计算(x,j)和(x,j+1)之间点(x,y)处的插值f(x,y);

        这个公式就是双线性插值公式。我们用这个公式,对比最邻近插值效果,如下图Fig.5所示。

[绘制与代码]

        有了上述算法的解析,下面我们通过C语言来实现,首先定义接口如下:

/*********************************************************
*Function:图像缩放变换
*Params:
*          srcData:32bgra图像数据
*          width:图像宽度
*          height:图像高度
*          stride:图像幅度,对于32bgra格式而言,stride=width*4
*          scaleX:水平方向缩放比例,[0,]
*          scaleY:垂直方向缩放比例,[0,]
*          outW:缩放结果图宽度
*          outH:缩放结果图高度
*          outStride:缩放结果图Stride
*          interpolation:插值方式,0-最邻近插值,1-双线性插值
*Return:  缩放图像bgra数据指针
*********************************************************/
unsigned char* f_Zoom(unsigned char* srcData, int width, int height, int stride, float scaleX, float scaleY, int* outW, int* outH, int * outStride, int interpolation);

        我们定义了f_Zoom的接口,这个接口中,由于缩放会改变图像大小,因此,我们返回一个缩放后的图像数据指针,同时,返回缩放后的图像宽高信息outW、outH和outStride;由于缩放包含水平和垂直方向的缩放因子,所以,添加水平缩放参数scaleX和垂直缩放参数scaleY,当scaleX小于1时表示水平缩小,等于1表示水平不缩放,大于1表示水平放大,垂直方向亦如此;最后,由于我们可以使用最邻近插值和双线性插值两种方式进行缩放,因此,添加了interpolation插值参数;

        有了接口,我们给出完整的接口实现代码如下:

/*********************************************************
*Function:图像缩放变换
*Params:
*          srcData:32bgra图像数据
*          width:图像宽度
*          height:图像高度
*          stride:图像幅度,对于32bgra格式而言,stride=width*4
*          scaleX:水平方向缩放比例,[0,]
*          scaleY:垂直方向缩放比例,[0,]
*          outW:缩放结果图宽度
*          outH:缩放结果图高度
*          outStride:缩放结果图Stride
*          interpolation:插值方式,0-最邻近插值,1-双线性插值
*Return:  缩放图像bgra数据指针
*********************************************************/
unsigned char* f_Zoom(unsigned char* srcData, int width, int height, int stride, float scaleX, float scaleY, int* outW, int* outH, int * outStride, int interpolation)
{
	int w = width * scaleX;
	int h = height * scaleY;
	int s = w * 4;
	unsigned char* tempData = (unsigned char*)malloc(sizeof(unsigned char) * s * h);
	memset(tempData, 255, sizeof(unsigned char) * s * h);
	unsigned char* pTemp = tempData;
	//最邻近插值
	if(interpolation == 0)
	{
	    for(int j = 0; j < h; j++)
	    {
	    	for(int i = 0; i < w; i++)
	    	{
	    		int cx = CLIP3(i * width / w, 0, width - 1);
	    		int cy = CLIP3(j * height / h, 0, height - 1);
	    		int pos = cx * 4 + cy * stride;
	    		pTemp[0] = srcData[pos];
	    		pTemp[1] = srcData[pos + 1];
	    		pTemp[2] = srcData[pos + 2];
	    		pTemp[3] = srcData[pos + 3];
	    		pTemp += 4;			
	    	}
	    }
	}
	else//双线性插值
	{
		for(int j = 0; j < h; j++)
	    {
	    	for(int i = 0; i < w; i++)
	    	{
	    		float cx = CLIP3((float)i * width / w, 1, width - 2);
	    		float cy = CLIP3((float)j * height / h, 1, height - 2);
				int tx = (int)cx;
				int ty = (int)cy;
				float p = abs(cx - tx);
				float q = abs(cy - ty);
	    		int pos = tx * 4 + ty * stride;
				int p1 = pos;
				int p2 = pos + stride;
				int p3 = pos + 4;
				int p4 = pos + 4 + stride;
				float a = (1.0f - p) * (1.0f - q);
				float b = (1.0f - p) * q;
				float c = p * (1.0f - q);
				float d = p * q;
	    		pTemp[0] = CLIP3((a * srcData[p1 + 0] + b * srcData[p2 + 0] + c * srcData[p3 + 0] + d * srcData[p4 + 0]), 0, 255);
	    		pTemp[1] = CLIP3((a * srcData[p1 + 1] + b * srcData[p2 + 1] + c * srcData[p3 + 1] + d * srcData[p4 + 1]), 0, 255);
	    		pTemp[2] = CLIP3((a * srcData[p1 + 2] + b * srcData[p2 + 2] + c * srcData[p3 + 2] + d * srcData[p4 + 2]), 0, 255);
	    		pTemp[3] = CLIP3((a * srcData[p1 + 3] + b * srcData[p2 + 3] + c * srcData[p3 + 3] + d * srcData[p4 + 3]), 0, 255);
	    		pTemp += 4;			
	    	}
	    }
	}
	*outW = w;
	*outH = h;
	*outStride = s;
	return tempData;
};

        代码不足60行,通俗易懂,最后给出接口的调用代码:

#include "stdafx.h"
#include"imgRW\f_SF_ImgBase_RW.h"
#include"f_Transform.h"
int _tmain(int argc, _TCHAR* argv[])
{
	//定义输入图像路径
	char* inputImgPath = "t150.png";
	//定义图像宽高信息
	int width = 0, height = 0, component = 0, stride = 0;
	//图像读取(得到32位bgra格式图像数据)
	unsigned char* bgraData = Trent_ImgBase_ImageLoad(inputImgPath, &width, &height, &component);
	stride = width * 4;
	int ret = 0;
	//其他图像处理操作(这里以32位彩色图像为例)
	//////////////////////////IMAGE PROCESS/////////////////////////////
	//图像缩放
	int outW, outH, outS;
	float scaleX = 5;
	float scaleY = 5;
	int interpolation = INTERPOLATE_BILINEAR;//INTERPOLATE_NEAREST;
	char* outZoomImgPath = "res_zoom.png";
	unsigned char* pResZoom = f_Zoom(bgraData, width, height, stride, scaleX, scaleY, &outW, &outH, &outS, interpolation);
	ret = Trent_ImgBase_ImageSave(outZoomImgPath, outW, outH, pResZoom, PNG);
	free(pResZoom);
	printf("f_zoom is finished!");
	////////////////////////////////////////////////////////////////////
	free(bgraData);
	return 0;
}

        效果测试如下图Fig.6所示:

图像旋转变换

[定义与算法]

        图像旋转即将图像按照某个原点顺时针或者逆时针旋转某个角度。

        如果平面上所有点(x,y)绕原点O旋转\Theta角度,旋转后的点为(x’,y’),则两者正向和逆向计算公式如下:

        公式的推导我们以正向变换为例,在极坐标系中,假设(x,y)到原点O距离为r,注意,这里O点表示上文平移后的图像中心点,(x,y)和原点的连线与x轴方向的夹角为b,旋转角度为a,旋转后坐标为(x’,y’),如下图Fig.7所示,则按照极坐标公式有:

        上述便是正向推导过程,即由(x,y)到旋转后的点(x’,y’)。

        在实际中,图像平面中的原点一般为左上角,也就是左上角为(0,0)点,垂直方向向下为正方向,与正常的笛卡尔坐标系不同。我们想要的往往是图像围绕图像中心点进行角度旋转,这个时候,我们需要把点(x,y)先转换为以图像中心为原点的坐标,也就是进行一定的坐标平移,然后再进行旋转变换。同时,为例避免孔洞现象,我们一般在计算的过程中,是按照逆向变换,根据目标图像素位置(x’,y’)计算它在原图中的位置(x,y),一次完成旋转变换的。

        假设旋转后图像的宽为W’,高为H’,点(x’,y’)映射到原图中的坐标为(x,y),则计算过程如下:

        ①按照平移逆变换将(x’,y’)进行平移:

        ②按照旋转逆变换公式将(x,y)进行变换:

        ③将坐标原点由图像中心平移至左上角:

        ④根据(x,y)位置选择插值算法插值得到最终旋转后的像素值;

        上面的过程就是完整的图像旋转变换,下面我们将动手实践一下。

[绘制与代码]

        我们首先定义一个图像旋转的接口,如下:

/*********************************************************
*Function:图像旋转变换
*Params:
*          srcData:32bgra图像数据
*          width:图像宽度
*          height:图像高度
*          stride:图像幅度,对于32bgra格式而言,stride=width*4
*          angle:图像旋转角度
*          outW:旋转结果图宽度
*          outH:旋转结果图高度
*          outStride:旋转结果图Stride
*          interpolation:插值方式,0-最邻近插值,1-双线性插值
*Return:  旋转图像bgra数据指针
*********************************************************/
unsigned char* f_Rotate(unsigned char* srcData, int width, int height, int stride, int angle, int* outW, int* outH, int* outStride, int interpolation);

        图像旋转变换中,图像的大小发生了变化,因此这里我们的接口返回一个变换后的图像数据指针,与缩放接口类似,添加新图像宽高输出参数outW,outH和outStride;由于旋转变换需要角度信息,因此这里添加了角度输入参数angle,范围为0到360度,同时,设置插值算法参数interpolation;

        按照前文的旋转变换算法公式,我们给出C代码如下:

/*********************************************************
*Function:图像旋转变换
*Params:
*          srcData:32bgra图像数据
*          width:图像宽度
*          height:图像高度
*          stride:图像幅度,对于32bgra格式而言,stride=width*4
*          angle:图像旋转角度
*          outW:旋转结果图宽度
*          outH:旋转结果图高度
*          outStride:旋转结果图Stride
*          interpolation:插值方式,0-最邻近插值,1-双线性插值
*Return:  旋转图像bgra数据指针
*********************************************************/
unsigned char* f_Rotate(unsigned char* srcData, int width, int height, int stride, int angle, int* outW, int* outH, int* outStride, int interpolation)
{
	float degree = angle * PI / 180.0f;
	float cx = 0, cy = 0, Cos = 0, Sin = 0;
	Cos = cos(degree);
	Sin = sin(degree);
	//计算新图像的宽高
    int w = width * Cos + height * Sin;
	int h = height * Cos + width * Sin;
	int s = w * 4;
	*outW = w;
	*outH = h;
	*outStride = s;
	//常量计算,用来优化速度
	cx = -w / 2.0f * Cos - h / 2.0f * Sin + width / 2.0f;
	cy = w / 2.0f * Sin - h / 2.0f * Cos + height / 2.0f;
	unsigned char* tempData = (unsigned char*)malloc(sizeof(unsigned char) * s * h);
	memset(tempData, 255, sizeof(unsigned char) * s * h);
	unsigned char* pTemp = tempData;
	//最邻近插值
	if(interpolation == 0)
	{
	    for(int j = 0; j < h; j++)
	    {
	    	for(int i = 0; i < w; i++)
	    	{
				//这里实际上就是按照公式计算,进行了优化,把一些常量计算放到了外面的cx和cy中
	    		int tx = i * Cos + j * Sin + cx;
	    		int ty = j * Cos - i * Sin + cy;
				if(tx >= 0 && tx < width && ty >= 0 && ty < height)
				{
	    		    int pos = tx * 4 + ty * stride;
	    		    pTemp[0] = srcData[pos];
	    		    pTemp[1] = srcData[pos + 1];
	    		    pTemp[2] = srcData[pos + 2];
	    		    pTemp[3] = srcData[pos + 3];
				}
				else
				{
					pTemp[0] = 0;
	    		    pTemp[1] = 0;
	    		    pTemp[2] = 0;
	    		    pTemp[3] = 255;
				}
	    		pTemp += 4;			
	    	}
	    }
	}
	else//双线性插值
	{
		for(int j = 0; j < h; j++)
	    {
	    	for(int i = 0; i < w; i++)
	    	{
				//这里实际上就是按照公式计算,进行了优化,把一些常量计算放到了外面的cx和cy中
                float mx = i * Cos + j * Sin + cx;
	    		float my = j * Cos - i * Sin + cy;
				if(mx >= 0 && mx < width && my >= 0 && my < height)
				{
				    int tx = (int)mx;
				    int ty = (int)my;
				    float p = abs(mx - tx);
				    float q = abs(my - ty);
	    		    int pos = tx * 4 + ty * stride;
				    int p1 = pos;
				    int p2 = pos + stride;
				    int p3 = pos + 4;
				    int p4 = pos + 4 + stride;
				    float a = (1.0f - p) * (1.0f - q);
				    float b = (1.0f - p) * q;
				    float c = p * (1.0f - q);
				    float d = p * q;
	    		    pTemp[0] = CLIP3((a * srcData[p1 + 0] + b * srcData[p2 + 0] + c * srcData[p3 + 0] + d * srcData[p4 + 0]), 0, 255);
	    		    pTemp[1] = CLIP3((a * srcData[p1 + 1] + b * srcData[p2 + 1] + c * srcData[p3 + 1] + d * srcData[p4 + 1]), 0, 255);
	    		    pTemp[2] = CLIP3((a * srcData[p1 + 2] + b * srcData[p2 + 2] + c * srcData[p3 + 2] + d * srcData[p4 + 2]), 0, 255);
	    		    pTemp[3] = CLIP3((a * srcData[p1 + 3] + b * srcData[p2 + 3] + c * srcData[p3 + 3] + d * srcData[p4 + 3]), 0, 255);
				}
				else
				{
					pTemp[0] = 0;
	    		    pTemp[1] = 0;
	    		    pTemp[2] = 0;
	    		    pTemp[3] = 255;
				}
				pTemp += 4;
	    	}
	    }
	}
	return tempData;
};

        本文的代码可以看到,基本都是将插值的interpolation条件判断放到了循环外面,导致代码段较长,实际上这样做是为了优化速度,增强代码可读性,大家可以体会一下。

        我们对上面接口进行调用测试如下:

#include "stdafx.h"
#include"imgRW\f_SF_ImgBase_RW.h"
#include"f_Transform.h"
int _tmain(int argc, _TCHAR* argv[])
{
	//定义输入图像路径
	char* inputImgPath = "Test.png";
	//定义图像宽高信息
	int width = 0, height = 0, component = 0, stride = 0;
	//图像读取(得到32位bgra格式图像数据)
	unsigned char* bgraData = Trent_ImgBase_ImageLoad(inputImgPath, &width, &height, &component);
	stride = width * 4;
	int ret = 0;
	//其他图像处理操作(这里以32位彩色图像为例)
	//////////////////////////IMAGE PROCESS/////////////////////////////
	//图像旋转
	int outW, outH, outS;
	int angle = 80;
	int interpolation = INTERPOLATE_NEAREST;//INTERPOLATE_BILINEAR;//INTERPOLATE_NEAREST;
	char* outRotateImgPath = "res_rotate_nearest.jpg";
	unsigned char* pResRotate = f_Rotate(bgraData, width, height, stride, angle, &outW, &outH, &outS, interpolation);
	ret = Trent_ImgBase_ImageSave(outRotateImgPath, outW, outH, pResRotate, JPG);
	free(pResRotate);
	printf("f_Rotate is finished!");
	////////////////////////////////////////////////////////////////////
	free(bgraData);
	return 0;
}

        大家可以看到,调用非常方便,甚至比opencv更加通俗易懂。最后我们给出对应的测试效果,如图Fig.8所示。

        本节完整的代码工程关注本人公众号“SF图像算法”有相关下载链接即可免费下载。

[知识扩展]

        在本文中,我们详细了解了图像平移、图像缩放和图像旋转三种图像几何变换,也是最常用的图像基本变换。在这个过程中我们是单个一一讲解的,为的是让新手同学们能够各个击破,单独理解。而实际中,这三个变换可以通过一个仿射变换矩阵来统一表达,放这边还矩阵如下:

        其中,s表示缩放比例,tx和ty表示平移量,theta表示角度;

        按照这个公式,我们可以一次计算出三种变换后对应的结果(x’,y’),然后再进行插值计算即可,大家可以自己尝试一下;

        对于图像变形和插值算法,多少年来,有无数相关的论文研究,近几年来,随着深度学习的飞速发展,基于卷积神经网络的超分辨率,图像复原以及图像变形的算法也是层出不穷,可见其意义之大!

        最后,学海无涯,共勉!

 

 

2019-04-26 21:26:00 zaishuiyifangxym 阅读数 2080
  • SIFT特征提取2

    了解Hough变换基本思想及直线检测步骤; 掌握Harris角点检测的基本思想及实现步骤; 了解尺度空间概念及SIFT特征提取的基本思想 了解ORB快速特征检测及BRIEF特征建立方法 掌握前述算子的OpenCV实现

    2606人学习 CSDN就业班
    免费试看

目录

1 图像缩放- resize()

2 图像旋转- getRotationMatrix2D(), warpAffine()

3 图像翻转- flip()

4 图像平移- warpAffine()

参考资料


图像几何变换有图像缩放、图像旋转、图像翻转和图像平移等。

 

1 图像缩放- resize()

图像缩放主要调用 resize() 函数实现,具体如下:

result = cv2.resize(src, dsize[, result[. fx[, fy[, interpolation]]]])

其中,参数

src 表示原始图像;

dsize 表示缩放大小;

fx和fy 也可以表示缩放大小倍数,他们两个(dsize或fx/fy)设置一个即可实现图像缩放。例如:

(1)result = cv2.resize(src, (160,160))

(2)result = cv2.resize(src, None, fx=0.5, fy=0.5)

图像缩放:设({x_0},{y_0})是缩放后的坐标,(x,y)是缩放前的坐标,{s_x} 和 {s_y} 为缩放因子,则公式如下:

                                                                       [{x_0}\text{ }{\text{ }}{y_0}\text{ }{\text{ }}1] = [x\text{ }{\text{ }}y\text{ }{\text{ }}1]{\text{ }}\left[ \begin{gathered} {s_x}\text{ }{\text{ }}0\text{ }\text{ }{\text{ 0}} \hfill \\ 0{\text{ }}\text{ }\text{ }{s_y}\text{ }{\text{ 0}} \hfill \\ {\text{0 }}\text{ }\text{ }0\text{ }\text{ }{\text{ 1}} \hfill \\ \end{gathered} \right]

 

(1) cv2.resize(src, (200,100)) 设置的dsize是列数为200,行数为100

result = cv2.resize(src, (200,100))

代码如下:

# encoding:utf-8
import cv2
import numpy as np

# 读取图片
src = cv2.imread("lena.tiff", cv2.IMREAD_UNCHANGED)

# 图像缩放
result = cv2.resize(src, (200,100))
print (result.shape)

# 显示图像
cv2.imshow("src", src)
cv2.imshow("result", result)

# 等待显示
cv2.waitKey(0)
cv2.destroyAllWindows()

 

运行结果如下图所示:

 

(2)可以获取 原始图像像素\times乘以缩放系数 进行图像变换;

result = cv2.resize(src, (int(cols * 0.6), int(rows * 1.2)))

 

代码如下所示:

# encoding:utf-8
import cv2
import numpy as np

# 读取图片
src = cv2.imread("lena.tiff", cv2.IMREAD_UNCHANGED)
rows, cols = src.shape[:2]
print
rows, cols

# 图像缩放 dsize(列,行)
result = cv2.resize(src, (int(cols * 0.6), int(rows * 1.2)))

# 显示图像
cv2.imshow("src", src)
cv2.imshow("result", result)

# 等待显示
cv2.waitKey(0)
cv2.destroyAllWindows()

 

运行结果如下图所示:

 

(3)(fx,fy) 缩放倍数的方法对图像进行放大或缩小。

result = cv2.resize(src, None, fx=0.3, fy=0.3)

 

代码如下所示:

# encoding:utf-8
import cv2
import numpy as np

# 读取图片
src =cv2.imread("lena.tiff", cv2.IMREAD_UNCHANGED)
rows, cols = src.shape[:2]
print
rows, cols

# 图像缩放
result = cv2.resize(src, None, fx=0.3, fy=0.3)

# 显示图像
cv2.imshow("src", src)
cv2.imshow("result", result)

# 等待显示
cv2.waitKey(0)
cv2.destroyAllWindows()

 

运行结果如下图所示:(按例比 0.3 \times 0.3 缩小)


 

2 图像旋转- getRotationMatrix2D(), warpAffine()

图像旋转:设 ({x_0},{y_0}) 是旋转后的坐标,({x},{y}) 是旋转前的坐标,({ m},{n}) 是旋转中心,a 是旋转的角度,({ left},{top}) 是旋转后图像的左上角坐标,则公式如下:

                                     [{x_0}\text{ }{\text{ }}{y_0}\text{ }{\text{ }}1] = [x\text{ }{\text{ }}y\text{ }{\text{ }}1]{\text{ }}\left[ \begin{gathered} \text{ }\text{ } 1\text{ }\text{ }\text{ }{\text{ }}0\text{ }\text{ }{\text{ 0}} \hfill \\ \text{ }\text{ }0{\text{ }} - 1\text{ }{\text{ 0}} \hfill \\ {\text{ - }}m\text{ }{\text{ }}n\text{ }{\text{ 1}} \hfill \\ \end{gathered} \right]{\text{ }}\left[ \begin{gathered} \cos a{\text{ }} - \sin a{\text{ }}0 \hfill \\ \sin a\text{ }\text{ }{\text{ }}\cos a\text{ }{\text{ }}0 \hfill \\ \text{ }\text{ }{\text{ }}0{\text{ }}\text{ }\text{ }\text{ }\text{ }\text{ }\text{ }0\text{ }\text{ }\text{ }{\text{ }}1 \hfill \\ \end{gathered} \right]{\text{ }}\left[ \begin{gathered} \text{ }\text{ }1\text{ }\text{ }\text{ }\text{ }{\text{ }}0\text{ }\text{ }{\text{ }}0 \hfill \\ \text{ }\text{ }0{\text{ }} \text{ }- 1\text{ }{\text{ }}0 \hfill \\ left\text{ }{\text{ }}top{\text{ }}\text{ }1 \hfill \\ \end{gathered} \right]

 

图像旋转主要调用getRotationMatrix2D() 函数和 warpAffine() 函数实现,绕图像的中心旋转,具体如下:

M = cv2.getRotationMatrix2D((cols/2, rows/2), 30, 1)

其中,参数分别为:旋转中心、旋转度数、scale

rotated = cv2.warpAffine(src, M, (cols, rows))

其中,参数分别为:原始图像、旋转参数 和 原始图像宽高

 

(1)旋转30度

代码如下:

# encoding:utf-8
import cv2
import numpy as np

# 读取图片
src =  cv2.imread("lena.tiff", cv2.IMREAD_UNCHANGED)

# 原图的高、宽 以及通道数
rows, cols, channel = src.shape

# 绕图像的中心旋转
# 参数:旋转中心 旋转度数 scale
M = cv2.getRotationMatrix2D((cols / 2, rows / 2), 30, 1)
# 参数:原始图像 旋转参数 元素图像宽高
rotated = cv2.warpAffine(src, M, (cols, rows))

# 显示图像
cv2.imshow("src", src)
cv2.imshow("rotated", rotated)

 

运行结果如下图所示:

 

(2)旋转90度

代码如下:

# encoding:utf-8
import cv2
import numpy as np

# 读取图片
src =  cv2.imread("lena.tiff", cv2.IMREAD_UNCHANGED)

# 原图的高、宽 以及通道数
rows, cols, channel = src.shape

# 绕图像的中心旋转
# 参数:旋转中心 旋转度数 scale
M = cv2.getRotationMatrix2D((cols / 2, rows / 2), 90, 1)
# 参数:原始图像 旋转参数 元素图像宽高
rotated = cv2.warpAffine(src, M, (cols, rows))

# 显示图像
cv2.imshow("src", src)
cv2.imshow("rotated", rotated)

 

运行结果如下图所示:

 

(3)旋转180度

代码如下:

# encoding:utf-8
import cv2
import numpy as np

# 读取图片
src =  cv2.imread("lena.tiff", cv2.IMREAD_UNCHANGED)

# 原图的高、宽 以及通道数
rows, cols, channel = src.shape

# 绕图像的中心旋转
# 参数:旋转中心 旋转度数 scale
M = cv2.getRotationMatrix2D((cols / 2, rows / 2), 180, 1)
# 参数:原始图像 旋转参数 元素图像宽高
rotated = cv2.warpAffine(src, M, (cols, rows))

# 显示图像
cv2.imshow("src", src)
cv2.imshow("rotated", rotated)

 

运行结果如下图所示:


 

3 图像翻转- flip()

图像翻转在OpenCV中调用函数 flip() 实现,函数用法如下:

dst = cv2.flip(src, flipCode)

其中,参数:

src 表示原始图像;

flipCode 表示翻转方向,如果flipCode为0,则以X轴为对称轴翻转,如果fliipCode>0则以Y轴为对称轴翻转,如果flipCode<0则在X轴、Y轴方向同时翻转。

 

代码如下:(注意一个窗口多张图像的用法

# encoding:utf-8
import cv2
import numpy as np
import matplotlib.pyplot as plt

# 读取图片
img = cv2.imread("lena.tiff", cv2.IMREAD_UNCHANGED)
src = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

# 图像翻转
# 0以X轴为对称轴翻转 >0以Y轴为对称轴翻转 <0X轴Y轴翻转
img1 = cv2.flip(src, 0)
img2 = cv2.flip(src, 1)
img3 = cv2.flip(src, -1)

# 显示图形 (注意一个窗口多张图像的用法)
titles = ['Source', 'Image1', 'Image2', 'Image3']
images = [src, img1, img2, img3]
for i in range(4):
    plt.subplot(2, 2, i + 1), plt.imshow(images[i], 'gray')
    plt.title(titles[i])
    plt.xticks([]), plt.yticks([])
plt.show()

# 等待显示
cv2.waitKey(0)
cv2.destroyAllWindows()

 

运行结果如下图所示:(注意一个窗口多张图像的用法


 

4 图像平移- warpAffine()

图像平移:设 ({x_0},{y_0}) 是缩放后的坐标,({x},{y}) 是缩放前的坐标,{d_x},{d_y} 为偏移量,则公式如下:

                                                             [{x_0}\text{ }{\text{ }}{y_0}\text{ }{\text{ }}1] = [x\text{ }{\text{ }}\text{ }y{\text{ }}\text{ }1]{\text{ }}\left[ \begin{gathered} \text{ }1{\text{ }}\text{ }\text{ }0\text{ }\text{ }{\text{ }}0 \hfill \\ \text{ }0\text{ }\text{ }{\text{ }}1\text{ }\text{ }{\text{ }}0 \hfill \\ {d_x}{\text{ }}\text{ }{d_y}{\text{ }}\text{ }1 \hfill \\ \end{gathered} \right]

 

图像平移首先定义平移矩阵M,再调用 warpAffine() 函数实现平移,函数用法如下:

M = np.float32([[1, 0, x], [0, 1, y]])

shifted = cv2.warpAffine(image, M, (image.shape[1], image.shape[0]))

 

代码如下:

# encoding:utf-8
import cv2
import numpy as np
import matplotlib.pyplot as plt

# 读取图片
img = cv2.imread("lena.tiff", cv2.IMREAD_UNCHANGED)
image = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

# 图像平移 下、上、右、左平移
M = np.float32([[1, 0, 0], [0, 1, 100]])
img1 = cv2.warpAffine(image, M, (image.shape[1], image.shape[0]))

M = np.float32([[1, 0, 0], [0, 1, -100]])
img2 = cv2.warpAffine(image, M, (image.shape[1], image.shape[0]))

M = np.float32([[1, 0, 100], [0, 1, 0]])
img3 = cv2.warpAffine(image, M, (image.shape[1], image.shape[0]))

M = np.float32([[1, 0, -100], [0, 1, 0]])
img4 = cv2.warpAffine(image, M, (image.shape[1], image.shape[0]))

# 显示图形
titles = ['Image1', 'Image2', 'Image3', 'Image4']
images = [img1, img2, img3, img4]
for i in range(4):
    plt.subplot(2, 2, i + 1), plt.imshow(images[i], 'gray')
    plt.title(titles[i])
    plt.xticks([]), plt.yticks([])
plt.show()

 

运行结果如下图所示:


 

参考资料

[1] https://blog.csdn.net/Eastmount/article/details/82454335

[2] Python+OpenCV图像处理