DataWhale 机器视觉组队学习task1

1.1 简介

opencv 散点插值 opencv图像插值_opencv 散点插值中,灰度值仅在整数位置上有定义。然而,输出图象[x,y]的灰度值一般由处在非整数坐标上的opencv 散点插值 opencv图像插值_opencv_02值来决定。这就需要插值算法来进行处理,常见的插值算法有最近邻插值、双线性插值和三次样条插值。

1.2 算法理论介绍与推荐

1.2.1 最近邻插值算法原理

opencv 散点插值 opencv图像插值_插值_03,作为插值后的输出。




opencv 散点插值 opencv图像插值_opencv_04


opencv 散点插值 opencv图像插值_计算机视觉_05.

一个例子:

opencv 散点插值 opencv图像插值_计算机视觉_06表示目标图像,opencv 散点插值 opencv图像插值_插值_07表示原图像,我们有如下公式:

opencv 散点插值 opencv图像插值_插值_08

opencv 散点插值 opencv图像插值_opencv 散点插值_09



opencv 散点插值 opencv图像插值_插值_10


opencv 散点插值 opencv图像插值_计算机视觉_11

另外缩小也是相同的公式,由高等数学知识知:单调函数必有反函数。因为该函数为单调不减函数,所以推理可知最近邻法放大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,所以可以证明我们的推理

缺点:
用该方法作放大处理时,在图象中可能出现明显的块状效应



opencv 散点插值 opencv图像插值_灰度值_12


1.2.2 双线性插值

  在讲双线性插值之前先看以一下线性插值,线性插值多项式为:

opencv 散点插值 opencv图像插值_插值_13



opencv 散点插值 opencv图像插值_opencv_14


opencv 散点插值 opencv图像插值_灰度值_15

双线性插值

如图,已知opencv 散点插值 opencv图像插值_灰度值_16,但是要插值的点为opencv 散点插值 opencv图像插值_插值_17点,这就要用双线性插值了,首先在opencv 散点插值 opencv图像插值_灰度值_18轴方向上,对opencv 散点插值 opencv图像插值_计算机视觉_19opencv 散点插值 opencv图像插值_计算机视觉_20两个点进行插值,这个很简单,然后根据opencv 散点插值 opencv图像插值_计算机视觉_19opencv 散点插值 opencv图像插值_计算机视觉_20opencv 散点插值 opencv图像插值_插值_17点进行插值,这就是所谓的双线性插值。在数学上,双线性插值是有两个变量的插值函数的线性插值扩展,其核心思想是在两个方向分别进行一次线性插值。

opencv 散点插值 opencv图像插值_计算机视觉_24


opencv 散点插值 opencv图像插值_计算机视觉_25

首先在opencv 散点插值 opencv图像插值_灰度值_18方向进行线性插值,得到

opencv 散点插值 opencv图像插值_插值_27
opencv 散点插值 opencv图像插值_opencv_28

然后在opencv 散点插值 opencv图像插值_opencv_29方向进行线性插值,得到:

opencv 散点插值 opencv图像插值_opencv 散点插值_30

opencv 散点插值 opencv图像插值_opencv_31

opencv 散点插值 opencv图像插值_opencv 散点插值_32

这就是双线性插值最后的结果,由于图像双线性插值只会用相邻的4个点,因此上述公式的分母都是1,图像双线性插值原理如下:



opencv 散点插值 opencv图像插值_opencv 散点插值_33


opencv 散点插值 opencv图像插值_灰度值_34为两个变量的函数,其在单位正方形顶点的值已知。假设我们希望通过插值得到正方形内任意点的函数值。则可由双线性方程:
opencv 散点插值 opencv图像插值_计算机视觉_35

  来定义的一个双曲抛物面与四个已知点拟合。

  首先对上端的两个顶点进行线性插值得:

opencv 散点插值 opencv图像插值_opencv 散点插值_36

  类似地,再对底端的两个顶点进行线性插值有:
opencv 散点插值 opencv图像插值_opencv_37

  最后,做垂直方向的线性插值,以确定:

opencv 散点插值 opencv图像插值_插值_38

  整理得:

opencv 散点插值 opencv图像插值_计算机视觉_39

用一开始的例子算一下:

opencv 散点插值 opencv图像插值_opencv 散点插值_40

???可以看到,算到opencv 散点插值 opencv图像插值_插值_41的时候不知道怎么带入公式了,并且我们用代码验证一下:

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 散点插值 opencv图像插值_计算机视觉_42

改为:

opencv 散点插值 opencv图像插值_计算机视觉_43

另外,遇到边界时

opencv 散点插值 opencv图像插值_opencv 散点插值_44

参照这个评论处理,接下来我们重新手算一遍:

opencv 散点插值 opencv图像插值_opencv_45

可以看到和Opencv输出结果一样啦,hhhhhhh

opencv 散点插值 opencv图像插值_opencv_46

另外,线性插值其实就是拉格朗日插值有两个取值点的情况

1.2.3 三次样条插值

为什么会有三次样条插值呢?
之前数值分析课做过实验:

当n=5和n=20时,拉格朗日插值逼近(蓝色为原函数,橙色为拉格朗日插值):

opencv 散点插值 opencv图像插值_插值_47

可以明显看出,随着n增大,拉格朗日插值会出现振荡现象,反观三弯角法(三次样条的一种):

opencv 散点插值 opencv图像插值_opencv_48

opencv 散点插值 opencv图像插值_opencv_49,但是缺点就是算的太慢

三次样条插值原理
  给定opencv 散点插值 opencv图像插值_灰度值_50个点,opencv 散点插值 opencv图像插值_灰度值_51,以及他们的函数值opencv 散点插值 opencv图像插值_插值_52,在每个区间opencv 散点插值 opencv图像插值_插值_53上,确定一个三次多项式:
opencv 散点插值 opencv图像插值_插值_54
  每个三次多项式中有四个未知参数,有opencv 散点插值 opencv图像插值_opencv_55个区间,opencv 散点插值 opencv图像插值_opencv_55个多项式,共opencv 散点插值 opencv图像插值_灰度值_57个未知参数。我们知道“opencv 散点插值 opencv图像插值_opencv_55个未知数需要opencv 散点插值 opencv图像插值_opencv_55个已知条件确定唯一解”,所以要确定这opencv 散点插值 opencv图像插值_灰度值_57个未知参数,共需要opencv 散点插值 opencv图像插值_灰度值_57个已知条件。

  每个三次多项式满足如下条件:

  • opencv 散点插值 opencv图像插值_opencv 散点插值_62二阶可导,且opencv 散点插值 opencv图像插值_opencv 散点插值_62opencv 散点插值 opencv图像插值_opencv 散点插值_64opencv 散点插值 opencv图像插值_opencv 散点插值_65在区间opencv 散点插值 opencv图像插值_灰度值_66内连续
  • opencv 散点插值 opencv图像插值_灰度值_67opencv 散点插值 opencv图像插值_插值_68opencv 散点插值 opencv图像插值_计算机视觉_69(共opencv 散点插值 opencv图像插值_灰度值_70个条件)
  • opencv 散点插值 opencv图像插值_插值_71opencv 散点插值 opencv图像插值_插值_72(共opencv 散点插值 opencv图像插值_插值_73个条件)
  • opencv 散点插值 opencv图像插值_opencv 散点插值_74opencv 散点插值 opencv图像插值_插值_72(共opencv 散点插值 opencv图像插值_插值_73个条件)
  • opencv 散点插值 opencv图像插值_插值_77opencv 散点插值 opencv图像插值_插值_72(共opencv 散点插值 opencv图像插值_插值_73个条件)

opencv 散点插值 opencv图像插值_计算机视觉_80个条件,还差2个条件,由如下三种边界条件确定:

  • 给定端点处一阶导数值:opencv 散点插值 opencv图像插值_插值_81,称为固定边界条件
  • 给定端点处二阶导数值:opencv 散点插值 opencv图像插值_插值_82,特别地,opencv 散点插值 opencv图像插值_插值_83,称为自然边界条件
  • 周期性条件:opencv 散点插值 opencv图像插值_插值_84opencv 散点插值 opencv图像插值_灰度值_85
          opencv 散点插值 opencv图像插值_opencv_86

opencv 散点插值 opencv图像插值_灰度值_57个条件有了,就可以确定每个区间上的三次多项式。

opencv 散点插值 opencv图像插值_插值_88得到插值结果。三次样条插值具有良好的收敛性,稳定性和光滑性,优点明显,是非常重要的插值工具。

  这里主要了解三次样条插值的作用,具体的推导过程比较繁琐,想了解的可以查阅资料。

流程图如下:

opencv 散点插值 opencv图像插值_opencv_89

三次样条图像插值原理https://www.sohu.com/a/323023965_468740:

该方法利用三次多项式S(x)求逼近理论上最佳插值函数sin(x)/x, 其数学表达式为:

opencv 散点插值 opencv图像插值_opencv 散点插值_90

待求像素(x, y)的灰度值由其周围16个灰度值加权内插得到,如下图:

opencv 散点插值 opencv图像插值_灰度值_91


待求像素的灰度计算式如下:

opencv 散点插值 opencv图像插值_插值_92

其中:

opencv 散点插值 opencv图像插值_opencv 散点插值_93

1.2.4 映射方法

opencv 散点插值 opencv图像插值_计算机视觉_94

opencv 散点插值 opencv图像插值_opencv_95

opencv 散点插值 opencv图像插值_opencv 散点插值_96

opencv 散点插值 opencv图像插值_计算机视觉_97

opencv 散点插值 opencv图像插值_opencv_98

向前映射法

  可以将几何运算想象成一次一个象素地转移到输出图象中。如果一个输入象素被映射到四个输出象素之间的位置,则其灰度值就按插值算法在4个输出象素之间进行分配。称为向前映射法,或象素移交影射。

注:从原图象坐标计算出目标图象坐标镜像、平移变换使用这种计算方法

由公式(1)我们已知原图像到目标图像的坐标变换opencv 散点插值 opencv图像插值_灰度值_99,因此我们可以知道原图像的一点在变换后在目标图像的位置,我们称为向前映射。

向后映射法

  向后映射法(或象素填充算法)是输出象素一次一个地映射回到输入象素中,以便确定其灰度级。如果一个输出象素被映射到4个输入象素之间,则其灰度值插值决定,向后空间变换是向前变换的逆。

注:从结果图象的坐标计算原图象的坐标

公式(2)中我们知道目标图像的一点(x’,y’)在变换前在原图像上的位置opencv 散点插值 opencv图像插值_计算机视觉_100,我们称为向后映射。

总结

  • 旋转、拉伸、放缩可以使用
  • 解决了漏点的问题,出现了马赛克

向后映射比较直观,计算量也小,我们经常使用的图像变换都是采用向后映射的方法来处理。但向后映射需要知道变换的反函数。但在有些变换比较复杂的场合,这个反变换是很难得到的。此时就需要采用前向映射的方法进行变换了。

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;
}

原图

opencv 散点插值 opencv图像插值_插值_101

0.2倍缩小,双线性插值

opencv 散点插值 opencv图像插值_灰度值_102

1.5倍放大,最近邻插值

opencv 散点插值 opencv图像插值_opencv 散点插值_103

1.5倍放大,双线性插值

opencv 散点插值 opencv图像插值_opencv 散点插值_104


仔细观察可以看出,最邻近法有块状效应,而双线性插值法效果较好

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倍缩小,双线性插值

opencv 散点插值 opencv图像插值_计算机视觉_105

1.5倍放大,最近邻插值

opencv 散点插值 opencv图像插值_计算机视觉_106

1.5倍放大,双线性插值

opencv 散点插值 opencv图像插值_插值_107