sensor、codec、display device都是基于pixel的,高分辨率圖像能呈現更多的detail,由于sensor制造和chip的限制,我們需要用到圖像插值(scaler/resize)技術,這種方法代價小,使用方便。同時,該技術還可以放大用戶希望看到的感興趣區域。圖像縮放算法往往基于插值實現,常見的圖像插值算法包括最近鄰插值(Nearest-neighbor)、雙線性插值(Bilinear)、雙立方插值(bicubic)、lanczos插值、方向插值(Edge-directed interpolation)、example-based插值、深度學習等算法。
插值縮放的原理是基于目標分辨率中的點,將其按照縮放關系對應到源圖像中,尋找源圖像中的點(不一定是整像素點),然后通過源圖像中的相關點插值得到目標點。本篇文章,我們介紹Nearest-neighbor和Bilinear插值的原理及C實現。
插值算法原理如下:
1. Nearest-neighbor
最近鄰插值,是指將目標圖像中的點,對應到源圖像中后,找到最相鄰的整數點,作為插值后的輸出。如下圖所示,P為目標圖像對應到源圖像中的點,Q11、Q12、Q21、Q22是P點周圍4個整數點,Q12與P離的最近,因此P點的值等于Q12
的值。這里寫圖片描述由于圖像中像素具有鄰域相關性,因此,用這種拷貝的方法會產生明顯的鋸齒。
2. Bilinear
雙線性插值使用周圍4個點插值得到輸出,雙線性插值,是指在xy方法上,都是基于線性距離來插值的。
如圖1,目標圖像中的一點對應到源圖像中點P(x,y),我們先在x方向插值:
然后,進行y方向插值:
可以驗證,先進行y方向插值再進行x方向插值,結果也是一樣的。值得一提的是,雙線性插值在單個方向上是線性的,但對整幅圖像來說是非線性的。
3. C實現
使用VS2010,工程包含三個文件,如下:
main.cpp
#include <string.h>
#include <IOStream>
#include "resize.h"
int main()
{
const char *input_file = "D:\simuTest\teststream\00_YUV_data\01_DIT_title\data.yuv"; //absolute path
const char *output_file = "D:\simuTest\teststream\00_YUV_data\01_DIT_title\data_out2.yuv"; //absolute path
int src_width = 720;
int src_height = 480;
int dst_width = 1920;
int dst_height = 1080;
int resize_type = 1; //0:nearest, 1:bilinear
resize(input_file, src_width, src_height, output_file, dst_width, dst_height, resize_type);
return 0;
}
resize.cpp
#include "resize.h"
int clip3(int data, int min, int max)
{
return (data > max) ? max : ((data < min) ? min : data);
if(data > max)
return max;
else if(data > min)
return data;
else
return min;
}
//bilinear takes 4 pixels (2×2) into account
/*
* 函數名: bilinearHorScaler
* 說明: 水平方向雙線性插值
* 參數:
*/
void bilinearHorScaler(int *src_image, int *dst_image, int src_width, int src_height, int dst_width, int dst_height)
{
double resizeX = (double)dst_width / src_width;
for(int ver = 0; ver < dst_height; ++ver){
for(int hor = 0; hor < dst_width; ++hor){
double srcCoorX = hor / resizeX;
double weight1 = srcCoorX - (double)((int)srcCoorX);
double weight2 = (double)((int)(srcCoorX + 1)) - srcCoorX;
double dstValue = *(src_image + src_width * ver + clip3((int)srcCoorX, 0, src_width - 1)) * weight2 + *(src_image + src_width * ver + clip3((int)(srcCoorX + 1), 0, src_width - 1)) * weight1;
*(dst_image + dst_width * ver + hor) = clip3((uint8)dstValue, 0, 255);
}
}
}
/*
* 函數名: bilinearVerScaler
* 說明: 垂直方向雙線性插值
* 參數:
*/
void bilinearVerScaler(int *src_image, int *dst_image, int src_width, int src_height, int dst_width, int dst_height)
{
double resizeY = (double)dst_height / src_height;
for(int ver = 0; ver < dst_height; ++ver){
for(int hor = 0; hor < dst_width; ++hor){
double srcCoorY = ver / resizeY;
double weight1 = srcCoorY - (double)((int)srcCoorY);
double weight2 = (double)((int)(srcCoorY + 1)) - srcCoorY;
double dstValue = *(src_image + src_width * clip3((int)srcCoorY, 0, src_height - 1) + hor) * weight2 + *(src_image + src_width * clip3((int)(srcCoorY + 1), 0, src_height - 1) + hor) * weight1;
*(dst_image + dst_width * ver + hor) = clip3((uint8)dstValue, 0, 255);
}
}
}
/*
* 函數名: yuv420p_NearestScaler
* 說明: 最近鄰插值
* 參數:
*/
void nearestScaler(int *src_image, int *dst_image, int src_width, int src_height, int dst_width, int dst_height)
{
double resizeX = (double)dst_width /src_width; //水平縮放系數
double resizeY = (double)dst_height / src_height; //垂直縮放系數
int srcX = 0;
int srcY = 0;
for(int ver = 0; ver < dst_height; ++ver) {
for(int hor = 0; hor < dst_width; ++hor) {
srcX = clip3(int(hor/resizeX + 0.5), 0, src_width - 1);
srcY = clip3(int(ver/resizeY + 0.5), 0, src_height - 1);
*(dst_image + dst_width * ver + hor) = *(src_image + src_width * srcY + srcX);
}
}
}
void resize(const char *input_file, int src_width, int src_height, const char *output_file, int dst_width, int dst_height, int resize_type)
{
//define and init src buffer
int *src_y = new int[src_width * src_height];
int *src_cb = new int[src_width * src_height / 4];
int *src_cr = new int[src_width * src_height / 4];
memset(src_y, 0, sizeof(int) * src_width * src_height);
memset(src_cb, 0, sizeof(int) * src_width * src_height / 4);
memset(src_cr, 0, sizeof(int) * src_width * src_height / 4);
//define and init dst buffer
int *dst_y = new int[dst_width * dst_height];
int *dst_cb = new int[dst_width * dst_height / 4];
int *dst_cr = new int[dst_width * dst_height / 4];
memset(dst_y, 0, sizeof(int) * dst_width * dst_height);
memset(dst_cb, 0, sizeof(int) * dst_width * dst_height / 4);
memset(dst_cr, 0, sizeof(int) * dst_width * dst_height / 4);
//define and init mid buffer
int *mid_y = new int[dst_width * src_height];
int *mid_cb = new int[dst_width * src_height / 4];
int *mid_cr = new int[dst_width * src_height / 4];
memset(mid_y, 0, sizeof(int) * dst_width * src_height);
memset(mid_cb, 0, sizeof(int) * dst_width * src_height / 4);
memset(mid_cr, 0, sizeof(int) * dst_width * src_height / 4);
uint8 *data_in_8bit = new uint8[src_width * src_height * 3 / 2];
memset(data_in_8bit, 0, sizeof(uint8) * src_width * src_height * 3 / 2);
uint8 *data_out_8bit = new uint8[dst_width * dst_height * 3 / 2];
memset(data_out_8bit, 0, sizeof(uint8) * dst_width * dst_height * 3 / 2);
FILE *fp_in = fopen(input_file,"rb");
if(NULL == fp_in)
{
//exit(0);
printf("open file failure");
}
FILE *fp_out = fopen(output_file, "wb+");
//data read
fread(data_in_8bit, sizeof(uint8), src_width * src_height * 3 / 2, fp_in);
//Y component
for(int ver = 0; ver < src_height; ver++)
{
for(int hor =0; hor < src_width; hor++)
{
src_y[ver * src_width + hor] = data_in_8bit[ver * src_width + hor];
}
}
//c component YUV420P
for(int ver = 0; ver < src_height / 2; ver++)
{
for(int hor =0; hor < src_width / 2; hor++)
{
src_cb[ver * (src_width / 2) + hor] = data_in_8bit[src_height * src_width + ver * src_width / 2 + hor];
src_cr[ver * (src_width / 2) + hor] = data_in_8bit[src_height * src_width + src_height * src_width / 4 + ver * src_width / 2 + hor];
}
}
//resize
if(0 == resize_type)
{
nearestScaler(src_y, dst_y, src_width, src_height, dst_width, dst_height);
nearestScaler(src_cb, dst_cb, src_width / 2, src_height / 2, dst_width / 2, dst_height / 2);
nearestScaler(src_cr, dst_cr, src_width / 2, src_height / 2, dst_width / 2, dst_height / 2);
}
else if(1 == resize_type)
{
bilinearHorScaler(src_y, mid_y, src_width, src_height, dst_width, src_height);
bilinearHorScaler(src_cb, mid_cb, src_width / 2, src_height / 2, dst_width / 2, src_height / 2);
bilinearHorScaler(src_cr, mid_cr, src_width / 2, src_height / 2, dst_width / 2, src_height / 2);
bilinearVerScaler(mid_y, dst_y, dst_width, src_height, dst_width, dst_height);
bilinearVerScaler(mid_cb, dst_cb, dst_width / 2, src_height / 2, dst_width / 2, dst_height / 2);
bilinearVerScaler(mid_cr, dst_cr, dst_width / 2, src_height / 2, dst_width / 2, dst_height / 2);
}
else
{
nearestScaler(src_y, dst_y, src_width, src_height, dst_width, dst_height);
nearestScaler(src_cb, dst_cb, src_width / 2, src_height / 2, dst_width / 2, dst_height / 2);
nearestScaler(src_cr, dst_cr, src_width / 2, src_height / 2, dst_width / 2, dst_height / 2);
}
//data write
for(int ver = 0; ver < dst_height; ver++)
{
for(int hor =0; hor < dst_width; hor++)
{
data_out_8bit[ver * dst_width + hor] = clip3(dst_y[ver * dst_width + hor], 0, 255);
}
}
for(int ver = 0; ver < dst_height / 2; ver++)
{
for(int hor = 0; hor < dst_width / 2; hor++)
{
data_out_8bit[dst_height * dst_width + ver * dst_width / 2 + hor] = clip3(dst_cb[ver * (dst_width / 2) + hor], 0, 255);
data_out_8bit[dst_height * dst_width + dst_height * dst_width / 4 + ver * dst_width / 2 + hor] = clip3(dst_cr[ver * (dst_width / 2) + hor], 0, 255);
}
}
fwrite(data_out_8bit, sizeof(uint8), dst_width * dst_height * 3 / 2, fp_out);
delete [] src_y;
delete [] src_cb;
delete [] src_cr;
delete [] dst_y;
delete [] dst_cb;
delete [] dst_cr;
delete [] mid_y;
delete [] mid_cb;
delete [] mid_cr;
delete [] data_in_8bit;
delete [] data_out_8bit;
fclose(fp_in);
fclose(fp_out);
}
resize.h
#ifndef RESIZE_H
#define RESIZE_H
#include <stdio.h>
#include <string.h>
typedef unsigned char uint8;
typedef unsigned short uint16;
int clip3(int data, int min, int max);
void bilinearHorScaler(int *src_image, int *dst_image, int src_width, int src_height, int dst_width, int dst_height);
void bilinearVerScaler(int *src_image, int *dst_image, int src_width, int src_height, int dst_width, int dst_height);
void nearestScaler(int *src_image, int *dst_image, int src_width, int src_height, int dst_width, int dst_height);
void resize(const char *input_file, int src_width, int src_height, const char *output_file, int dst_width, int dst_height, int resize_type);
#endif
效果比較
將720x480分辨率圖像放大到1080p,1:1截取局部畫面如下,左邊是最近鄰放大的效果,右邊是雙線性效果,可以看到,雙線性放大的鋸齒要明顯比最近鄰小。
Matlab
常用的matlab縮放方法有兩種,如下
- B = imresize(A, scale, method) B = imresize(A, 0.5, ‘bicubic’)使用雙立方插值將寬高各縮小1/2
- B = imresize(A, outputSize, method) B = imresize(A, [1080,1920], ‘bilinear’)使用雙線性插值縮放到1920x1080分辨率