就地操作

问题来源:看代码发现self.relu=nn.ReLU(inplace=True)不明白inplace=True什么意思。

解答:查看pytorch官网关于ReLU定义,ReLU其中inplace参数表示可以选择就地执行操作,默认为False,就地执行操作是指图像处理函数的输入图像和输出图像是同一对象,即同一张图像,常规的图像处理函数是不支持输入图像和输出图像是同一图像的。

eg:中值滤波函数

medianBlur(src, dst, 7); //常规操作

medianBlur(src, src, 7); //就地操作

就地操作直接更改张量的内容,而无需复制它。由于它不创建输入的副本,因此在处理高维数据时减少了内存使用,就地操作有助于使用更少的GPU内存,详情请看该博客如何在Pytorch中执行就地操作

torch.max中keepdim的作用

torch.max的用法:

(max, max_indices) = torch.max(input, dim, keepdim=False)

  • 输入:
  1. input 是输入的tensor。
  2. dim 是索引的维度,dim=0寻找每一列的最大值,dim=1寻找每一行的最大值。
  3. keepdim 表示是否需要保持输出的维度与输入一样,keepdim=True表示输出和输入的维度一样,keepdim=False表示输出的维度被压缩了,也就是输出会比输入低一个维度。
  • 输出:
  1. max 表示取最大值后的结果。

  2. 2max_indices 表示最大值的索引

import torch

import numpy as np

x = torch.randint(0,9,(2,4))

print(x) tensor([[7, 8, 7, 2], [6, 0, 3, 0]])

取每一行的最大值,torch.max的输出结果

y = torch.max(x, 1) print(y) torch.return_types.max(values=tensor([8, 6]),indices=tensor([1, 0])) #索引值

y = torch.max(x, 1, keepdim=True)[0]
print(y)
print(np.shape(y)) # keepdim=True,输出仍然是二维的
tensor([[8], [6]])torch.Size([2, 1])
y = torch.max(x, 1, keepdim=False)[0]
print(y)
print(np.shape(y)) keepdim=False # 输出变成了一维tensor([8, 6])
torch.Size([2])

ConstantPad2d的用法

torch.nn.ConstantPad2d(padding, value)

参数:padding(int, tuple)-padding的尺寸,如果是整型,那么所有的边界都使用相同的填充,如果是四元组,使用(padding_left, padding_right, padding_top, padding_bottom)

形状:

  • 输入:

  • 输出:

    其中,

测试用例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import torch
import torch.nn as nn

n1 = nn.ConstantPad2d(2, 0)
n2 = nn.ConstantPad2d((0, 1, 0, 1), 0)
n3 = nn.ConstantPad2d((-1, 0, -1, 0), 0)
input = torch.randn(1, 2, 2)
print(input)
t = n1(input)
print(t)
x = n2(input)
y = n3(x)
print(x)
print(y)

结果:

tensor([[[ 1.0826, 0.1191],
[-0.3506, 0.1677]]])
tensor([[[ 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000],
[ 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000],
[ 0.0000, 0.0000, 1.0826, 0.1191, 0.0000, 0.0000],
[ 0.0000, 0.0000, -0.3506, 0.1677, 0.0000, 0.0000],
[ 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000],
[ 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000]]])
tensor([[[ 1.0826, 0.1191, 0.0000],
[-0.3506, 0.1677, 0.0000],
[ 0.0000, 0.0000, 0.0000]]])
tensor([[[0.1677, 0.0000],
[0.0000, 0.0000]]])

更多详情参考ConstantPad2d

enumerate()函数

描述

enumerate() 函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标,一般用在 for 循环当中。Python 2.3. 以上版本可用,2.6 添加 start 参数。

语法

enumerate(sequence, [start=0])

参数

·sequence—一个序列、迭代器或其他支持迭代对象

·start—下标起始位置的值

返回值

返回enumerate(枚举)对象

实例

以下展示了使用enumerate()方法的实例:

seasons = [‘Spring’, ‘Summer’, ‘Fall’, ‘Winter’]

list(enumerate(seasons))

[(0, 'Spring'), (1, 'Summer'), (2, 'Fall'), (3, 'Winter')]

list(enumerate(seasons, start=1)) # 下标从1开始

[(1, 'Spring'), (2, 'Summer'), (3, 'Fall'), (4, 'Winter')]

普通的for循环

1
2
3
4
5
6
7
8
9
10
i = 0
seq = ['one', 'two', 'three']
for i in enumerate(seq):
print(i, seq[i])
i += 1

result:
0 one
1 two
2 three

for循环使用enumerate

1
2
3
4
5
6
7
8
seq = ['one', 'two', 'three']
for i, element in enumerate(seq):
print(i, element)

result:
0 one
1 two
2 three

torch.clamp

torch.clamp(input, min=None, max=None, *, out=None)->Tensor

Clamps中所有输入的元素都在[min, max]范围内,让最小值和最大值分别是min和max,将会返回:

如果min为空,就没有下界。或者,如果max为空,没有上界。

注:

如果min大于max,torch.clamp(...,min,max)设置输入的所有元素为max的值。

参数:

·input(Tensor)-输入张量

·min(Number或Tensor,可选)-被限制范围的下界

·max(Number或Tensor,可选)-被限制范围的上界

关键字参数:

out(Tensor, 可选)-输出的张量

举例:

1
2
3
4
5
6
7
8
9
>>>a = torch.randn(4)
>>>a
tensor([-1.7120, 0.1734, -0.0478, -0.0922])
>>>torch.clamp(a, min=-0.5, max=0.5)
tensor([-0.5000, 0.1734, -0.0478, -0.0922])

>>>min = torch.linspace(-1, 1, steps=4)
>>>torch.clamp(a, min=min)
tensor([-1.000, 0.1734, 0.3333, 1.0000])

FPPI

(68条消息) Recall/Precision/FPPI评价方式详解_Bruce_0712的博客-CSDN博客

torchvision.ops.box_iou

语法

torchvision.ops.box_iou(boxes1:torch.Tensor, boxes2:torch.Tensor)->torch.TensorSOURCE

返回两个框的交并比,这两个框的形式都是并且

参数

·boxes1(Tensor[N, 4])->第一个框

·boxes2(Tensor[N, 4])->第二个框

返回

返回boxes1和boxes2逐元素的配对IOU矩阵(N×M)

返回类型

Tensor(N,M)

torch.argmax()函数

torch.argmax(input)->LongTensor

返回input张量所有元素的最大值序号,这是torch.max()返回的第二个值。

注:

如果这里有多个最大值,则会返回第一个最大值的序号。

参数

input(Tensor)->输入的张量

举例:

1
2
3
4
5
6
7
8
>>> a = torch.randn(4, 4)
>>> a
tensor([[ 1.3398, 0.2663, -0.2686, 0.2450],
[-0.7401, -0.8805, -0.3402, -1.1936],
[ 0.4907, -1.3948, -1.0691, -0.3132],
[-1.6092, 0.5419, -0.2993, 0.3195]])
>>> torch.argmax(a)
tensor(0)

torch.argmax(input, dim, keppdim=False)->LongTensor

通过指定维度返回张量最大值的序号,这个最大值将会通过torch.max()返回

参数

·input(Tensor):输入的张量

·dim(int):减少的维度,如果没有,将返回平铺后的张量的argmax.

·keepdim(bool):输出的张量是否保持维度,如果dim=None将忽略。

举例:

1
2
3
4
5
6
7
8
>>> a = torch.randn(4, 4)
>>> a
tensor([[ 1.3398, 0.2663, -0.2686, 0.2450],
[-0.7401, -0.8805, -0.3402, -1.1936],
[ 0.4907, -1.3948, -1.0691, -0.3132],
[-1.6092, 0.5419, -0.2993, 0.3195]])
>>> torch.argmax(a, dim=1)
tensor([ 0, 2, 0, 1])
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import torch
a = torch.tensor([
[
[1, 5, 5, 2],
[9, -6, 2, 8],
[-3, 7, -9, 1]
],

[
[-1, 7, -5, 2],
[9, 6, 2, 8],
[3, 7, 9, 1]
]])
b = torch.argmax(a, dim=0)
print(b)
print(a.shape)

"""
tensor([[0, 1, 0, 1],
[1, 1, 1, 1],
[1, 1, 1, 1]])
torch.Size([2, 3, 4])"""

# dim=0,即将第一个维度消除,也就是将两个[3*4]矩阵只保留一个,因此要在两组中作比较,即将上下两个[3*4]的矩阵分别在对应的位置上比较大小

b = torch.argmax(a, dim=1)
"""
tensor([[1, 2, 0, 1],
[1, 2, 2, 1]])
torch.Size([2, 3, 4])
"""
# dim=1,即将第二个维度消除,这么理解:矩阵维度变为[2*4];
"""
[1, 5, 5, 2],
[9, -6, 2, 8],
[-3, 7, -9, 1];
纵向压缩成一维,因此变为[1,2,0,1];同理得到[1,2,2,1];
"""
b = torch.argmax(a,dim=2)
"""
tensor([[2, 0, 1],
[1, 0, 2]])
"""
# dim=2,即将第三个维度消除,这么理解:矩阵维度变为[2*3]
"""
[1, 5, 5, 2],
[9, -6, 2, 8],
[-3, 7, -9, 1];
横向压缩成一维
[2,0,1],同理得到下面的"""

python _call_()方法

本节再介绍 Python 类中一个非常特殊的实例方法,即 _call_()。该方法的功能类似于在类中重载 () 运算符,使得类实例对象可以像调用普通函数那样,以“对象名()”的形式使用。

1
2
3
4
5
6
7
8
# 引用来自C语言中文网,详情请参考:http://c.biancheng.net/view/2380.html
class CLanguage:
# 定义__call__方法
def __call__(self,name,add):
print("调用__call__()方法",name,add)

clangs = CLanguage()
clangs("C语言中文网","http://c.biancheng.net")

程序执行结果:

1
调用__call__()方法 C语言中文网 http://c.biancheng.net

可以看到,通过在 CLanguage 类中实现 _call_() 方法,使的 clangs 实例对象变为了可调用对象。

Python 中,凡是可以将 () 直接应用到自身并执行,都称为可调用对象。可调用对象包括自定义的函数、Python 内置函数以及本节所讲的类实例对象。

对于可调用对象,实际上“名称()”可以理解为是“名称.call()”的简写。仍以上面程序中定义的 clangs 实例对象为例,其最后一行代码还可以改写为如下形式:

1
clangs.__call__("C语言中文网","http://c.biancheng.net")

运行程序会发现,其运行结果和之前完全相同。

自定义函数:

1
2
3
4
def say():
print("Python教程:http://c.biancheng.net/python")
say()
say.__call__()

程序执行结果:

1
2
Python教程:http://c.biancheng.net/python
Python教程:http://c.biancheng.net/python

call() 弥补 hasattr() 函数的短板

前面章节介绍了 hasattr() 函数的用法,该函数的功能是查找类的实例对象中是否包含指定名称的属性或者方法,但该函数有一个缺陷,即它无法判断该指定的名称,到底是类属性还是类方法。

要解决这个问题,我们可以借助可调用对象的概念。要知道,类实例对象包含的方法,其实也属于可调用对象,但类属性却不是。举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
class CLanguage:
def __init__ (self):
self.name = "C语言中文网"
self.add = "http://c.biancheng.net"
def say(self):
print("我正在学Python")

clangs = CLanguage()
if hasattr(clangs,"name"):
print(hasattr(clangs.name,"__call__"))
print("**********")
if hasattr(clangs,"say"):
print(hasattr(clangs.say,"__call__"))

程序执行结果:

1
2
3
False
**********
True

可以看到,由于 name 是类属性,它没有以 call 为名的 call() 方法;而 say 是类方法,它是可调用对象,因此它有 call() 方法。

argparse()函数

argparse 模块可以让人轻松编写用户友好的命令行接口。程序定义它需要的参数,然后 argparse 将弄清如何从 sys.argv 解析出那些参数。 argparse 模块还会自动生成帮助和使用手册,并在用户给程序传入无效参数时报出错误信息。

以下代码是一个 Python 程序,它获取一个整数列表并计算总和或者最大值:

1
2
3
4
5
6
7
8
9
10
11
import argparse

parser = argparse.ArgumentParser(description='Process some integers.')
parser.add_argument('integers', metavar='N', type=int, nargs='+',
help='an integer for the accumulator')
parser.add_argument('--sum', dest='accumulate', action='store_const',
const=sum, default=max,
help='sum the integers (default: find the max)')

args = parser.parse_args()
print args.accumulate(args.integers)

假设上面的 Python 代码保存在名为 prog.py 的文件中,它可以在命令行运行并提供有用的帮助信息:

1
2
3
4
5
6
7
8
9
10
11
$ python prog.py -h
usage: prog.py [-h] [--sum] N [N ...]

Process some integers.

positional arguments:
N an integer for the accumulator

optional arguments:
-h, --help show this help message and exit
--sum sum the integers (default: find the max)

当使用适当的参数运行时,它会输出命令行传入整数的总和或者最大值:

1
2
3
4
5
$ python prog.py 1 2 3 4
4

$ python prog.py 1 2 3 4 --sum
10

如果传入无效参数,则会报出错误:

1
2
3
$ python prog.py a b c
usage: prog.py [-h] [--sum] N [N ...]
prog.py: error: argument N: invalid int value: 'a'

更多详情参考python文档,网址:15.4. argparse — 命令行选项、参数和子命令解析器 — Python 2.7.18 文档

Distuils

大部分Python程序员都知道,有很多第三方包管理器供选择,包括setuptools、distribute等等。 有些是为了替代标准库中的distutils。

1.1概念和术语

对于模块开发者以及需要安装模块的使用者来说,Distutils的使用都很简单,作为一个开发者,除了编写源码之外,还需要:

·编写setup脚本(一般是setup.py);

·编写一个setup配置文件(可选);

·创建一个源码发布;

·创建一个或多个构建(二进制)发布(可选);

有些模块开发者在开发时不会考虑多个平台发布,所以就有了packagers的角色,它们从模块开发者那取得源码发布,然后在多个平台上面进行构建,并发布多个平台的构建版本。

1.2简单例子

由python编写的setup脚本一般都非常简单。作为autoconf类型的配置脚本,setup脚本可以在构建和安装模块发布时运行多次。

比如,如果需要发布一个叫做foo的模块,它包含在一个文件foo.py,那setup脚本可以这样写:

1
2
3
4
5
from distutils.core import setup  
setup(name='foo',
version='1.0',
py_modules=['foo'],
)

setup函数的参数表示提供给Distutils的信息,这些参数分为两类:包的元数据(包名、版本号)以及包的信息(本例中是一个Python模块的列表);模块由模块名表示,而不是文件名(对于包和扩展而言也是这样);建议可以提供更多的元数据,比如你的名字,email地址和项目的URL地址。

编写好setup.py之后,就可以创建该模块的源码发布了:

1
python setup.py sdist  

sdist命令会创建一个archive 文件(比如Unix上的tar文件,Windows上的zip文件),它包含setup.py, foo.py。该archive文件命名为foo-1.0.tar.gz(zip),解压之后的目录名是foo-1.0。

如果一个用户希望安装foo模块,他只需要下载foo-1.0.tar.gz,解压,进入foo-1.0目录,然后运行:

1
python setup.py install  

该命令最终会将foo.py复制到Python环境存放第三方模块的目录中。在linux环境下,运行该命令的输出是:

1
2
3
4
5
6
7
8
9
10
11
12
# python setup.py install  
running install
running build
running build_py
creating build
creating build/lib
copying foo.py -> build/lib
running install_lib
copying build/lib/foo.py -> /usr/lib/python2.7/site-packages
byte-compiling /usr/lib/python2.7/site-packages/foo.py to foo.pyc
running install_egg_info
Writing /usr/lib/python2.7/site-packages/foo-1.0-py2.7.egg-info

该命令生成的文件是:

/usr/lib/python2.7/site-packages/foo-1.0-py2.7.egg-info

/usr/lib/python2.7/site-packages/foo.py

/usr/lib/python2.7/site-packages/foo.pyc

图片缩放方式

对图像进行预处理操作的时候,一般有两种缩放方式。

  • 一种是直接宽、高缩放至想要的宽、高,这种方式快捷,但可能会导致图像变形
    step1: 计算宽高缩放比例,选择较小的那个缩放系数;
    step2: 计算缩放后的尺寸: 原始图片的长宽都乘以较小的缩放系数;
    step3:计算短边需要填充的灰边数,将短边的两边各自填充一半的灰行即可。
  • 一种是等比例缩放,然后用灰色边缘填充

直接缩放

代码实现如下:

new_image = image.resize((target_w, target_h), Image.BICUBIC)

不变形缩放,两端填充灰边

不变形缩放,一端填充灰边

很多图片的长宽比不同导致缩放填充后,两端的黑边大小都不同。而如果填充的比较多,则存在信息冗余,影响推理速度。YOLOv5作者对letterbox的缩放策略进行了修改,对原图自适应的添加最少的黑边。
计算方法:
1.计算原始图片宽高与输入尺寸的缩放比例rw和rh,选取r = min(rw,rh)后把原图按r进行缩放
2.原图宽和高中一定有一边完全贴合输入尺寸,没有达到输入尺寸的一边计算与输入尺寸的差值,然后进行上下(or左右)的填充。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import matplotlib.pyplot as plt
from PIL import Image

# ---------------------------------------------------#
# 对输入图像进行resize,他人测试发现,不用letterbox_image直接resize的效果更好
# ---------------------------------------------------#
def resize_image(image, size, letterbox_image):
iw, ih = image.size
w, h = size # w=200, h=300
if letterbox_image:
scale = min(w/iw, h/ih)
nw = int(iw*scale)
nh = int(ih*scale)

image = image.resize((nw,nh), Image.BICUBIC)
new_image = Image.new('RGB', size, (128,128,128)) # 新建一张image,第二个参数表示尺寸,第三个参数表示颜色
# --------------------------------------------------#
# image.paste函数表示将一张图片覆盖到另一张图片的指定位置去
# a.paste(b, (50,50)) 将b的左上顶点贴到a的坐标为(50,50)的位置,左上顶点为(0,0), b超出a的部分会被自动舍弃
# ---------------------------------------------------#
# new_image.paste(image, ((w-nw)//2, (h-nh)//2)) # 不变形resize,两端填充灰边
new_image.paste(image, (0, 0)) # 不变形resize,一端填充灰边
else:
new_image = image.resize((w, h), Image.BICUBIC)
return new_image


img_PIL = Image.open("Avatar.jpg")
img = resize_image(img_PIL, (200, 300), True) # 第二参数表示目标尺寸,第三参数表示是否使用letterbox
plt.imshow(img)
plt.show()
# 作者:寻找永不遗憾
# 链接:https://www.jianshu.com/p/2ae3a497f5f4
# 来源:简书
# 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

图像resize插值方式比较

resize函数说明:

void resize(InputArray src, OutputArray dst, Size dsize, double fx=0, double fy=0, int interpolation=INTER_LINEAR)

参数说明:

src:输入,原图像,即待改变大小的图像;
dst:输出,改变大小之后的图像,这个图像和原图像具有相同的内容,只是大小和原图像不一样而已;
dsize:输出图像的大小。如果这个参数不为0,那么就代表将原图像缩放到这个Size(width,height)指定的大小;如果这个参数为0,那么原图像缩放之后的大小就要通过下面的公式来计算:

其中,fx和fy就是下面要说的两个参数,是图像width方向和height方向的缩放比例。

fx:width方向的缩放比例,如果它是0,那么它就会按照(double)dsize.width/src.cols来计算;
fy:height方向的缩放比例,如果它是0,那么它就会按照(double)dsize.height/src.rows来计算;
interpolation:这个是指定插值的方式,图像缩放之后,肯定像素要进行重新计算的,就靠这个参数来指定重新计算像素的方式,有以下几种:
·INTER_NEAREST - 最邻近插值
·INTER_LINEAR - 双线性插值,如果最后一个参数你不指定,默认使用这种方法
·INTER_AREA-区域插值
·INTER_CUBIC - 4x4像素邻域内的双立方插值
·INTER_LANCZOS4- 8x8像素邻域内的Lanczos插值

各种插值方式比较:

每种插值算法的前部分代码是相同的,如下:

1
2
3
4
5
6
7
8
cv::Mat matSrc, matDst1, matDst2;

matSrc = cv::imread("lena.jpg", 2 | 4);
matDst1 = cv::Mat(cv::Size(800, 1000), matSrc.type(), cv::Scalar::all(0));
matDst2 = cv::Mat(matDst1.size(), matSrc.type(), cv::Scalar::all(0));

double scale_x = (double)matSrc.cols / matDst1.cols;
double scale_y = (double)matSrc.rows / matDst1.rows;

最近邻:

实现代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
for (int i = 0; i < matDst1.cols; ++i)
{
int sx = cvFloor(i * scale_x);
sx = std::min(sx, matSrc.cols - 1);
for (int j = 0; j < matDst1.rows; ++j)
{
int sy = cvFloor(j * scale_y);
sy = std::min(sy, matSrc.rows - 1);
matDst1.at<cv::Vec3b>(j, i) = matSrc.at<cv::Vec3b>(sy, sx);
}
}
cv::imwrite("nearest_1.jpg", matDst1);

cv::resize(matSrc, matDst2, matDst1.size(), 0, 0, 0);
cv::imwrite("nearest_2.jpg", matDst2);

双线性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
uchar* dataDst = matDst1.data;
int stepDst = matDst1.step;
uchar* dataSrc = matSrc.data;
int stepSrc = matSrc.step;
int iWidthSrc = matSrc.cols;
int iHiehgtSrc = matSrc.rows;

for (int j = 0; j < matDst1.rows; ++j)
{
float fy = (float)((j + 0.5) * scale_y - 0.5);
int sy = cvFloor(fy);
fy -= sy;
sy = std::min(sy, iHiehgtSrc - 2);
sy = std::max(0, sy);

short cbufy[2];
cbufy[0] = cv::saturate_cast<short>((1.f - fy) * 2048);
cbufy[1] = 2048 - cbufy[0];

for (int i = 0; i < matDst1.cols; ++i)
{
float fx = (float)((i + 0.5) * scale_x - 0.5);
int sx = cvFloor(fx);
fx -= sx;

if (sx < 0) {
fx = 0, sx = 0;
}
if (sx >= iWidthSrc - 1) {
fx = 0, sx = iWidthSrc - 2;
}

short cbufx[2];
cbufx[0] = cv::saturate_cast<short>((1.f - fx) * 2048);
cbufx[1] = 2048 - cbufx[0];

for (int k = 0; k < matSrc.channels(); ++k)
{
*(dataDst+ j*stepDst + 3*i + k) = (*(dataSrc + sy*stepSrc + 3*sx + k) * cbufx[0] * cbufy[0] +
*(dataSrc + (sy+1)*stepSrc + 3*sx + k) * cbufx[0] * cbufy[1] +
*(dataSrc + sy*stepSrc + 3*(sx+1) + k) * cbufx[1] * cbufy[0] +
*(dataSrc + (sy+1)*stepSrc + 3*(sx+1) + k) * cbufx[1] * cbufy[1]) >> 22;
}
}
}
cv::imwrite("linear_1.jpg", matDst1);

cv::resize(matSrc, matDst2, matDst1.size(), 0, 0, 1);
cv::imwrite("linear_2.jpg", matDst2);

双三次:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
int iscale_x = cv::saturate_cast<int>(scale_x);
int iscale_y = cv::saturate_cast<int>(scale_y);

for (int j = 0; j < matDst1.rows; ++j)
{
float fy = (float)((j + 0.5) * scale_y - 0.5);
int sy = cvFloor(fy);
fy -= sy;
sy = std::min(sy, matSrc.rows - 3);
sy = std::max(1, sy);

const float A = -0.75f;

float coeffsY[4];
coeffsY[0] = ((A*(fy + 1) - 5*A)*(fy + 1) + 8*A)*(fy + 1) - 4*A;
coeffsY[1] = ((A + 2)*fy - (A + 3))*fy*fy + 1;
coeffsY[2] = ((A + 2)*(1 - fy) - (A + 3))*(1 - fy)*(1 - fy) + 1;
coeffsY[3] = 1.f - coeffsY[0] - coeffsY[1] - coeffsY[2];

short cbufY[4];
cbufY[0] = cv::saturate_cast<short>(coeffsY[0] * 2048);
cbufY[1] = cv::saturate_cast<short>(coeffsY[1] * 2048);
cbufY[2] = cv::saturate_cast<short>(coeffsY[2] * 2048);
cbufY[3] = cv::saturate_cast<short>(coeffsY[3] * 2048);

for (int i = 0; i < matDst1.cols; ++i)
{
float fx = (float)((i + 0.5) * scale_x - 0.5);
int sx = cvFloor(fx);
fx -= sx;

if (sx < 1) {
fx = 0, sx = 1;
}
if (sx >= matSrc.cols - 3) {
fx = 0, sx = matSrc.cols - 3;
}

float coeffsX[4];
coeffsX[0] = ((A*(fx + 1) - 5*A)*(fx + 1) + 8*A)*(fx + 1) - 4*A;
coeffsX[1] = ((A + 2)*fx - (A + 3))*fx*fx + 1;
coeffsX[2] = ((A + 2)*(1 - fx) - (A + 3))*(1 - fx)*(1 - fx) + 1;
coeffsX[3] = 1.f - coeffsX[0] - coeffsX[1] - coeffsX[2];

short cbufX[4];
cbufX[0] = cv::saturate_cast<short>(coeffsX[0] * 2048);
cbufX[1] = cv::saturate_cast<short>(coeffsX[1] * 2048);
cbufX[2] = cv::saturate_cast<short>(coeffsX[2] * 2048);
cbufX[3] = cv::saturate_cast<short>(coeffsX[3] * 2048);

for (int k = 0; k < matSrc.channels(); ++k)
{
matDst1.at<cv::Vec3b>(j, i)[k] = abs((matSrc.at<cv::Vec3b>(sy-1, sx-1)[k] * cbufX[0] * cbufY[0] + matSrc.at<cv::Vec3b>(sy, sx-1)[k] * cbufX[0] * cbufY[1] +
matSrc.at<cv::Vec3b>(sy+1, sx-1)[k] * cbufX[0] * cbufY[2] + matSrc.at<cv::Vec3b>(sy+2, sx-1)[k] * cbufX[0] * cbufY[3] +
matSrc.at<cv::Vec3b>(sy-1, sx)[k] * cbufX[1] * cbufY[0] + matSrc.at<cv::Vec3b>(sy, sx)[k] * cbufX[1] * cbufY[1] +
matSrc.at<cv::Vec3b>(sy+1, sx)[k] * cbufX[1] * cbufY[2] + matSrc.at<cv::Vec3b>(sy+2, sx)[k] * cbufX[1] * cbufY[3] +
matSrc.at<cv::Vec3b>(sy-1, sx+1)[k] * cbufX[2] * cbufY[0] + matSrc.at<cv::Vec3b>(sy, sx+1)[k] * cbufX[2] * cbufY[1] +
matSrc.at<cv::Vec3b>(sy+1, sx+1)[k] * cbufX[2] * cbufY[2] + matSrc.at<cv::Vec3b>(sy+2, sx+1)[k] * cbufX[2] * cbufY[3] +
matSrc.at<cv::Vec3b>(sy-1, sx+2)[k] * cbufX[3] * cbufY[0] + matSrc.at<cv::Vec3b>(sy, sx+2)[k] * cbufX[3] * cbufY[1] +
matSrc.at<cv::Vec3b>(sy+1, sx+2)[k] * cbufX[3] * cbufY[2] + matSrc.at<cv::Vec3b>(sy+2, sx+2)[k] * cbufX[3] * cbufY[3] ) >> 22);
}
}
}
cv::imwrite("cubic_1.jpg", matDst1);

cv::resize(matSrc, matDst2, matDst1.size(), 0, 0, 2);
cv::imwrite("cubic_2.jpg", matDst2);

基于像素区域关系:共分三种情况,图像放大时类似于双线性插值,图像缩小(x轴、y轴同时缩小)又分两种情况,此情况下可以避免波纹出现

具体实现代码可以参考https://github.com/fengbingchun/OpenCV_Test/blob/master/src/fbc_cv/include/resize.hpp,用法如下:

1
2
3
fbc::Mat3BGR src(matSrc.rows, matSrc.cols, matSrc.data);
fbc::Mat3BGR dst(matDst1.rows, matDst1.cols, matDst1.data);
fbc::resize(src, dst, 3);

兰索斯插值:略

测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include <chrono>
#include <opencv2/opencv.hpp>
#define millisecond 1000000
#define DEBUG_PRINT(...) printf( __VA_ARGS__); printf("\n")
#define DEBUG_TIME(time_) auto time_ =std::chrono::high_resolution_clock::now()
#define RUN_TIME(time_) (double)(time_).count()/millisecond
using namespace std;

cv::Mat image_resize(cv::Mat image, int width, int height, int interpolation, int num) {
cv::Mat dest;
for (int i = 0; i < num; ++i) {
cv::resize(image, dest, cv::Size(width, height), 0, 0, interpolation);//最近邻插值
}
return dest;
}


int main() {
string path = "../1.jpg";
cv::Mat image = cv::imread(path);
cv::resize(image, image, cv::Size(1000, 1000));
int re_width = 900;
int re_height = 900;
int num=10;
cv::Mat image2X_INTER_NEAREST;
cv::Mat image2X_INTER_LINEAR;
cv::Mat image2X_INTER_AREA;
cv::Mat image2X_INTER_CUBIC;
cv::Mat initMat;
DEBUG_PRINT("image input size:%dx%d", image.rows, image.cols);
DEBUG_TIME(T0);
image2X_INTER_NEAREST=image_resize(image, re_width, re_height, cv::INTER_NEAREST, num);
DEBUG_TIME(T1);
image2X_INTER_LINEAR=image_resize(image, re_width, re_height, cv::INTER_LINEAR, num);
DEBUG_TIME(T2);
image2X_INTER_AREA=image_resize(image, re_width, re_height, cv::INTER_AREA, num);
DEBUG_TIME(T3);
image2X_INTER_CUBIC=image_resize(image, re_width, re_height, cv::INTER_CUBIC, num);
DEBUG_TIME(T4);
DEBUG_PRINT("resize_image:%dx%d,INTER_NEAREST:%3.3fms",
image2X_INTER_NEAREST.rows,
image2X_INTER_NEAREST.cols,
RUN_TIME(T1 - T0)/num);
DEBUG_PRINT("resize_image:%dx%d,INTER_LINEAR :%3.3fms",
image2X_INTER_LINEAR.rows,
image2X_INTER_LINEAR.cols,
RUN_TIME(T2 - T1)/num);
DEBUG_PRINT("resize_image:%dx%d,INTER_AREA :%3.3fms",
image2X_INTER_AREA.rows,
image2X_INTER_AREA.cols,
RUN_TIME(T3 - T2)/num);
DEBUG_PRINT("resize_image:%dx%d,INTER_CUBIC :%3.3fms",
image2X_INTER_CUBIC.rows,
image2X_INTER_CUBIC.cols,
RUN_TIME(T4 - T3)/num);
return 0;
}
版权声明:本文为CSDN博主「pan_jinquan」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/guyuealian/article/details/85097633

运行结果:

1
2
3
4
5
6
image input size:1000x1000
resize_image:900x900,INTER_NEAREST:0.389ms
resize_image:900x900,INTER_LINEAR :0.605ms
resize_image:900x900,INTER_AREA :2.611ms
resize_image:900x900,INTER_CUBIC :1.920ms

总结:

1
2
3
4
 速度比较:INTER_NEAREST(最近邻插值)>INTER_LINEAR(线性插值)>INTER_CUBIC(三次样条插值)>INTER_AREA  (区域插值)
对图像进行缩小时,为了避免出现波纹现象,推荐采用INTER_AREA 区域插值方法。
OpenCV推荐:如果要缩小图像,通常推荐使用#INTER_AREA插值效果最好,而要放大图像,通常使用INTER_CUBIC(速度较慢,但效果最好),或者使用INTER_LINEAR(速度较快,效果还可以)。至于最近邻插值INTER_NEAREST,一般不推荐使用

更多详情参考:OpenCV: Geometric Image Transformations

torch.nn.Identity()

dentity模块如果不改变输入,直接返回输入

一种编码技巧吧,比如我们要加深网络,有些层是不改变输入数据的维度的,

在增减网络的过程中我们就可以用identity占个位置,这样网络整体层数永远不变,

应用:

例如此时:如果此时我们使用了se_layer,那么就SELayer(dim),否则就输入什么就输出什么(什么都不做)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 定义操作
self.attn_drop = nn.Dropout(attn_drop)
self.proj = nn.Linear(dim, dim)
self.se_layer = SELayer(dim) if se_layer else nn.Identity()
self.proj_drop = nn.Dropout(proj_drop)
# dropout
attn = self.attn_drop(attn)
# 与矩阵V相乘
x = (attn @ v).transpose(1, 2).reshape(B_, N, C)
# 经过一层全连接层
x = self.proj(x)
# nn.Identity():建立一个输入模块,什么都不做
x = self.se_layer(x)
# dropout
x = self.proj_drop(x)
return x

更多详情参考Identity — PyTorch 1.11.0 documentation