最近邻插值和双线性插值的基本原理以及OpenCV中resize函数的⽤法改变图像
的⼤⼩
最近邻插值和双线性插值的基本原理
图像的缩放很好理解,就是图像的放⼤和缩⼩。传统的绘画⼯具中,有⼀种叫做“放⼤尺”的绘画⼯具,画家常⽤它来放⼤图画。当然,在计算机上,我们不再需要⽤放⼤尺去放⼤或缩⼩图像了,把这个⼯作交给程序来完成就可以了。下⾯就来讲讲计算机怎么来放⼤缩⼩图象;在本⽂中,我们所说的图像都是指点阵图,也就是⽤⼀个像素矩阵来描述图像的⽅法,对于另⼀种图像:⽤函数来描述图像的⽮量图,不在本⽂讨论之列。
越是简单的模型越适合⽤来举例⼦,我们就举个简单的图像:3X3 的256级灰度图,也就是⾼为3个象素,宽也是3个象素的图像,每个象素的取值可以是 0-255,代表该像素的亮度,255代表最亮,也就是⽩⾊,0代表最暗,即⿊⾊。假如图像的象素矩阵如下图所⽰(这个原始图把它叫做源图,Source):
234 38 22
67 44 12
89 65 63
这个矩阵中,元素坐标(x,y)是这样确定的,x从左到右,从0开始,y从上到下,也是从零开始,这是图象处理中最常⽤的坐标系,就是这样⼀个坐标:
---------------------->X
|
|
|
|
|
∨Y
如果想把这副图放⼤为 4X4⼤⼩的图像,那么该怎么做呢?那么第⼀步肯定想到的是先把4X4的矩阵先画出来再说,好了矩阵画出来了,如下所⽰,当然,矩阵的每个像素都是未知数,等待着我们去填
充(这个将要被填充的图的叫做⽬标图,Destination):
然后要往这个空的矩阵⾥⾯填值了,要填的值从哪⾥来来呢?是从源图中来,好,先填写⽬标图最左上⾓的象素,坐标为(0,0),那么该坐标对应源图中的坐标可以由如下公式得出:
srcX=dstX* (srcWidth/dstWidth) , srcY = dstY * (srcHeight/dstHeight)
好了,套⽤公式,就可以到对应的原图的坐标了(0*(3/4),0*(3/4))=>(00.75,00.75)=>(0,0)
,到了源图的对应坐标,就可以把源图中坐标为(0,0)处的234象素值填进去⽬标图的(0,0)这个位置了。
接下来,如法炮制,寻⽬标图中坐标为(1,0)的象素对应源图中的坐标,套⽤公式:
(10.75,00.75)=>(0.75,0)
结果发现,得到的坐标⾥⾯竟然有⼩数,这可怎么办?计算机⾥的图像可是数字图像,象素就是最⼩单位了,象素的坐标都是整数,从来没有⼩数坐标。这时候采⽤的⼀种策略就是采⽤四舍五⼊的⽅法(也可以采⽤直接舍掉⼩数位的⽅法),把⾮整数坐标转换成整数,好,那么按照四舍五⼊的⽅法就得到坐标(1,0),完整的运算过程就是这样的:
(10.75,00.75)=>(0.75,0)=>(1,0)
那么就可以再填⼀个象素到⽬标矩阵中了,同样是把源图中坐标为(1,0)处的像素值38填⼊⽬标图中的坐标。
依次填完每个象素,⼀幅放⼤后的图像就诞⽣了,像素矩阵如下所⽰:
234 38 22 22
67 44 12 12
89 65 63 63
89 65 63 63
这种放⼤图像的⽅法叫做最临近插值算法,这是⼀种最基本、最简单的图像缩放算法,效果也是最不好的,放⼤后的图像有很严重的马赛克,缩⼩后的图像有很严重的失真;效果不好的根源就是其简单的最临近插值⽅法引⼊了严重的图像失真,⽐如,当由⽬标图的坐标反推得到的源图的的坐标是⼀个浮点数的时候,采⽤了四舍五⼊的⽅法,直接采⽤了和这个浮点数最接近的象素的值,这种⽅法是很不科学的,当推得坐标值为 0.75的时候,不应该就简单的取为1,既然是0.75,⽐1要⼩0.25 ,⽐0要
⼤0.75 ,那么⽬标象素值其实应该根据这个源图中虚拟的点四周的四个真实的点来按照⼀定的规律计算出来的,这样才能达到更好的缩放效果。双线型内插值算法就是⼀种⽐较好的图像缩放算法,它充分的利⽤了源图中虚拟点四周的四个真实存在的像素值来共同决定⽬标图中的⼀个像素值,因此缩放效果⽐简单的最邻近插值要好很多。
双线性内插值算法描述如下:
对于⼀个⽬的像素,设置坐标通过反向变换得到的浮点坐标为(i+u,j+v) (其中i、j均为浮点坐标的整数部分,u、v为浮点坐标的⼩数部分,是取值[0,1)区间的浮点数),则这个像素得值 f(i+u,j+v) 可由原图像中坐标为 (i,j)、(i+1,j)、(i,j+1)、(i+1,j+1)所对应的周围四个像素的值决定,即:
f(i+u,j+v) = (1-u)(1-v)f(i,j) + (1-u)vf(i,j+1) + u(1-v)f(i+1,j) + uvf(i+1,j+1) 公式1
其中f(i,j)表⽰源图像(i,j)处的的像素值,以此类推。
switch函数用法举例⽐如,象刚才的例⼦,现在假如⽬标图的象素坐标为(1,1),那么反推得到的对应于源图的坐标是(0.75 , 0.75), 这其实只是⼀个概念上的虚拟象素,实际在源图中并不存在这样⼀个象素,那么⽬标图的象素(1,1)的取值不能够由这个虚拟象素来决定,⽽只能由源图的这四个象素共同决定:(0,0)(0,1)(1,0)(1,1),⽽由于(0.75,0.75)离(1,1)要更近⼀些,那么(1,1)所起的决
定作⽤更⼤⼀些,这从公式1中的系数uv=0.75×0.75就可以体现出来,⽽(0.75,0.75)离(0,0)最远,所以(0,0)所起的决定作⽤就要⼩⼀些,公式中系数为(1-u)(1-v)=0.25×0.25也体现出了这⼀特点。
OpenCV提供了resize函数来改变图像的⼤⼩,函数原型如下:
void resize(InputArray src, OutputArray dst, Size dsize, double fx=0, double fy=0, int interpolation=INTER_LINEAR );
先解释⼀下各个参数的意思:
src:输⼊,原图像,即待改变⼤⼩的图像;
dst:输出,改变⼤⼩之后的图像,这个图像和原图像具有相同的内容,只是⼤⼩和原图像不⼀样⽽已;
dsize:输出图像的⼤⼩。如果这个参数不为0,那么就代表将原图像缩放到这个Size(width,height)指定的⼤⼩;如果这个参数为0,那么原图像缩放之后的⼤⼩就要通过下⾯的公式来计算:
dsize = Size(round(ls), round(ws))
其中,fx和fy就是下⾯要说的两个参数,是图像width⽅向和height⽅向的缩放⽐例。
fx:width⽅向的缩放⽐例,如果它是0,那么它就会按照(double)dsize.ls来计算;
fy:height⽅向的缩放⽐例,如果它是0,那么它就会按照(double)dsize.ws来计算;
interpolation:这个是指定插值的⽅式,图像缩放之后,肯定像素要进⾏重新计算的,就靠这个参数来指定重新计算像素的⽅式,有以下⼏种:
INTER_NEAREST - 最邻近插值
INTER_LINEAR - 双线性插值,如果最后⼀个参数你不指定,默认使⽤这种⽅法
INTER_AREA - resampling using pixel area relation. It may be a preferred method for image decimation, as it gives moire’-free results. But when the image is zoomed, it is similar to the INTER_NEAREST method.
INTER_CUBIC - 4x4像素邻域内的双⽴⽅插值
INTER_LANCZOS4 - 8x8像素邻域内的Lanczos插值
使⽤注意事项:
1. dsize和fx/fy不能同时为0,要么你就指定好dsize的值,让fx和fy空置直接使⽤默认值,就像
resize(img, imgDst, Size(30,30));
要么你就让dsize为0,指定好fx和fy的值,⽐如fx=fy=0.5,那么就相当于把原图两个⽅向缩⼩⼀倍!
2. ⾄于最后的插值⽅法,正常情况下使⽤默认的双线性插值就够⽤了。
⼏种常⽤⽅法的效率是:最邻近插值>双线性插值>双⽴⽅插值>Lanczos插值;
但是效率和效果成反⽐,所以根据⾃⼰的情况酌情使⽤。
3. 正常情况下,在使⽤之前dst图像的⼤⼩和类型都是不知道的,类型从src图像继承⽽来,⼤⼩也是从原图像根据参数计算出来。但是如果你事先已经指定好dst图像的⼤⼩,那么你可以通过下⾯这种⽅式来调⽤函数:
resize(src, dst, dst.size(), 0, 0, interpolation);
代码如下:::::
/*
#include <opencv2\opencv.hpp>
#include<cv.h>
#include<highgui.h>
using namespace cv;
int g_nCurrRowsSize = 0, g_nCurrColsSize = 0;
int main()
{
Mat src =imread("C:\\1.bmp");
Mat dst;
double scale=0.5; //缩放倍数
g_nCurrRowsSize = ws* scale;
g_nCurrColsSize = ls* scale;
resize(src, dst,Size(g_nCurrColsSize, g_nCurrRowsSize), 0, 0, 3); //缩放源图像到⽬标图像
namedWindow( "src",WINDOW_AUTOSIZE ); //创建⽤于显⽰源图像的窗⼝
namedWindow( "dst",WINDOW_AUTOSIZE ); //创建⽤于显⽰⽬标图像的窗⼝
imshow( "src", src ); //显⽰源图像
imshow( "dst", dst ); //显⽰⽬标图像
waitKey(0);
return 0;
}
*/
#include<iostream>
#include<opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int g_nCurrRowsSize = 0, g_nCurrColsSize = 0;
int main()
{
Mat srcImage = imread("C:\\1.bmp");
imshow("【原图】", srcImage);
imshow("【原图】", srcImage);
/
*Mat dstImage;
resize(srcImage, dstImage, Size(50, 50), 0, 0, 3);
imshow("【处理后的图⽚】", dstImage);
waitKey(0);*/
Mat midImage, dstImage;
g_nCurrRowsSize = ws;
g_nCurrColsSize = ls;
namedWindow("【滚动条】");
createTrackbar("rows", "【滚动条】", &g_nCurrRowsSize, ws * 2, 0);
createTrackbar("cols", "【滚动条】", &g_nCurrColsSize, ls * 2, 0);
char key;
while (1)
{
resize(srcImage, dstImage, Size(g_nCurrColsSize, g_nCurrRowsSize), 0, 0, INTER_LINEAR); /*
INTER_NEAREST - 最邻近插值
INTER_LINEAR - 双线性插值,如果最后⼀个参数你不指定,默认使⽤这种⽅法
INTER_AREA 使⽤象素关系重采样。当图像缩⼩时候,该⽅法可以避免波纹出现。
当图像放⼤时,类似于 CV_INTER_NN ⽅法
INTER_CUBIC - 4x4像素邻域内的双⽴⽅插值
INTER_LANCZOS4 - 8x8像素邻域内的Lanczos插值
*/
imshow("【变换尺⼨】", dstImage);
key = waitKey(10);
switch (key)
{
case 27:
return 0;
break;
//如果检测到键盘值为 'a' 则恢复原图
case 'a':
g_nCurrColsSize = ls;
g_nCurrRowsSize = ws;
break;
default:
break;
}
}
return 0;
}
测试结果:
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论