导言
图像放大是日常学习中经常要用到的两个算法,我们首先讨论缩放的流程以及放大时如何优化双线性插值算法。
采用国际标准测试图像Lena,为了方便,我们将读入的彩色图转为灰度图进行缩放。
图像放大
和图像缩小不同,图像放大是小数据量到大数据量的处理过程,因此需要对许多未知的数据进行估计。
如果一幅 W × H W\times H W×H图像要放大 k 1 × k 2 k_1 \times k_2 k1×k2(即行放大 k 1 k_1 k1倍,列放大 k 1 k_1 k1倍),则放大后的图像大小为 i n t ( W ∗ k 1 ) × i n t ( H ∗ k 1 ) int(W*k_1)\times int(H*k_1) int(W∗k1)×int(H∗k1),这里取int的原因是乘出来的的数值可能为小数。
具体过程如下图所示:
我们有一个图像大小为4*4,每一个像素值都是1。
它的坐标矩阵如下所示
现在我们将原图(下面称为f)的行和列都放大1.5倍( k 1 = 1.5 k_1=1.5 k1=1.5, k 2 = 1.5 k_2=1.5 k2=1.5)。则放大后的图像(下面称为g)的 widht=6= 4 × 1.5 4\times1.5 4×1.5,height=6= 4 × 1.5 4\times1.5 4×1.5。接着我们要计算g中的坐标在f中映射。如下图所示:
我们计算出g中的(0,1)点应该对应f中的(0,0.67)点,但是f中无此坐标,所有我们使用线性插值法来计算(0,0.67)处的像素值。
线性插值法如果你没有了解过,建议先去了解一下。
线性插值法的通用公式如下:
f ( x 2 ) − f ( x 1 ) x 2 − x 1 = f ( x ) − f ( x 1 ) x − x 1 \frac{f(x_2)-f(x_1)}{x_2-x_1}=\frac{f(x)-f(x_1)}{x-x_1} x2−x1f(x2)−f(x1)=x−x1f(x)−f(x1)
f ( x ) = x 2 − x x 2 − x 1 f ( x 1 ) + x − x 1 x 2 − x 1 f ( x 2 ) f(x)=\frac{x_2-x}{x_2-x_1}f(x_1)+\frac{x-x_1}{x_2-x_1}f(x_2) f(x)=x2−x1x2−xf(x1)+x2−x1x−x1f(x2)
上面的第二个公式就是权重公式。
所以 f ( 0 , 0.67 ) = 1 − 0.67 1 − 0 f ( 0 , 0 ) + 0.67 − 0 1 − 0 f ( 0 , 1 ) = 1 f(0,0.67)=\frac{1-0.67}{1-0}f(0,0)+\frac{0.67-0}{1-0}f(0,1)=1 f(0,0.67)=1−01−0.67f(0,0)+1−00.67−0f(0,1)=1
我们再举一个列子:
注意到此时计算出的x为1.3,在1和2之间;y为2.67,在2和3之间。所以我们要通过双线性插值来计算(1.3,2.67)坐标处的像素值。双线性插值算法如下图。
具体就是先对 Q 11 Q_{11} Q11、 Q 21 Q_{21} Q21进行线性插值,计算出 R 1 R_1 R1,然后对 Q 12 Q_{12} Q12、 Q 22 Q_{22} Q22进行线性插值,计算出 R 2 R_2 R2,最后对 R 2 R_2 R2、 R 1 R_1 R1进行插值,计算出我们想要的坐标 P P P的值。
f ( 1.3 , 2 ) = ( 2 − 1.3 ) f ( 2 , 2 ) + ( 1.3 − 1 ) f ( 1 , 2 ) = 1 f(1.3,2)=(2-1.3)f(2,2)+(1.3-1)f(1,2)=1 f(1.3,2)=(2−1.3)f(2,2)+(1.3−1)f(1,2)=1
f ( 1.3 , 3 ) = ( 2 − 1.3 ) f ( 2 , 3 ) + ( 1.3 − 1 ) f ( 1 , 3 ) = 1 f(1.3,3)=(2-1.3)f(2,3)+(1.3-1)f(1,3)=1 f(1.3,3)=(2−1.3)f(2,3)+(1.3−1)f(1,3)=1
我们对(1.3,2)和(1.3,3)进行线性插值。
f ( 1.3 , 2.67 ) = ( 3 − 2.67 ) f ( 1.3 , 2 ) + ( 2.67 − 2 ) f ( 1.3 , 3 ) = 1 f(1.3,2.67)=(3-2.67)f(1.3,2)+(2.67-2)f(1.3,3)=1 f(1.3,2.67)=(3−2.67)f(1.3,2)+(2.67−2)f(1.3,3)=1
所以根据双线性插值计算出来的(1.3,2.37)处的像素值为1。
总而言之,如果计算出的点在f中是边缘点,则使用单线性插值,如果不是边缘点,则使用双线性插值。
C++代码如下:
int main()
{
cv::Mat image = cv::imread("LenaRGB.bmp");
//Using gray image for easy calculations
cv::Mat grayImage(image.size(), CV_8UC1);
cv::cvtColor(image, grayImage, CV_BGR2GRAY);
int width = grayImage.cols;
int height = grayImage.rows;
// Scale factor
double k1 = 1.4;
double k2 = 1.7;
//Create zero mat to save the outputs
cv::Mat outImage = cv::Mat::zeros(round(height*k1), round(width*k2), CV_64FC1);
for (int row = 0; row < outImage.rows; row++)
{
for (int col = 0; col < outImage.cols; col++)
{
//得到放大图坐标在原图中对应的坐标
double srcX = (1 / 1.4)*row;
double srcY = (1 / 1.7)*col;
if (srcX > (grayImage.rows - 1))
{
srcX = grayImage.rows - 1;
}
if (srcY > (grayImage.cols - 1))
{
srcY = grayImage.cols - 1;
}
// Get x0,x1,y0,y1
int x0 = floor(srcX);
int x1 = ceil(srcX);
int y0 = floor(srcY);
int y1 = ceil(srcY);
if ((x0 == x1) && (y0 == y1))
{
outImage.at<double>(row, col) = (double)grayImage.at<uchar>(x0, y0);
continue;
}
// 如果是边缘点,则使用单线性插值
if (x0 == x1)
{
double temp = (y1 - srcY)*grayImage.at<uchar>(x0, y0)+
(srcY-y0)*grayImage.at<uchar>(x0,y1);
outImage.at<double>(row, col) = temp;
continue;
}
if (y0 == y1)
{
double temp = (x1 - srcX)*grayImage.at<uchar>(x0, y0) +
(srcX - x0)*grayImage.at<uchar>(x1, y1);
outImage.at<double>(row, col) = temp;
continue;
}
// 不是边缘点则使用双线性插值
double temp1 = (y1 - srcY)*grayImage.at<uchar>(x0, y0) +
(srcY - y0)*grayImage.at<uchar>(x0, y1);
double temp2 = (y1 - srcY)*grayImage.at<uchar>(x1, y0) +
(srcY - y0)*grayImage.at<uchar>(x1, y1);
double temp = (x1 - srcX)*temp1 + (srcX - x0)*temp2;
outImage.at<double>(row, col) = temp;
}
}
// Convert CV_64FC1 to CV_8UC1
outImage.convertTo(outImage, CV_8UC1);
return 0;
}