图像的本质是一个矩阵, 矩阵中的一个点就是一个像素,如果像素大小为 1000 × 1000 1000 \times 1000 1000×1000,那么图像原本的宽和高都是1000;同时,为了丰富图像的色彩,加入了通道这一概念,通道一般有三个,由三原色组成:RGB 红色,绿色,蓝色;jpg图像格式就是三通道图像,而png有四个通道,最后一个通道表示透明程度;
视觉基础
图像基础
PIL中有九种不同模式。分别为1,L,P,RGB,RGBA,CMYK,YCbCr,I,F
模式 | 描述 | 补充 |
1 | 模式“1”为二值图像,非黑即白。但是它每个像素用8个BIT表示,0表示黑,255表示白 | |
L | 模式“L”为灰色图像,它的每个像素用8个BIT表示,0表示黑,255表示白,其他数字表示不同的灰度。 | 从模式“RGB”转换为“L”模式是按照下面的公式转换的: L = R ∗ 299 / 1000 + G ∗ 587 / 1000 + B ∗ 114 / 1000 L = R * 299/1000 + G * 587/1000+ B * 114/1000 L=R∗299/1000+G∗587/1000+B∗114/1000 |
P | 模式“P”为8位彩色图像,它的每个像素用8个BIT表示,其对应的彩色值是按照调色板查询出来的。 | |
RGBA | 模式“RGBA”为32位彩色图像,它的每个像素用32个BIT表示,其中24BIT表示红色、绿色和蓝色三个通道,另外8BIT表示ALPHA通道,即透明通道。 | |
CMYK | 模式“CMYK”为32位彩色图像,它的每个像素用32个BIT表示。模式“CMYK”就是印刷四分色模式,它是彩色印刷时采用的一种套色模式,利用色料的三原色混色原理,加上黑色油墨,共计四种颜色混合叠加,形成所谓“全彩印刷”。 | 四种标准颜色是:C:Cyan = 青色,又称为‘天蓝色’或是‘湛蓝’M:Magenta = 品红色,又称为‘洋红色’;Y:Yellow = 黄色;K:Key Plate(blacK) = 定位套版色(黑色)。 C = 255 − R C = 255 - R C=255−R M = 255 − G M = 255 - G M=255−G Y = 255 − B Y = 255 - B Y=255−B K = 0 K = 0 K=0 |
YCbCr | 模式“YCbCr”为24位彩色图像,它的每个像素用24个bit表示。YCbCr其中Y是指亮度分量,Cb指蓝色色度分量,而Cr指红色色度分量。人的肉眼对视频的Y分量更敏感,因此在通过对色度分量进行子采样来减少色度分量后,肉眼将察觉不到的图像质量的变化。 | 模式“RGB”转换为“YCbCr”的公式如下: Y = 0.257 ∗ R + 0.504 ∗ G + 0.098 ∗ B + 16 Y= 0.257*R+0.504*G+0.098*B+16 Y=0.257∗R+0.504∗G+0.098∗B+16 C b = − 0.148 ∗ R − 0.291 ∗ G + 0.439 ∗ B + 128 Cb = -0.148*R-0.291*G+0.439*B+128 Cb=−0.148∗R−0.291∗G+0.439∗B+128 C r = 0.439 ∗ R − 0.368 ∗ G − 0.071 ∗ B + 128 Cr = 0.439*R-0.368*G-0.071*B+128 Cr=0.439∗R−0.368∗G−0.071∗B+128 |
I | 模式“I”为32位整型灰色图像,它的每个像素用32个bit表示,0表示黑,255表示白,(0,255)之间的数字表示不同的灰度。在PIL中,从模式“RGB”转换为“I”模式是按照下面的公式转换的: | 从实验的结果看,模式“I”与模式“L”的结果是完全一样,只是模式“L”的像素是8bit,而模式“I”的像素是32bit。 |
F | 模式“F”为32位浮点灰色图像,它的每个像素用32个bit表示,0表示黑,255表示白,(0,255)之间的数字表示不同的灰度。 | 模式“F”与模式“L”的转换公式是一样的,都是RGB转换为灰色值的公式,但模式“F”会保留小数部分,如实验中的数据。 |
显示详细信息
转载自:Python图像处理库PIL中图像格式转换(一)_image .pixel-CSDN博客
卷积层:图像的中全连接层的优化
这里以单通道图像为例子,图像为 ,假设隐藏层为 ; 如果使用全连接,有:
这样首先有几个缺点:1. 参数量爆炸式增长; 2. 这相当于把矩阵展开成了向量,丢失了位置信息组成的局部信息;
图像有两个性质:一个是局部性,一个是平移不变性;假设我们需要识别一张图像,我们不可能去关注所有的像素,假设目标是一只马,我们关注的是:它有几条腿,几只眼睛,站着还是坐着等等;这些都是局部信息,从局部信息推导全局信息这是一只马;平移不变性表示我们在局部获取的信息目标是相同的,比如我们要看一只马有几条腿,我们是从局部一个一个查找腿,最后汇总局部的结果,一共有4条腿;
这里使 ,有:
添加局部性我们可以把 a, b 不设置为宽和高这种全局参数,改小一点,如改成 ,有:
由于平移不变性 应该与 无关,有 ,得到:
这就是卷积层的实质! 这里 W被称为卷积核或者过滤器,是该层唯一一个可学习的权重,因此参数量大大减少;
卷积核
卷积核是卷积过程的核心, 卷积核主要是垂直卷积核,水平卷积核;这两个核在不同算子下面不一样,算子总结:
垂直卷积核,水平卷积核能够检测图像的水平边缘,垂直边缘等;
上述图片的代码如下:
from PIL import Image import numpy as np import scipy as sp import pandas as pd import matplotlib.pyplot as plt # 导入图片 img = np.array(Image.open('demo.jpg').convert('L')) # 设置算子 scharr = np.array([[ -3-3j, 0-10j, +3 -3j], [-10+0j, 0+ 0j, +10 +0j], [ -3+3j, 0+10j, +3 +3j]]) prewitt = np.array([[ -1-1j, 0-1j, +1 -1j], [ -1+0j, 0+0j, +1 +0j], [ -1+1j, 0+1j, +1 +1j]]) # 计算卷积 new_imgs = [img] for in2 in [scharr, prewitt]: new_img = sp.signal.convolve2d(img, in2, boundary='fill', mode='same') new_imgs.append(new_img) # 作图 fig, axes = plt.subplots(1,3, figsize=(10,4)) axes[0].imshow(np.abs(new_imgs[0]), cmap='gray') axes[0].axis('off') axes[0].set_title('origin') axes[1].imshow(np.abs(new_imgs[1]), cmap='gray') axes[1].axis('off') axes[1].set_title('scharr') axes[2].imshow(np.abs(new_imgs[2]), cmap='gray') axes[2].axis('off') axes[2].set_title('prewitt') plt.show()
这里对二维卷积的 mode
, boundary
进行讨论
参数 | 解释 |
in1 | 输入的二维数组 |
in2 | 输入的二维数组,代表卷积核 |
mode | 卷积类型:“fulll”,“valid”,“same” |
boundary | 边界填充方式:“fill”,“warp”,“symm” |
fillvalue | 当boundary="fill"时,设置边界填充的方式,默认为0 |
mode的形象化如下图:
boundary的形象化如下图:
在神经网络中,还有一个参数叫步幅,默认步幅为1,这个很好理解,在计算卷积的时候,隔一个还是多个进行卷积计算,超出就不管了;
多通道图片进行卷积的话,相当于多了一个维度的卷积核,对应相加后再整体相加;
tf.keras中的卷积函数
详细可以看:
深入浅出理解转置卷积Conv2DTranspose_convtranspose2d-CSDN博客
Conv2d
tf.keras.layers.Conv2D(
filters,
kernel_size,
strides=(1, 1),
padding=‘valid’,
data_format=None,
dilation_rate=(1, 1),
groups=1,
activation=None,
use_bias=True,
kernel_initializer=‘glorot_uniform’,
bias_initializer=‘zeros’,
kernel_regularizer=None,
bias_regularizer=None,
activity_regularizer=None,
kernel_constraint=None,
bias_constraint=None,
**kwargs)
- filters:过滤器数量,对应输出的通道数
- kernel_size:过滤器的尺寸,好像网上大多数教程说2维卷积其尺寸是二维的,其实并不是:为了对通道数进行处理,过滤器的尺寸后面还加了一个维度,其维度值等于输入的通道数;加上filters,其真实维度为 [kernel_size_1, kernel_size_2, input_c, output_c]
- stride:步幅;
- padding:有两种取值,valid ,same。其意义和上面的一致;
- dilation:卷积核分散计算;
dilation具体形式如图:
输入维度
输出维度
Conv2DTranspose
tf.keras.layers.Conv2DTranspose(
filters,
kernel_size,
strides=(1, 1),
padding=‘valid’,
output_padding=None,
data_format=None,
dilation_rate=(1, 1),
activation=None,
use_bias=True,
kernel_initializer=‘glorot_uniform’,
bias_initializer=‘zeros’,
kernel_regularizer=None,
bias_regularizer=None,
activity_regularizer=None,
kernel_constraint=None,
bias_constraint=None,
**kwargs)
其参数和Conv2D基本一致,从矩阵的角度出发,卷积的实质还是矩阵运算,不过多了变形的步骤:
Conv2DTranspose 主要利用逆变换的方式
s=1, p=0, k=3 | s=2, p=0, k=3 | s=2, p=1, k=3 |
从图中可以看到,有些块被重复利用了,这会照成一种棋盘效应,图像中的某个部位要比其他部位颜色更深;这是上采样照成的一种负面效应,信息只是看起来增长了;
反卷积和棋盘伪影 — Deconvolution and Checkerboard Artifacts (distill.pub)
这里有两种解决办法:
- 使kernel size能够被stride整除;
前面关于棋盘效应产生的原因就提到了,当kernel size不能被stride整除时会出现这种问题,那一个直观的方法就是让它能够被整除。但是,有研究指出,即使这样也无法从根本避免这个问题,因为我们不知道网络学到了什么样的卷积权重,无法解决;
- 双线性插值+卷积;
这是一个有效的解决方法,双线性插值可以减小插值的像素和原输入像素值的大小差异,再进行卷积可以避免该问题产生,如下所示:
One approach is to make sure you use a kernel size that is divided by your stride, avoiding the overlap issue. This is equivalent to “sub-pixel convolution,” a technique which has recently had success in image super-resolution 8. However, while this approach helps, it is still easy for deconvolution to fall into creating artifacts.
Another approach is to separate out upsampling to a higher resolution from convolution to compute features. For example, you might resize the image (using nearest-neighbor interpolation or bilinear interpolation) and then do a convolutional layer. This seems like a natural approach, and roughly similar methods have worked well in image super-resolution (eg. 9).
两个概念:特征图,感受野;特征图指的是卷积层的输出;感受野指的是某层某一输出的值依赖于原输入的值的范围,可以得到随着卷积层变多,感受野越大;
池化层
池化层是下采样算法,其是为了解决图像特征太多导致过拟合的问题,同时可以降低模型的参数量;池化相当于用一个值去代表一个二维矩阵;
池化层分为局部池化和全局池化:局部池化是对图像某一局部用数值代替,全局池化是对图像用数值代替;
最大池化:用最大值去代替矩阵,平均池化:用平均值去代替矩阵;
tf.keras.layers.MaxPool2D(
pool_size=(2, 2),
strides=None,
padding=‘valid’,
data_format=None,
**kwargs,
)
tf.keras.layers.GlobalMaxPool2D(*args, **kwargs)
data_format 表示输入的维度的顺序,默认是 channels_last
,对应 ;还有一个 channels_first
对应 ,其他参数的参数意义和卷积一样;
现代经典网络
下面博客对经典卷积神经网络进行了总结:经典卷积神经网络总结_卷积神经网络报告-CSDN博客
DenseNet + 数据增强
这里以猫狗数据集为例子
导入数据
import matplotlib.pyplot as plt import tensorflow as tf (x_train, y_train), (x_test, y_test) = tf.keras.datasets.cifar10.load_data() # x_train.shape, y_train.shape, x_test.shape, y_test.shape # ((50000, 32, 32, 3), (50000, 1), (10000, 32, 32, 3), (10000, 1)) index_name = { 0:'airplane', 1:'automobile', 2:'bird', 3:'cat', 4:'deer', 5:'dog', 6:'frog', 7:'horse', 8:'ship', 9:'truck' }
建立数据集:
def process_data(img, label): img = tf.cast(img, tf.float32) / 255.0 img = tf.image.random_crop(img, [28,28,3]) img = tf.image.random_flip_up_down(img) img = tf.image.random_flip_left_right(img) img = tf.image.random_brightness(img, max_delta=0.5) return img, label batch_size = 64 AUTOTUNE = tf.data.experimental.AUTOTUNE train_data = tf.data.Dataset.from_tensor_slices((x_train, y_train)) test_data = tf.data.Dataset.from_tensor_slices((x_test, y_test)) train_data = train_data.map(process_data).batch(batch_size) test_data = test_data.map(process_data).batch(batch_size)
自定义层
class Conv_block(tf.keras.layers.Layer): def __init__(self, filters): super(Conv_block, self).__init__() self.bn = tf.keras.layers.BatchNormalization(axis=3, epsilon=1e-6) self.conv_1 = tf.keras.layers.Conv2D(filters, 3, padding='same') self.conv_2 = tf.keras.layers.Conv2D(filters, 1, padding='same') self.concat = tf.keras.layers.Concatenate() def call(self, x): x = self.bn(x) x = tf.keras.activations.relu(x) x1 = self.conv_1(x) x2 = self.conv_2(x) x = self.concat([x1, x2]) return x class Transition_block(tf.keras.layers.Layer): def __init__(self, filters, stride): super(Transition_block, self).__init__() self.bn = tf.keras.layers.BatchNormalization(axis=3, epsilon=1e-6) self.conv = tf.keras.layers.Conv2D(filters, 3, padding='same') self.maxpool = tf.keras.layers.MaxPooling2D(padding='same', strides=2) def call(self, x): x = self.bn(x) x = tf.keras.activations.relu(x) x = self.conv(x) x = self.maxpool(x) return x class Dense_block(tf.keras.layers.Layer): def __init__(self, filters, stride, blocks=3): super(Dense_block, self).__init__() self.conv_block_list = [Conv_block(filters) for _ in range(blocks)] self.transition_block = Transition_block(filters*2, stride) def call(self, x): for conv_block in self.conv_block_list: x = conv_block(x) x = self.transition_block(x) return x
建立模型:
class CustomModel(tf.keras.Model): def __init__(self, filters_list=[4,8,16]): super(CustomModel, self).__init__(self) self.dense_block_list = [Dense_block(filters, stride=2, blocks=3) for filters in filters_list] self.global_pool = tf.keras.layers.GlobalAveragePooling2D() self.dense_final = tf.keras.layers.Dense(10, activation='softmax') def call(self, x): for dense_block in self.dense_block_list: x = dense_block(x) x = self.global_pool(x) x = self.dense_final(x) return x model = CustomModel() model.compile( loss=tf.keras.losses.sparse_categorical_crossentropy, optimizer='adam', metrics=[tf.keras.metrics.sparse_categorical_accuracy] )
开始训练:
model.fit(train_data, epochs=10)
训练结果
Epoch 1/10 782/782 [==============================] - 13s 13ms/step - loss: 1.9584 - sparse_categorical_accuracy: 0.2602 - val_loss: 1.8581 - val_sparse_categorical_accuracy: 0.2886 Epoch 2/10 782/782 [==============================] - 10s 13ms/step - loss: 1.7564 - sparse_categorical_accuracy: 0.3421 - val_loss: 1.8680 - val_sparse_categorical_accuracy: 0.3110 Epoch 3/10 782/782 [==============================] - 10s 13ms/step - loss: 1.6269 - sparse_categorical_accuracy: 0.3994 - val_loss: 1.5945 - val_sparse_categorical_accuracy: 0.4117 Epoch 4/10 782/782 [==============================] - 10s 13ms/step - loss: 1.5092 - sparse_categorical_accuracy: 0.4506 - val_loss: 1.4653 - val_sparse_categorical_accuracy: 0.4673 Epoch 5/10 782/782 [==============================] - 9s 12ms/step - loss: 1.4051 - sparse_categorical_accuracy: 0.4891 - val_loss: 1.4340 - val_sparse_categorical_accuracy: 0.4818 Epoch 6/10 782/782 [==============================] - 10s 12ms/step - loss: 1.3170 - sparse_categorical_accuracy: 0.5240 - val_loss: 1.3246 - val_sparse_categorical_accuracy: 0.5269 Epoch 7/10 782/782 [==============================] - 10s 13ms/step - loss: 1.2470 - sparse_categorical_accuracy: 0.5515 - val_loss: 1.3144 - val_sparse_categorical_accuracy: 0.5209 Epoch 8/10 782/782 [==============================] - 10s 12ms/step - loss: 1.1866 - sparse_categorical_accuracy: 0.5733 - val_loss: 1.2512 - val_sparse_categorical_accuracy: 0.5572 Epoch 9/10 782/782 [==============================] - 10s 13ms/step - loss: 1.1420 - sparse_categorical_accuracy: 0.5896 - val_loss: 1.1930 - val_sparse_categorical_accuracy: 0.5681 Epoch 10/10 782/782 [==============================] - 10s 13ms/step - loss: 1.1046 - sparse_categorical_accuracy: 0.6040 - val_loss: 1.1394 - val_sparse_categorical_accuracy: 0.5952