【计算机视觉】三、图像处理——实验:图像去模糊和去噪、提取边缘特征

0. 实验环境

  本实验使用了PyTorch深度学习框架,相关操作如下:

代码语言:javascript
复制
conda create -n DL python==3.11
代码语言:javascript
复制
conda activate DL
代码语言:javascript
复制
conda install pytorch torchvision torchaudio pytorch-cuda=12.1 -c pytorch -c nvidia
代码语言:javascript
复制
conda install matplotlib
代码语言:javascript
复制
conda install pillow numpy

软件包

本实验版本

matplotlib

3.8.0

numpy

1.26.3

pillow

10.0.1

python

3.11.0

torch

2.1.2

torchaudio

2.1.2

torchvision

0.16.2

1. 理论基础

  二维卷积运算是信号处理和图像处理中常用的一种运算方式,当给定两个二维离散信号或图像

f(x, y)

g(x, y)

,其中

f(x, y)

表示输入信号或图像,

g(x, y)

表示卷积核。二维卷积运算可以表示为:

h(x, y) = \sum_{m}\sum_{n} f(m, n) \cdot g(x-m, y-n)

其中

\sum_{m}\sum_{n}

表示对所有

m, n

的求和,

h(x, y)

表示卷积后的输出信号或图像。

  在数学上,二维卷积运算可以理解为将输入信号或图像

f(x, y)

和卷积核

g(x, y)

进行对应位置的乘法,然后将所有乘积值相加得到输出信号或图像

h(x, y)

。这个过程可以用于实现一些信号处理和图像处理的操作,例如模糊、边缘检测、图像增强等。

详见:【深度学习】Pytorch 系列教程(七):PyTorch数据结构:2、张量的数学运算(5):二维卷积及其数学原理

1.1 滤波器(卷积核)

  在图像处理中,卷积经常作为特征提取的有效方法.一幅图像在经过卷积操作后得到结果称为特征映射(Feature Map)。图5.3给出在图像处理中几种常用的滤波器,以及其对应的特征映射.图中最上面的滤波器是常用的高斯滤波器,可以用来对图像进行平滑去噪;中间和最下面的滤波器可以用来提取边缘特征

代码语言:javascript
复制
# 高斯滤波~平滑去噪
conv_kernel1 = torch.tensor([[1/16, 1/8, 1/16],
                            [1/8, 1/4, 1/8],
                            [1/16, 1/8, 1/16]], dtype=torch.float).unsqueeze(0).unsqueeze(0)
# 提取边缘特征
conv_kernel2 = torch.tensor([[0, 1, 0],
                            [1, -4, 1],
                            [0, 1, 0]], dtype=torch.float).unsqueeze(0).unsqueeze(0)
conv_kernel3 = torch.tensor([[0, 1, 1],
                            [-1, 0, 1],
                            [-1, -1, 0]], dtype=torch.float).unsqueeze(0).unsqueeze(0)
print(conv_kernel1.size())
  • 上述均为3x3的单通道卷积核,需要拓展为四维张量(PyTorch就是这么设计的~)

1.2 PyTorch:卷积操作

代码语言:javascript
复制
def conv2d(img_tensor, conv_kernel):
    convolved_channels = []
    for i in range(3):
        channel_input = img_tensor[:, i, :, :]  # 取出每个通道的输入
        convolved = F.conv2d(channel_input, conv_kernel, padding=1)  
        convolved_channels.append(convolved)
# 合并各通道卷积后的结果
output = torch.cat(convolved_channels, dim=1)
# 将张量转换为NumPy数组,进而转换为图像
output_img = output.squeeze().permute(1, 2, 0).numpy().astype(np.uint8)
output_img = Image.fromarray(output_img)
return output_img</code></pre></div></div><h3 id="954728" name="2.-%E5%9B%BE%E5%83%8F%E5%A4%84%E7%90%86">2. 图像处理</h3><h4 id="954729" name="2.1-%E5%9B%BE%E5%83%8F%E8%AF%BB%E5%8F%96">2.1 图像读取</h4><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">img = Image.open(&#39;1.jpg&#39;)  

img = img.resize((128, 128)) # 调整图像大小

img_tensor = torch.tensor(np.array(img), dtype=torch.float).permute(2, 0, 1).unsqueeze(0)

print(img_tensor.shape)

  • 将图像转换为PyTorch张量:将通道顺序从HWC转换为CHW,并在第一个维度上增加一个维度卷积操作使用四维张量

2.2 查看通道

  本部分内容纯属没事儿闲的

代码语言:javascript
复制
img = Image.open('1.jpg')
img_tensor = torch.tensor(np.array(img), dtype=torch.float).permute(2, 0, 1).unsqueeze(0)
channel1 = img_tensor[:, 0, :, :] # 提取每个通道
channel2 = img_tensor[:, 1, :, :]
channel3 = img_tensor[:, 2, :, :]
plt.figure(figsize=(12, 12))
plt.subplot(1, 4, 1)
plt.imshow(img)
plt.axis('off')
plt.subplot(1, 4, 2)
channel1_img = channel1.squeeze().numpy().astype(np.uint8)
channel1_img = Image.fromarray(channel1_img)
plt.imshow(channel1_img)
plt.axis('off')
plt.subplot(1, 4, 3)
channel2_img = channel2.squeeze().numpy().astype(np.uint8)
channel2_img = Image.fromarray(channel2_img)
plt.imshow(channel2_img)
plt.axis('off')
plt.subplot(1, 4, 4)
channel3_img = channel3.squeeze().numpy().astype(np.uint8)
channel3_img = Image.fromarray(channel3_img)
plt.imshow(channel3_img)
plt.axis('off')

2.3 图像处理

代码语言:javascript
复制
def plot_img(img_tensor):
output_img1 = conv2d(img_tensor, conv_kernel1)
output_img2 = conv2d(img_tensor, conv_kernel2)
output_img3 = conv2d(img_tensor, conv_kernel3)

plt.subplot(2, 2, 1)
plt.title(&#39;原始图像&#39;, fontproperties=font)
plt.imshow(img)
plt.axis(&#39;off&#39;)  
plt.subplot(2, 2, 2)
plt.title(&#39;平滑去噪&#39;, fontproperties=font)
plt.imshow(output_img1)
plt.axis(&#39;off&#39;)  
plt.subplot(2, 2, 3)
plt.imshow(output_img2)
plt.title(&#39;边缘特征1&#39;, fontproperties=font)  
plt.axis(&#39;off&#39;)  
plt.subplot(2, 2, 4)
plt.imshow(output_img3)
plt.title(&#39;边缘特征2&#39;, fontproperties=font)  
plt.axis(&#39;off&#39;)  
plt.show()</code></pre></div></div><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">font = FontProperties(fname=&#39;C:\Windows\Fonts\simkai.ttf&#39;, size=16)  # 使用楷体

plt.figure(figsize=(12, 12)) # 设置图大小12*12英寸
plot_img(img_tensor)

  • 如图所示,图像提取边缘特征效果明显
  • 但图片过于高清,plt输出的(12英寸)原始图像、平滑去噪图像都很模糊~,下面会先降低像素,然后进行去模糊去噪实验
  • 原图为

3. 图像去模糊

代码语言:javascript
复制
img = Image.open('2.jpg')
img = img.resize((480, 480)) # 调小图像~先使原图变模糊
img_tensor = torch.tensor(np.array(img), dtype=torch.float).permute(2, 0, 1).unsqueeze(0)
conv_kernel4 = torch.tensor([[0, 0, 0],
[0, 2, 0],
[0, 0, 0]], dtype=torch.float).unsqueeze(0).unsqueeze(0)
conv_kernel5 = torch.ones(3, 3).unsqueeze(0).unsqueeze(0)/9

print(conv_kernel4-conv_kernel5)

font = FontProperties(fname='C:\Windows\Fonts\simkai.ttf', size=32)
plt.figure(figsize=(32, 32))
plt.subplot(2, 2, 1)
plt.title('原始图像', fontproperties=font)
plt.imshow(img)
plt.axis('off')
plt.subplot(2, 2, 2)
plt.title('线性滤波-2', fontproperties=font)
plt.imshow(conv2d(img_tensor, conv_kernel4))
plt.axis('off')
plt.subplot(2, 2, 3)
plt.imshow(conv2d(img_tensor, conv_kernel5))
plt.title('均值滤波器:模糊', fontproperties=font)
plt.axis('off')
plt.subplot(2, 2, 4)
plt.imshow(conv2d(img_tensor, conv_kernel4-conv_kernel5))
plt.title('锐化滤波器:强调局部差异', fontproperties=font)
plt.axis('off')
plt.show()

4. 图像去噪

4.1 添加随机噪点

代码语言:javascript
复制
img = Image.open('1.jpg')
img = img.resize((640, 640)) # 调小图像~先使原图变模糊
img_tensor = torch.tensor(np.array(img), dtype=torch.float).permute(2, 0, 1).unsqueeze(0)

noise = torch.randn_like(img_tensor) # 与图像相同大小的随机标准正态分布噪点
noisy_img_tensor = img_tensor + noise # 将噪点叠加到图像上
noisy_img = noisy_img_tensor.squeeze(0).permute(1, 2, 0).to(dtype=torch.uint8)
noisy_img = Image.fromarray(noisy_img.numpy())

4.2 图像去噪

代码语言:javascript
复制
# conv_kernel1 = torch.tensor([[1/16, 1/8, 1/16],

[1/8, 1/4, 1/8],

[1/16, 1/8, 1/16]], dtype=torch.float).unsqueeze(0).unsqueeze(0)

# 生成随机3x3高斯分布

random_gaussian = torch.randn(3, 3).unsqueeze(0).unsqueeze(0)

print(random_gaussian)

font = FontProperties(fname='C:\Windows\Fonts\simkai.ttf', size=32) # 使用楷体
plt.figure(figsize=(32, 32))
plt.subplot(1, 3, 1)
plt.title('原始图像', fontproperties=font)
plt.imshow(img)
plt.axis('off')
plt.subplot(1, 3, 2)
plt.title('噪点图像', fontproperties=font)
plt.imshow(noisy_img)
plt.axis('off')
plt.subplot(1, 3, 3)
plt.title('去噪图像', fontproperties=font)
plt.imshow(conv2d(noisy_img_tensor, conv_kernel1))
plt.axis('off')
plt.show()