目录

数字图像处理

数字图像处理

本博客主要涉及算法方面。需要堆图形由一定了解

预处理,加载,存储

使用pillow进行加载

1
2
from PIL import Image
im = Image.open('./../image/lena.png')

存储

1
2
# 把图像用png格式保存:
im.save('thumbnail.png', 'png')

转换成numpy形式

1
img = np.array(im)      # image类 转 numpy

numpy格式数组用图片显示

1
2
3
from matplotlib import pyplot as plt
plt.imshow(img)
plt.show()

https://raw.githubusercontent.com/kengerlwl/kengerlwl.github.io/refs/heads/master/image/84ec341a66fdbd14311e39fb5b266923/f94e03f541141f10e5ae260eeaf3b346.png

分为不同得通道显示

1
2
b = img[:,:,0:1]
plt.imshow(img, 'Blues')

https://raw.githubusercontent.com/kengerlwl/kengerlwl.github.io/refs/heads/master/image/84ec341a66fdbd14311e39fb5b266923/0970dd506bec75a22096a10f6c3409af.png

关于图像平滑处理

首先对图像添加噪声

我们这里添加白色小点得噪声

https://raw.githubusercontent.com/kengerlwl/kengerlwl.github.io/refs/heads/master/image/84ec341a66fdbd14311e39fb5b266923/88e7af24819a548676006107387ee677.png

白色得噪声是(255,255,255)

得到噪声图

https://raw.githubusercontent.com/kengerlwl/kengerlwl.github.io/refs/heads/master/image/84ec341a66fdbd14311e39fb5b266923/1a7eea8b29c386eb818b8a7545ab761b.png

定义

图像平滑是一种区域增强的算法,平滑算法有邻域平均法、中指滤波、边界保持类滤波等。在图像产生、传输和复制过程中,常常会因为多方面原因而被噪声干扰或出现数据丢失,降低了图像的质量(某一像素,如果它与周围像素点相比有明显的不同,则该点被噪声所感染)。这就需要对图像进行一定的增强处理以减小这些缺陷带来的影响

https://raw.githubusercontent.com/kengerlwl/kengerlwl.github.io/refs/heads/master/image/84ec341a66fdbd14311e39fb5b266923/00af0448eb5eaf0c9dec1f5eaf765889.png

邻域平均法和均值滤波

其实就是一个卷积运算

可以类比一下卷积神经网络

$$g(x,y)= 1/M \sum_{(x,y)∈S} f(x,y)$$

https://raw.githubusercontent.com/kengerlwl/kengerlwl.github.io/refs/heads/master/image/84ec341a66fdbd14311e39fb5b266923/39f570ba9bec4752ee89dd997b2561cd.png

如图

https://raw.githubusercontent.com/kengerlwl/kengerlwl.github.io/refs/heads/master/image/84ec341a66fdbd14311e39fb5b266923/12e61be231a5458f8770e4867e00131c.png

  • 我们这里先写一个单通道单核得卷积

https://raw.githubusercontent.com/kengerlwl/kengerlwl.github.io/refs/heads/master/image/84ec341a66fdbd14311e39fb5b266923/cf8b6e055283c133cd7bb2863f06ec80.png

  • 初始化一个卷积核

    1
    2
    3
    4
    5
    6
    
    # filterKernel = np.array([
    #     [1, 1, 1],
    #     [1, 1, 1],
    #     [1, 1, 1]
    # ])
    filterKernel = np.ones((6,6))
    
  • 查看结果

https://raw.githubusercontent.com/kengerlwl/kengerlwl.github.io/refs/heads/master/image/84ec341a66fdbd14311e39fb5b266923/c0cb034f14a22c8586369fd7181783fb.png

  • 很明显有效果, 然后就是变单通道为多通道,单核为多核

    https://raw.githubusercontent.com/kengerlwl/kengerlwl.github.io/refs/heads/master/image/84ec341a66fdbd14311e39fb5b266923/0c63e5cfbac9c05a15839a2bdff99fe3.png

success

高斯平滑

为了克服简单局部平均法的弊端(图像模糊),目前已提出许多保持边缘、细节的局部平滑算法。它们的出发点都集中在如何选择邻域的大小、形状和方向、参数加平均及邻域各店的权重系数等。 图像高斯平滑也是邻域平均的思想对图像进行平滑的一种方法,在图像高斯平滑中,对图像进行平均时,不同位置的像素被赋予了不同的权重。高斯平滑与简单平滑不同,它在对邻域内像素进行平均时,给予不同位置的像素不同的权值,下图的所示的 3 * 3 和 5 * 5 领域的高斯模板。

其实就是用二维高斯去生成一个卷积核

https://raw.githubusercontent.com/kengerlwl/kengerlwl.github.io/refs/heads/master/image/84ec341a66fdbd14311e39fb5b266923/8a28a3b57fe46705d989c0f2740d3511.png

总结

到这里,一些基本得卷积算法就已经剖析完毕,剩下得就只是对这些卷积核做一定处理。

引申滤波的概念

滤波的目的主要两个:

1.通过滤波来提取图像特征,简化图像所带的信息作为后续其它的图像处理

2.为适应图像处理的需求,通过滤波消除图像数字化时所混入的噪声

其中第一点就是边缘检测中所使用的基本思想,即简化图像信息,使用边缘线代表图像所携带信息

滤波可理解为滤波器(通常为33、55矩阵)在图像上进行从上到下,从左到右的遍历,计算滤波器与对应像素的值并根据滤波目的进行数值计算返回值到当前像素点,实际就是卷积

图像几何变换(缩放、图像旋转、图像翻转与图像平移)

主要知识:线性代数

提前概念

对于一个图像,我们可以表达成一个二维函数

$$f(x, y) = 色值 $$

而几何变换,并不改变色值,仅仅改变x,y得位置。

也就是说

$$f(x^{}, y^{}) = f(x, y)$$

是一个映射

(以下得图转载)

平移

https://raw.githubusercontent.com/kengerlwl/kengerlwl.github.io/refs/heads/master/image/84ec341a66fdbd14311e39fb5b266923/076fe31ea3d3beede9cfb8fe97fc778b.png

缩放

https://raw.githubusercontent.com/kengerlwl/kengerlwl.github.io/refs/heads/master/image/84ec341a66fdbd14311e39fb5b266923/abbcdf7c324d2fe5ecbf12b1b11b9319.png

旋转

https://raw.githubusercontent.com/kengerlwl/kengerlwl.github.io/refs/heads/master/image/84ec341a66fdbd14311e39fb5b266923/9faf48eb59ef440fe453efa8fc87b6d4.png

镜像变换

https://raw.githubusercontent.com/kengerlwl/kengerlwl.github.io/refs/heads/master/image/84ec341a66fdbd14311e39fb5b266923/2a8fcd4cd9e81aeb1681c1dbba85ecbe.png

插值算法

关于以上变换有个要注意得问题。

变换后得坐标,定义域不一定和值域完全重合。

也就是说,变换后得点可能落不到整数上(一种情况。)

那么就需要插值算法了。 (简单起见,这里只讲线性插值算法)

先讲一下整体得套路:

https://raw.githubusercontent.com/kengerlwl/kengerlwl.github.io/refs/heads/master/image/84ec341a66fdbd14311e39fb5b266923/09848574492cb96e8618543a2ab1bce7.png

邻近插值算法

简单来说就是,直接对x和y进行取整。

按照上述思路实现。

https://raw.githubusercontent.com/kengerlwl/kengerlwl.github.io/refs/heads/master/image/84ec341a66fdbd14311e39fb5b266923/a9d28767ad1d6502be7cc49cc768ccd1.png

不难看出,效果还行。

试试旋转矩阵

https://raw.githubusercontent.com/kengerlwl/kengerlwl.github.io/refs/heads/master/image/84ec341a66fdbd14311e39fb5b266923/c0d330b5d9ebac7b31702fbc514ed2e8.png

双线性插值

**双线性插值是线性插值在二维时的推广,在两个方向上共做了三次线性插值。**定义了一个双曲抛物面与四个已知点拟合。

具体操作为在X方向上进行两次线性插值计算,然后在Y方向上进行一次插值计算

https://raw.githubusercontent.com/kengerlwl/kengerlwl.github.io/refs/heads/master/image/84ec341a66fdbd14311e39fb5b266923/4fa4f7aa924b01c16cc2f124f2feb0f2.png

具体得公式

https://raw.githubusercontent.com/kengerlwl/kengerlwl.github.io/refs/heads/master/image/84ec341a66fdbd14311e39fb5b266923/5a2a029603dc329ab1648127d5ba2500.png

(我这里踩了坑,原有的图像数据的大小是8位的,最大255)所以在上述运算中很容易就溢出了。

结果

https://raw.githubusercontent.com/kengerlwl/kengerlwl.github.io/refs/heads/master/image/84ec341a66fdbd14311e39fb5b266923/e52beee2d09d319190019e323ab8c55d.png

success

仿射变换与透视变换

提前概念(二维为例子)

  • 什么是线性变换?

$$ x^{,}= A \cdot x_{0} $$

原点不变,且原有的平行关系和倍数关系都不变

仿射变换

在线性变换的基础上,原点可以发生变换

仿射变换是单纯对图片进行平移,缩放,倾斜和旋转,而这几个操作都不会改变图片线之间的平行关系。

$$ x^{,}= A \cdot x_{0} + b $$

如果用三维去线性表达,那么就是: $$ \begin{bmatrix} x^{,}\ y^{,}\ 1

\end{bmatrix}

\begin{bmatrix} a11 &a12 &b1 \ a21 & a22& b2\ 0&0 & 1 \end{bmatrix}

\cdot

\begin{bmatrix} x\ y\ 1

\end{bmatrix} $$

什么是透视变换

如果说仿射变换是在二维空间中的旋转,平移和缩放。那么透视变换则是在三维空间中视角的变化。

相对于仿射。透视变换能保持“直线性”,即原图像里面的直线,经透视变换后仍为直线

https://raw.githubusercontent.com/kengerlwl/kengerlwl.github.io/refs/heads/master/image/84ec341a66fdbd14311e39fb5b266923/6a9dc656fdb51208fb89fa5cae6e9db9.png

这里讲一下,如果说,仿射变化是三维空间中对某个平面的一些二维变化。

那么透视变化就是对这个平面进行变换,并且利用视觉原理将图像进行一定处理(近大,远小)。

https://raw.githubusercontent.com/kengerlwl/kengerlwl.github.io/refs/heads/master/image/84ec341a66fdbd14311e39fb5b266923/7cd9b73c2e30d18cc70089e39cde9ef7.png

也就是说,先将图像旋转到$A^{,}B^{,}C^{,}$

然后投影到平面$ABC$

这里有一下假设,假设我们人是远点,从我们的眼睛去看,垂直与我们的目光的这个轴是$z$轴.

在离我们一定距离的地方选一个画布$ABC$,其他所有画像都投影到这个画布上。

那么假设平面$z$轴的距离是1.

从$A^{,}B^{,}C^{,}$投影到画布$ABC$,计算公式为

$$ \begin{bmatrix} x\ y\ 1

\end{bmatrix}

(1/z^{,}) \cdot \begin{bmatrix} x^{,}\ y^{,}\ z^{,}

\end{bmatrix} $$

有了以上概念,我们来最后计算一遍

https://raw.githubusercontent.com/kengerlwl/kengerlwl.github.io/refs/heads/master/image/84ec341a66fdbd14311e39fb5b266923/cf979213798d2c47c9812aec744257a2.png

先进行旋转变换。

然后进行投影变换。

https://raw.githubusercontent.com/kengerlwl/kengerlwl.github.io/refs/heads/master/image/84ec341a66fdbd14311e39fb5b266923/6ac973beb6b9b2abd1f3015b67501623.png

然后就是繁琐的解方程过程,这里是非齐次线性方程组求解

https://raw.githubusercontent.com/kengerlwl/kengerlwl.github.io/refs/heads/master/image/84ec341a66fdbd14311e39fb5b266923/bab59271d7636ce0e8e0deca3e1bcb70.png

让我们来看一下最后效果。

https://raw.githubusercontent.com/kengerlwl/kengerlwl.github.io/refs/heads/master/image/84ec341a66fdbd14311e39fb5b266923/5aa9315b59033ce612da1ca402b2263b.png

右边是我们手动实现的。

具体的思路是仿照opencv,不过代码是手动用numpy实现的,有助于理解。

核心代码

 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
#根据四个顶点设置图像透视变换矩阵
pos1 = np.float32([[114, 82], [287, 156], [8, 322], [216, 333]])  # 原来的坐标
pos2 = np.float32([[0, 0], [188, 0], [0, 262], [188, 262]])    # 变换后的坐标

# 实际肉眼看上去的x,y和数组的存储是有区别的
def exchangeXY(pos):
    for i in range(len(pos)):
        pos[i] = [pos[i][1], pos[i][0]]

exchangeXY(pos1)
exchangeXY(pos2)

# 计算透视变换矩阵
def getPerspectiveTransform(pos1, pos2):
    length = len(pos1)
    tmpMatrix = []
    b = []
    for i in range(length):
        x0, y0 = pos1[i]
        xn, yn = pos2[i]
        tmpMatrix.append(
            [x0, y0,1,0,0,0,-1 * x0 * xn, -1 * y0 * xn]
        )
        tmpMatrix.append(
            [0,0,0,x0, y0,1,-1 * x0 * yn, -1 * y0 * yn]
        )

        b.append(xn)
        b.append(yn)


    tmpMatrix= np.array(tmpMatrix)
    b = np.array(b)
    ans =np.dot(np.linalg.inv(tmpMatrix), b)
    finalMatrix = [
        [ans[0], ans[1], ans[2]],
        [ans[3], ans[4], ans[5]],
        [ans[6], ans[7], 1]

    ]
    return np.array(finalMatrix)

M = getPerspectiveTransform(pos1, pos2)

图像阈值化

图像的二值化或阈值化(Binarization)旨在提取图像中的目标物体,将背景以及噪声区分开来。通常会设定一个阈值T,通过T将图像的像素划分为两类:大于T的像素群和小于T的像素群。

https://raw.githubusercontent.com/kengerlwl/kengerlwl.github.io/refs/heads/master/image/84ec341a66fdbd14311e39fb5b266923/433b35f3bcae835a073b6d32eebbd17a.png

二进制阈值化

https://raw.githubusercontent.com/kengerlwl/kengerlwl.github.io/refs/heads/master/image/84ec341a66fdbd14311e39fb5b266923/a8fbf0daf8cfae6d38d46288b089cb0f.png

所谓其他阈值化,其实也就是不断在基础形式上都不同的类进行一定处理。

灰度直方图

前置概念

RGB图像:

RGB的值分别为0,0,0 表示的是黑色。

RGB的值为255,255,255表示的是白色。

灰度图像:

灰度值为0表示黑色。

灰度值为255表示白色。

定义

灰度直方图(histogram)是灰度级的函数,描述的是图像中每种灰度级像素的个数,反映图像中每种灰度出现的频率。横坐标是灰度级,纵坐标是灰度级出现的频率。

https://raw.githubusercontent.com/kengerlwl/kengerlwl.github.io/refs/heads/master/image/84ec341a66fdbd14311e39fb5b266923/40a7560ec1269824078571db3fb345d9.png

用处:

在使用轮廓线确定物体边界时,通过直方图更好的选择边界阈值,进行阈值化处理;对物体与背景有较强对比的景物的分割特别有用;简单物体的面积和综合光密度IOD可以通过图像的直方图求得。

直方图修正

图像的直方图修正方法主要有直方图均衡化和直方图规定化直方图修正的目的是,使修正后的图像的灰度间距拉开或者是图像灰度分布均匀,从而增大反差,使图像细节清晰,从而达到图像增强的目的

我们这里主要直方图均衡化

算法原理,把原有的概率乘以一个数(通常是色值的大小,取整)。这样就可以把一些较为相近的值给化到一起了。

灰度值处理

rbg如何变为灰度值图像呢?

一种常见的方法是将RGB三个分量求和再取平均值,但更为准确的方法是设置不同的权重,将RGB分量按不同的比例进行灰度划分。比如人类的眼睛感官蓝色的敏感度最低,敏感最高的是绿色,因此将RGB按照0.299、0.587、0.144比例加权平均能得到较合理的灰度图像,

https://raw.githubusercontent.com/kengerlwl/kengerlwl.github.io/refs/heads/master/image/84ec341a66fdbd14311e39fb5b266923/502aaa4539120de42999f8496a000538.png

图像灰度线性变换

图像的灰度线性变换是通过建立灰度映射来调整原始图像的灰度,从而改善图像的质量,凸显图像的细节,提高图像的对比度

$$ f(D) = S \cdot D + b $$

图像灰度非线性变换

  • 例如什么一元二次函数
  • 对数变换
  • 伽玛变换又称为指数变换或幂次变换,是另一种常用的灰度非线性变换。就是指数函数

图像锐化

展图像锐化和边缘检测处理,加强原图像的高频部分。锐化突出图像的边缘细节,改善图像的对比度,使模糊的图像变得更清晰。

图像锐化和边缘提取技术可以消除图像中的噪声,提取图像信息中用来表征图像的一些变量,为图像识别提供基础。

通常使用灰度差分法对图像的边缘、轮廓进行处理,将其凸显。

如果把每一种算法都用卷积核来表示成矩阵的乘法,那么实际上就是一种卷积运算了

Roberts算子(梯度法)

通过计算梯度从而凸显轮廓

对于图像 https://www.zhihu.com/equation?tex=f%28x%2Cy%29 ,在点 https://www.zhihu.com/equation?tex=%28x%2Cy%29 处的梯度是一个矢量,定义为https://raw.githubusercontent.com/kengerlwl/kengerlwl.github.io/refs/heads/master/image/84ec341a66fdbd14311e39fb5b266923/af44bf51d6163dce5848043bcc3b45a4.png

梯度的幅度表示为 https://raw.githubusercontent.com/kengerlwl/kengerlwl.github.io/refs/heads/master/image/84ec341a66fdbd14311e39fb5b266923/9b7105a5b1496426ed25b23465be24d8.png

对于数字图像而言, https://raw.githubusercontent.com/kengerlwl/kengerlwl.github.io/refs/heads/master/image/84ec341a66fdbd14311e39fb5b266923/6d4fdcb8e36ea49c8022f6639dfdc5e9.png

该式可以简化成 https://raw.githubusercontent.com/kengerlwl/kengerlwl.github.io/refs/heads/master/image/84ec341a66fdbd14311e39fb5b266923/862192f8ea6c8e974e6f350ff000ba2b.png

当梯度计算完之后,可以根据需要生成不同的梯度增强图像,

1)第一种, https://raw.githubusercontent.com/kengerlwl/kengerlwl.github.io/refs/heads/master/image/84ec341a66fdbd14311e39fb5b266923/f3ae902679c7e7dff01e3e0b560d1341.png ,只显示灰度变化大的边缘轮廓,灰度变化平缓的呈黑色。

2)第二种, https://raw.githubusercontent.com/kengerlwl/kengerlwl.github.io/refs/heads/master/image/84ec341a66fdbd14311e39fb5b266923/cd884a2879adad0a0b1472324bed828a.png

可以显示出非常明显的边缘轮廓,又不会破坏原灰度变化平缓的背景。

3)第三种, https://raw.githubusercontent.com/kengerlwl/kengerlwl.github.io/refs/heads/master/image/84ec341a66fdbd14311e39fb5b266923/97a3a607639fe7d14c1d78b2f41e5f0f.png

。。。。还有很多类似的

Sobel算子

采用梯度微分锐化图像,会让噪声、条纹得到增强,Sobel算子在一定程度上解决了这个问题, https://raw.githubusercontent.com/kengerlwl/kengerlwl.github.io/refs/heads/master/image/84ec341a66fdbd14311e39fb5b266923/41e073a71a215b55b008f0b760277548.png

从这个式子中,可以得到两个性质,

  • Sobel引入了平均的因素,因此对噪声有一定的平滑作用

  • Sobel算子的操作就是相隔两个行(列)的差分,所以边缘两侧元素的得到了增强,因此边缘显得粗而亮。

  • Sobel算子表示形式为:

    https://raw.githubusercontent.com/kengerlwl/kengerlwl.github.io/refs/heads/master/image/84ec341a66fdbd14311e39fb5b266923/0e35c51da6c8afd65cd5fce63b02fbf6.png

拉普拉斯算子(二阶微分)

拉普拉斯运算也是各向同性的线性运算。拉普拉斯算子为: https://www.zhihu.com/equation?tex=+%5Cnabla+%5E2f%3D%5Cfrac%7B%5Cpartial+%5E2f%7D%7B%5Cpartial+x%5E2%7D%2B%5Cfrac%7B%5Cpartial+%5E2f%7D%7B%5Cpartial+y%5E2%7D++ ,锐化之后的图像 https://raw.githubusercontent.com/kengerlwl/kengerlwl.github.io/refs/heads/master/image/84ec341a66fdbd14311e39fb5b266923/7acb21174b6f45d337bd6545f59ded57.png

https://raw.githubusercontent.com/kengerlwl/kengerlwl.github.io/refs/heads/master/image/84ec341a66fdbd14311e39fb5b266923/a2b2713bb60871f3f1ec41c2aa6b28b1.png 为扩散效应的系数。

https://raw.githubusercontent.com/kengerlwl/kengerlwl.github.io/refs/heads/master/image/84ec341a66fdbd14311e39fb5b266923/109971bec2577b9104960b127601fe00.png

https://raw.githubusercontent.com/kengerlwl/kengerlwl.github.io/refs/heads/master/image/84ec341a66fdbd14311e39fb5b266923/d3c44e73ed44a2e0a465c4f92926048d.png

由此式可知,数字图像在 https://raw.githubusercontent.com/kengerlwl/kengerlwl.github.io/refs/heads/master/image/84ec341a66fdbd14311e39fb5b266923/52c725820821186c715be2460f505135.png 点的拉普拉斯算子,可以由该点的灰度值减去该点及其邻域四个点的平均灰度值求得。

Canny算子

1.使用高斯平滑(如公式所示)去除噪声。

https://raw.githubusercontent.com/kengerlwl/kengerlwl.github.io/refs/heads/master/image/84ec341a66fdbd14311e39fb5b266923/2b12e73be69e86eb73254448684195e8.png

2.按照Sobel滤波器步骤计算梯度幅值和方向,寻找图像的强度梯度。先将卷积模板分别作用x和y方向,再计算梯度幅值和方向,其公式如下所示。梯度方向一般取0度、45度、90度和135度四个方向。

https://raw.githubusercontent.com/kengerlwl/kengerlwl.github.io/refs/heads/master/image/84ec341a66fdbd14311e39fb5b266923/5b62600374d0cce9e566e19e6aa002fa.png

3.通过非极大值抑制(Non-maximum Suppression)过滤掉非边缘像素,将模糊的边界变得清晰。该过程保留了每个像素点上梯度强度的极大值,过滤掉其他的值。

4.利用滞后技术来跟踪边界。若某一像素位置和强边界相连的弱边界认为是边界,其他的弱边界则被删除。

边缘线检测

边缘检测算法主要是基于图像强度的一阶和二阶导数,但导数通常对噪声很敏感,因此需要采用滤波器来过滤噪声,并调用图像增强或阈值化算法进行处理最后再进行边缘检测

而所谓边缘检测,其实是用锐化算法算出边缘,然后通过一些细节处理捕捉边缘。