DataWhale 机器视觉组队学习task1
1.1 简介
中,灰度值仅在整数位置上有定义。然而,输出图象[x,y]的灰度值一般由处在非整数坐标上的值来决定。这就需要插值算法来进行处理,常见的插值算法有最近邻插值、双线性插值和三次样条插值。
1.2 算法理论介绍与推荐
1.2.1 最近邻插值算法原理
,作为插值后的输出。
.
一个例子:
表示目标图像,表示原图像,我们有如下公式:
另外缩小也是相同的公式,由高等数学知识知:单调函数必有反函数。因为该函数为单调不减函数,所以推理可知最近邻法放大n倍再缩小为原来1/n还是原图,下面用代码验证一下:
import cv2
import numpy as np
arr = np.random.randint(255, size=(500, 500), dtype=np.uint8)
amplifier = cv2.resize(arr, dsize=None, fx=2, fy=2, interpolation = cv2.INTER_NEAREST)
minimise = cv2.resize(amplifier, dsize=None, fx=0.5, fy=0.5, interpolation = cv2.INTER_NEAREST)
print((minimise == arr).sum())
输出为250000,刚好为500*500,所以可以证明我们的推理
缺点:
用该方法作放大处理时,在图象中可能出现明显的块状效应
1.2.2 双线性插值
在讲双线性插值之前先看以一下线性插值,线性插值多项式为:
双线性插值
如图,已知,但是要插值的点为点,这就要用双线性插值了,首先在轴方向上,对和两个点进行插值,这个很简单,然后根据和对点进行插值,这就是所谓的双线性插值。在数学上,双线性插值是有两个变量的插值函数的线性插值扩展,其核心思想是在两个方向分别进行一次线性插值。
设
首先在方向进行线性插值,得到
然后在方向进行线性插值,得到:
这就是双线性插值最后的结果,由于图像双线性插值只会用相邻的4个点,因此上述公式的分母都是1,图像双线性插值原理如下:
为两个变量的函数,其在单位正方形顶点的值已知。假设我们希望通过插值得到正方形内任意点的函数值。则可由双线性方程:
来定义的一个双曲抛物面与四个已知点拟合。
首先对上端的两个顶点进行线性插值得:
类似地,再对底端的两个顶点进行线性插值有:
最后,做垂直方向的线性插值,以确定:
整理得:
用一开始的例子算一下:
???可以看到,算到的时候不知道怎么带入公式了,并且我们用代码验证一下:
arr = np.array([[56, 23, 15], [65, 32, 78], [12, 45, 62]], dtype=np.uint8)
amplifier = cv2.resize(arr, dsize=None, fx=4/3, fy=4/3, interpolation = cv2.INTER_LINEAR)
print(amplifier)
输出:
[[56 35 20 15]
[62 41 38 54]
[45 40 50 72]
[12 33 51 62]]
可以看到,和我们手算的结果并不一样,这是为什么呢??
看了这篇博客后才知道里面的原理
其实就是将源图像和目标图像几何中心的对齐 ,把一开始的公式:
改为:
另外,遇到边界时
参照这个评论处理,接下来我们重新手算一遍:
可以看到和Opencv输出结果一样啦,hhhhhhh
另外,线性插值其实就是拉格朗日插值有两个取值点的情况
1.2.3 三次样条插值
为什么会有三次样条插值呢?
之前数值分析课做过实验:
当n=5和n=20时,拉格朗日插值逼近(蓝色为原函数,橙色为拉格朗日插值):
可以明显看出,随着n增大,拉格朗日插值会出现振荡现象,反观三弯角法(三次样条的一种):
,但是缺点就是算的太慢
三次样条插值原理
给定个点,,以及他们的函数值,在每个区间上,确定一个三次多项式:
每个三次多项式中有四个未知参数,有个区间,个多项式,共个未知参数。我们知道“个未知数需要个已知条件确定唯一解”,所以要确定这个未知参数,共需要个已知条件。
每个三次多项式满足如下条件:
- 二阶可导,且,,在区间内连续
- ,,(共个条件)
- ,(共个条件)
- ,(共个条件)
- ,(共个条件)
个条件,还差2个条件,由如下三种边界条件确定:
- 给定端点处一阶导数值:,称为固定边界条件
- 给定端点处二阶导数值:,特别地,,称为自然边界条件
- 周期性条件:,,
个条件有了,就可以确定每个区间上的三次多项式。
得到插值结果。三次样条插值具有良好的收敛性,稳定性和光滑性,优点明显,是非常重要的插值工具。
这里主要了解三次样条插值的作用,具体的推导过程比较繁琐,想了解的可以查阅资料。
流程图如下:
三次样条图像插值原理https://www.sohu.com/a/323023965_468740:
该方法利用三次多项式S(x)求逼近理论上最佳插值函数sin(x)/x, 其数学表达式为:
待求像素(x, y)的灰度值由其周围16个灰度值加权内插得到,如下图:
待求像素的灰度计算式如下:
其中:
1.2.4 映射方法
向前映射法
可以将几何运算想象成一次一个象素地转移到输出图象中。如果一个输入象素被映射到四个输出象素之间的位置,则其灰度值就按插值算法在4个输出象素之间进行分配。称为向前映射法,或象素移交影射。
注:从原图象坐标计算出目标图象坐标镜像、平移变换使用这种计算方法
由公式(1)我们已知原图像到目标图像的坐标变换,因此我们可以知道原图像的一点在变换后在目标图像的位置,我们称为向前映射。
向后映射法
向后映射法(或象素填充算法)是输出象素一次一个地映射回到输入象素中,以便确定其灰度级。如果一个输出象素被映射到4个输入象素之间,则其灰度值插值决定,向后空间变换是向前变换的逆。
注:从结果图象的坐标计算原图象的坐标
公式(2)中我们知道目标图像的一点(x’,y’)在变换前在原图像上的位置,我们称为向后映射。
总结
- 旋转、拉伸、放缩可以使用
- 解决了漏点的问题,出现了马赛克
向后映射比较直观,计算量也小,我们经常使用的图像变换都是采用向后映射的方法来处理。但向后映射需要知道变换的反函数。但在有些变换比较复杂的场合,这个反变换是很难得到的。此时就需要采用前向映射的方法进行变换了。
1.3 基于OpenCV的实现
1.3.1 C++ 伸缩操作
函数原型:
void cv::resize(InputArray src, OutputArray dst, Size dsize, double fx=0, double fy=0, int interpolation=INTER_LINEAR )
src:输入图像
dst:输出图像
dsize:输出图像尺寸
fx、fy:x,y方向上的缩放因子
INTER_LINEAR:插值方法,总共五种
1. INTER_NEAREST - 最近邻插值法
2. INTER_LINEAR - 双线性插值法(默认)
3. INTER_AREA - 基于局部像素的重采样(resampling using pixel area relation)。对于图像抽取(image decimation)来说,这可能是一个更好的方法。但如果是放大图像时,它和最近邻法的效果类似。
4. INTER_CUBIC - 基于4x4像素邻域的3次插值法
5. INTER_LANCZOS4 - 基于8x8像素邻域的Lanczos插值
代码实践:
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main(int argc, char* argv[])
{
Mat img = imread("D:/image/yuner.jpg");
if (img.empty())
{
cout << "无法读取图像" << endl;
return 0;
}
int height = img.rows;
int width = img.cols;
// 缩小图像,比例为(0.2, 0.2)
Size dsize = Size(round(0.2 * width), round(0.2 * height));
Mat shrink;
//使用双线性插值
resize(img, shrink, dsize, 0, 0, INTER_LINEAR);
// 在缩小图像的基础上,放大图像,比例为(1.5, 1.5)
float fx = 1.5;
float fy = 1.5;
Mat enlarge1, enlarge2;
resize(shrink, enlarge1, Size(), fx, fy, INTER_NEAREST);
resize(shrink, enlarge2, Size(), fx, fy, INTER_LINEAR);
// 显示
imshow("src", img);
imshow("shrink", shrink);
imshow("INTER_NEAREST", enlarge1);
imshow("INTER_LINEAR", enlarge2);
waitKey(0);
return 0;
}
原图
0.2倍缩小,双线性插值
1.5倍放大,最近邻插值
1.5倍放大,双线性插值
仔细观察可以看出,最邻近法有块状效应,而双线性插值法效果较好
1.3.2 Python
函数原型:
cv2.resize(src, dsize[, dst[, fx[, fy[, interpolation]]]])
参数:
参数 | 描述 |
src | 【必需】原图像 |
dsize | 【必需】输出图像所需大小 |
fx | 【可选】沿水平轴的比例因子 |
fy | 【可选】沿垂直轴的比例因子 |
interpolation | 【可选】插值方式 |
插值方式:
cv.INTER_NEAREST | 最近邻插值 |
cv.INTER_LINEAR | 双线性插值 |
cv.INTER_CUBIC | 基于4x4像素邻域的3次插值法 |
cv.INTER_AREA | 基于局部像素的重采样 |
通常,缩小使用cv.INTER_AREA,放缩使用cv.INTER_CUBIC(较慢)和cv.INTER_LINEAR(较快效果也不错)。默认情况下,所有的放缩都使用cv.INTER_LINEAR。
代码实践:
下面介绍一些python opencv的基本函数的参数
1.imread(filename, flags=None)
flags | 解释 |
IMREAD_UNCHANGED | 指定用图片的原来格式打开,即以不改变图片的方式打开,图片是彩色就是彩色,图片是灰度图像就是灰度图像 |
IMREAD_GRAYSCALE | 指定用灰度图像的方式打开图片,即将原始图像转化为灰度图像再打开 |
IMREAD_COLOR | 指定用彩色图像打开图片 |
import cv2
if __name__ == "__main__":
img = cv2.imread('D:/image/yuner.jpg', cv2.IMREAD_UNCHANGED)
print('Original Dimensions : ',img.shape)
scale_percent = 30 # percent of original size
width = int(img.shape[1] * scale_percent / 100)
height = int(img.shape[0] * scale_percent / 100)
dim = (width, height)
# resize image
resized = cv2.resize(img, dim, interpolation = cv2.INTER_LINEAR)
fx = 1.5
fy = 1.5
resized1 = cv2.resize(resized, dsize=None, fx=fx, fy=fy, interpolation = cv2.INTER_NEAREST)
resized2 = cv2.resize(resized, dsize=None, fx=fx, fy=fy, interpolation = cv2.INTER_LINEAR)
print('Resized Dimensions : ',resized.shape)
cv2.imshow("Resized image", resized)
cv2.imshow("INTER_NEAREST image", resized1)
cv2.imshow("INTER_LINEAR image", resized2)
cv2.waitKey(0)
cv2.destroyAllWindows()
0.3倍缩小,双线性插值
1.5倍放大,最近邻插值
1.5倍放大,双线性插值