https://en.wikipedia.org/wiki/Interpolation
https://en.wikipedia.org/wiki/Image_scaling
https://en.wikipedia.org/wiki/Comparison_gallery_of_image_scaling_algorithms
https://en.wikipedia.org/wiki/Whittaker%E2%80%93Shannon_interpolation_formula
https://en.wikipedia.org/wiki/Pixel-art_scaling_algorithms
常见的缩放插值方法有:最邻近插值、双线性内插值、三次卷积法
传统缩放插值是方形矩阵的,所以马赛克都是方形的
不同于传统插值,使用圆形覆盖在方格的面积权重来优化插值,如下图:
设正方形边长为2,则圆形的半径R为1. 计算面积:
把圆切出四分之一: S(A)+S(B)+(R/3)^2=(π*R^2)/4
绿色区域面积:S(绿)=(R/3)*sqrt((R^2-(R/3)^2))
绿色区域所在的圆弧区域面积:S(弧)=(2*(arcsin(1/3))/(2*π))*π*R^2
B所在区域白色区域面积:S(白) = (R*2/3)*(R-sqrt((R^2-(R/3)^2)))-((S(弧)-S(绿))
S(B)=(R*2/3)^2-S(白)
S(A)=(π*R^2)/4-S(B)-(R/3)^2
设R为1,则:
SA = 0.24240268452708302834111528260373
SB = 0.43188436775925417016343445210503
SC = 0.44444444444444444444444444444444
SC/SB = 1.0290820358939030947876874261659
SC/SA = 1.8334963794296915471537108683276
SB/SA = 1.781681455392466335545886919352
矩阵算子表示,大约SB占178份,而SA占100份,基本上就是在假设自然界都是点光源扩展模型的情况下的不丢失信息的无损缩放了:
|100 178 100|
|178 0 178|
|100 178 100|
缩小图像:已知A/B,插值填充像素C区域时,应该考虑四个对称B区域的权重 像素值S(PC)=((B1+B2+B3+B4)*178+(A1+A2+A3+A4)*100)/(4*(100+178))=((5+6+7+7)*178+(5+6+7+8)*100)/(4*(100+178))
5 5 6
6 PC 7
7 7 8
放大图像:已知C1=10,C2=8,插值填充像素A/B区域时,如下: 则可以确认红色问号区域应该填的值,特别是中间加粗红色问号区域的权值S(PC)=((5+6+7+8)*100+(S(PC1)+S(PC2)+S(PC3)+S(PC4))*178)/(4*(100+178)):
? ? ? ? ?
? 5 P2 6 ?
? P1 PC P3 ?
? 7 P4 8 ?
? ? ? ? ?
可以看出,S(PC)依赖于上下左右四个待计算的区域P1/P2/P3/P4像素值。剩下的就是要考虑怎么从缩放图像的边缘向中心区域递推计算S(PC),最终得到高质量的整幅放大图像。
直观上理解,它是为了得到图像整体平滑,没有马赛克,所以才需要从边界向中间像水一样漫延的全图计算——不是以前的只考虑已知邻域的简单双向线性插值。
// resize.cpp : This file contains the 'main' function. Program execution begins and ends there.
//
#include "pch.h"
#include <iostream>
#include "opencv2/imgproc.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/core/hal/interface.h"
#include <iostream>
using namespace cv;
using namespace std;
template <typename T>
string basic2str(const T &int_temp)
{
string string_temp;
stringstream stream;
stream << int_temp;
string_temp = stream.str(); //此处也可以用 stream>>string_temp
return string_temp;
}
int resizeCompare(Mat& image, double div, String fileName)
{
Mat imageNew(image.rows / div, image.cols / div, CV_8UC1, Scalar(1));
Mat imageResize(image.rows / div, image.cols / div, CV_8UC1, Scalar(1));
Mat imageResizeLine(image.rows / div, image.cols / div, CV_8UC1, Scalar(1));
Mat imageResizeLanczos4(image.rows / div, image.cols / div, CV_8UC1, Scalar(1));
resize(image, imageResizeLine, Size(image.cols / div, image.rows / div));
resize(image, imageResizeLanczos4, Size(image.cols / div, image.rows / div), 0, 0, INTER_LANCZOS4);
cv::imwrite(fileName.substr(0, fileName.length() - 4) + "Line.png", imageResizeLine);
cv::imwrite(fileName.substr(0, fileName.length() - 4) + "Lanczos4.png", imageResizeLanczos4);
// 调整分区的权重
double rightRatioValueArray[] = { 1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0 };
for (int k = 0; k < 11; k++)
{
for (int i = 0; i < imageNew.rows; i++)
{
for (int j = 0; j < imageNew.cols; j++)
{
int center_x = i * div + div / 2;
int center_y = j * div + div / 2;
if (center_x > 0 && center_y > 0 && center_x < image.rows - 1 && center_y < image.cols - 1)
{
int a[10] = { 0 };
a[1] = image.at<uchar>(center_x - 1, center_y - 1);
a[2] = image.at<uchar>(center_x - 1, center_y);
a[3] = image.at<uchar>(center_x - 1, center_y + 1);
a[4] = image.at<uchar>(center_x, center_y - 1);
a[5] = image.at<uchar>(center_x, center_y);
a[6] = image.at<uchar>(center_x, center_y + 1);
a[7] = image.at<uchar>(center_x + 1, center_y - 1);
a[8] = image.at<uchar>(center_x + 1, center_y);
a[9] = image.at<uchar>(center_x + 1, center_y + 1);
// 强行保留边缘部分
int b1 = 4, b2 = 4;
for (int p = 1; p < 10; p++)
{
if (abs(a[5] - a[p]) > 10)
{
a[p] = 0;
if(0 == p%2)
{
b2--;
}
else { b1--; }
}
}
// 使用圆形插值,保证平滑特性
int a0 = ((a[1] + a[3] + a[7] + a[9]) * 100.0 / rightRatioValueArray[k] + (a[2] + a[4] + a[6] + a[8]) * 178.0 / rightRatioValueArray[k] + a[5] * 183) * 1.0
/ ((b1 * 100 + b2 * 178)/ rightRatioValueArray[k] + 183) + 0.5;
imageNew.at<uchar>(i, j) = a0;
}
else
{
imageNew.at<uchar>(i, j) = image.at<uchar>(i * div + div / 2, j * div + div / 2);
}
}
}
string strRightRatio = basic2str(k);
string strDiv = basic2str(div);
cv::imwrite(fileName.substr(0, fileName.length() - 4) + "Circle" + "-" + strDiv + "-" +strRightRatio + ".png", imageNew);
}
return 0;
}
int main()
{
bool useCanny = false;
bool useRefine = false;
bool overlay = false;
String filename[2] = {
"D:\\opencv\\resize\\Debug\\Text.png" ,
"D:\\opencv\\resize\\Debug\\Lena.png" };
for (int k = 0; k < 2; k++)
{
for (int i = 0; i < 2; i++)
{
Mat image = imread(filename[i], IMREAD_GRAYSCALE);
if (k % 2 == 0)
{
resizeCompare(image, 2, filename[i]);
}
else
{
resizeCompare(image, 0.5, filename[i]);
}
}
}
waitKey();
return 0;
}