附加题part1: 图像处理必需基础
tis:该部分涉及卷积、梯度相关的基本概念和数学基础,已经学习过的uu可以直接跳转到part2;同时该部分是高数中的内容,若想详细学习,本文中的内容是远远不足的(可以作为一个入门了解)
一、卷积
tis:推荐先看下B站上的这些视频,能有效帮助你理解卷积
从“卷积”、到“图像卷积操作”、再到“卷积神经网络”,“卷积”意义的3次改变
【卷积】直观形象的实例,10分钟彻底搞懂
1、数学上的卷积
(1)关于卷积(数学上)
- 数学上的卷积:一种函数间的运算,符号是星号*
卷积的过程可以理解为:翻转,滑动,叠加
2、图像处理中的卷积
(1)一个很好的例子:
-
已知一个4*4位图
f(0,0) f(0,1) f(0,2) f(0,3) f(1,0) f(1,1) f(1,2) f(1,3) f(2,0) f(2,1) f(2,2) f(2,3) f(3,0) f(3,1) f(3,2) f(3,3) -
现在假定一个卷积函数,并设定一个卷积核g(x,y)[一般将中间位置设置为g(0,0)]
g(−1,−1) g(−1,0) g(−1,1) g(0,−1) g(0,0) g(0,1) g(1,−1) g(1,0) g(1,1) -
现在就可以设置函数进行卷积操作啦!
对于u和v的取值,取决于卷积核的大小一般常用的是3*3的卷积核,及u=v=1(上面的例子也是如此)
∴原式 = f(0,0)∗g(1,1)+f(0,1)∗g(1,0)+… -
哪里体现“卷”了?将g(x,y)沿着x轴和y轴分别旋转180度
g(1,1) g(1,0) g(1,−1) g(0,1) g(0,0) g(0,−1) g(−1,1) g(−1,0) g(−1,−1) 使得呈现出这样的效果(两个相乘的部分一一对应)
-
注意:为保证得到的图像与原图像规格相同,处理时会在图像外围加上一圈空白像素(padding)
当然也可能有减少参数的需要(以提取图像中最有用的特征)
(2)总结一下啥是卷积
- 就是在图像上滑动滤波器来进行卷积运算,最终得到一个新的图片
- 原理上其实是对两张像素矩阵进行点乘求和的数学操作,求得的结果表示为原始图像中提取的特定局部特征
- 这么看来,卷积也不过如此吧?
3、有关卷积操作的代码知识
(1)卷积操作中的基本参数
(2)卷积核的其它变种
-
标准卷积(就是上面那种,最常用)
-
扩张卷积(带孔卷积/空洞卷积)
- 引入了参数:扩张率(Dilation Rate)
- 同样尺寸的卷积核可以获得更大的感受视野,在相同感受视野的前提下采用更少的参数
- 广泛应用于实时图像分割领域
-
转置卷积
- 输入到输出的维度变换关系恰好与普通卷积的变换关系相反
- 常见于目标检测领域中对小目标的检测和图像分割领域还原输入图像尺度
-
可分离卷积
原理看图就能懂的
应用在模型压缩或一些轻量的卷积神经网络中
(3)有关边界填充的不同方式(borderType=)
- BORDER_REPLICATE:复制法,复制最边缘像素。
aaaaa|abcdefgh|hhhhh
- BORDER_REFLECT:以最边缘像素为轴,将像素对称过去(包含最边缘像素),
fedcba|abcdefgh|hgfedcb
- BORDER_REFLECT_101:以最边缘像素为轴,将像素对称过去(不包含最边缘像素)
gfedcb|abcdefgh|gfedcba
- BORDER_WRAP:外包装法(左边缘右移,右边缘左移)
cdefgh|abcdefgh|abcdefg
- BORDER_CONSTANT:常量法,常数值填充,需要在函数中填入常数参数。
iiiiii|abcdefgh|iiiiii
一般默认i=0(也就是cv.BORDER_ISOLATED)
(4)输入/输出通道数
- 输入通道数:
- 卷积核的通道数应当和原图片一致
- 每个通道对应一个卷积核
- 在RGB中,相当于有3个卷积核构成一个组,分别对3个通道起作用
- 输出通道数:
- 可能是同通道数(GBR->RGB)
- 若小于输入通道数,则可减少参数量,便于处理(RGB->Grey)
- 维度换算公式
- SAME:对不足卷积核大小边界的位置进行0填充(padding)
- VALID:不足的部分自动舍弃(自动将卷积核左上角对准图片左上角)
(该图中的演示结果只有第一通道,使用了两组卷积核对RGB图像的三通道进行卷积操作)
4、numpy手搓卷积
tis1:该部分推荐自己手搓一遍,可以加深对卷积操作的理解
tis2:此处博主基于numpy手搓卷积编写
1、简单模式:单纯的二维卷积
- np.zeros:初始化二维数组
- 代码实现:
import numpy as np
image = np.array([[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
[13, 14, 15, 16]])
filter = np.array([[1, 2],
[3, 4]])
image_height, image_width = image.shape
filter_height, filter_width = filter.shape
output_height = image_height - filter_height + 1
output_width = image_width - filter_width + 1
output_image = np.zeros((output_height, output_width))
for i in range(output_height):
for j in range(output_width):
output_image[i][j] = np.sum(image[i:i+filter_height, j:j+filter_width] * filter)
print(output_image)
2、普通模式:添加padding和stride
- 关于np.pad(array,pad_width,mode,**kwargs)
- 可以参照np.pad()详解学习哦
- 该函数自动添加行和列,所以后续使用赋值变量时就可以忽略padding的问题了
- 关于pad_width
参数格式:((before_1, after_1), (before_2, after_2), … , (before_N, after_N))
必须将其合并为一个整体(即外加括号变成一个大元组)一起进行传参 - 关于参数mode
mode参数类型:str(10种取值)、function
str类型取值包括:- ‘constant’——表示连续填充相同的值,每个轴可以分别指定填充值,constant_values=(x, y)时前面用x填充,后面用y填充,缺省值填充0
- ‘edge’——表示用边缘值填充(前面的复制法)
- ‘linear_ramp’——表示用边缘递减的方式填充
- ‘maximum’——表示最大值填充
- ‘mean’——表示均值填充
- ‘median’——表示中位数填充
- ‘minimum’——表示最小值填充
- ‘reflect’——表示对称填充(前面的REFLECT_101,即关于边缘对称)
- ‘symmetric’——表示对称填充(前面的REFLECT,即关于边缘外的空气对称)
- ‘wrap’——表示用原数组后面的值填充前面,前面的值填充后面(和前面的一致,平移)
- 输出维度的的计算问题
其实有两种思路:- output_height = (img_height + 2 * padding - filter_height + 1) // stride
- output_height = (img_height + 2 * padding - filter_height) // stride + 1
个人倾向于第二种,因为更能直观地理解
- 代码实现
import numpy as np
def convolve(img, filter, stride, padding = 0):
img_height, img_width = img.shape
filter_height, filter_width = filter.shape
output_height = (img_height + 2 * padding - filter_height + 1) // stride
output_width = (img_width + 2 * padding - filter_width + 1) // stride
output_img = np.empty((output_height, output_width))
pad_width = ((padding, padding), (padding, padding))
if padding > 0:
img = np.pad(img, pad_width, mode='constant')
for i in range(0, output_height, stride):
for j in range(0, output_width, stride):
output_img[i // stride][j // stride] = np.sum(img[i:i+filter_height, j:j+filter_width] * filter)
return output_img
img = np.array([[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
[13, 14, 15, 16]])
filter = np.array([[1, 2],
[3, 4]])
result = convolve(img, filter, 1, padding=0)
print(result)
3、困难模式:多通道支持
- 其实难度也不大,就是在前面的基础上多加了一个通道
- 需要注意的点:
图像和卷积核的通道数应当相同
pad的时候,通道位不添加,即传入(0, 0) - 代码实现
import numpy as np
def convolve(img, filter, stride, padding = 0):
img_height, img_width, img_channel = img.shape
filter_height, filter_width, filter_channel = filter.shape
output_height = (img_height + 2 * padding - filter_height + 1) // stride
output_width = (img_width + 2 * padding - filter_width + 1) // stride
output_img = np.empty((output_height, output_width))
if padding > 0:
img = np.pad(img, ((padding, padding), (padding, padding), (0, 0)), mode='constant')
for i in range(0, output_height, stride):
for j in range(0, output_width, stride):
for k in range(filter_channel):
output_img[i // stride][j // stride] = np.sum(img[i:i+filter_height, j:j+filter_width, k] * filter[:, :, k])
return output_img
img = np.random.rand(4, 4, 3)
filter = np.random.rand(2, 2, 3)
result = convolve(img, filter, 1, padding=0)
print(result)
二、梯度
tis:还是先奉上B站的up主的讲授微课,助于理解
【多元微分专题】第六期:方向导数和梯度的直观理解
【梯度下降】3D可视化讲解通俗易懂(这个可以先了解一下)
1、数学上的梯度是啥?
- first:什么是偏导数?
即:对于一个二维平面方程,分别关于x,y的变化率(是在某点处进行定义)
通俗理解:从该点位置分别沿自变量坐标轴方向上的导数(切线斜率)
- second:什么是方向导数?
通俗理解:将偏导数中的“沿坐标轴方向”改为“任意方向”
贡献一个宝藏网址:Directional Derivative
方向向量:(cosα ,cosβ),α和β分别为单位向量与x轴和y轴的角度
- third:梯度究竟是个啥?
即:由偏导数构成的向量(本质上就是一个向量)
方向向量与梯度的内积就是方向导数
梯度与方向向量共线时,方向导数取最大值(也就是在该点所截平面中所能取到的斜率最大值)
2、图像中的梯度
- 由于图像中的二维函数是离散型的故最小为1
即:图像的梯度相当于2个相邻像素之间的差值
掌握了相关数学基础,我们便可以正式进入图像处理啦!
特别声明:以上的图片部分来自于网络,感谢CSDN、知乎等平台上各位博主的分享,本文用作交流学习予以引用,在此一并表示感谢!