目录
- 一、图像
- 1、像素
- 2、图像分辨率
- 3、RGB 模型
- 4、灰度
- 5、通道
- 6、对比度
- 7、RGB 转化为 Gray
- 8、RGB 值转化为浮点数
- 9、二值化
- 10、常用视觉库
- 11、频率
- 12、幅值
- 二、图像的取样与量化
- 1、数字图像
- 2、取样
- 3、量化
- 三、上采样与下采样
- 1、上采样(upsampling)
- 2、下采样(subsampled)
- 3、插值方法
- 1、最邻近插值
- 2、 双线性插值
- 四、直方图
- 1、概念
- 2、性质
- 3、应用
- 4、均衡化
- 1、概念
- 2、方法
- 3、公式
- 五、滤波和卷积
- 1、概念
- 2、滤波器/过滤器/卷积核/Kernel
- 3、卷积
- 1、步长/Stride
- 2、填充/Pading
- 3、多通道卷积
一、图像
1、像素
像素是分辨率的单位。
像素是构成位图图像最基本的单元,每个像素都有自己的颜色。
2、图像分辨率
图像分辨率就是单位英寸内的像素点数。
- 单位为 PPI(Pixels Per Inch),通常叫做像素每英寸。
- PPI 表示的是每英寸对角线上所拥有的的像素数目。
P P I = ( 水平分辨率 ) 2 + ( 垂直分辨率 ) 2 对角线尺寸 PPI = \frac{\sqrt{(\text{水平分辨率})^2 + (\text{垂直分辨率})^2}}{\text{对角线尺寸}} PPI=对角线尺寸(水平分辨率)2+(垂直分辨率)2
- 屏幕尺寸指的是对角线长度。
3、RGB 模型
色彩三原色(CMYK):品红、黄、青
光学三原色(RGB):红、绿、蓝
RGB 颜色模型是三维直角坐标颜色系统中的一个单位正方体。
在正方体的主对角线上,各原色的量相等,产生由暗到亮的白色,即灰度。
正方体的其它6个角点分别为红、黄、绿、青、蓝和品红。
4、灰度
表示图像像素明暗程度的数值,也就是黑白图像中点的颜色深度。
范围一般为0-255。白色为255,黑色为0。
5、通道
把图像分解成一个或多个颜色成分。
-
单通道:一个像素点只需一个数值表示,只能表示灰度,0为黑色。(二值图&灰度图)
-
三通道:RGB 模式,把图像分为红绿蓝三个通道,可以表示彩色,全0表示黑色。
-
四通道:RGBA 模式,在 RGB 基础上加上 alpha 通道,表示透明度,alpha=0 表示全透明。
6、对比度
指不同颜色之间的差别。
对比度 = 最大灰度值/最小灰度值
7、RGB 转化为 Gray
- 浮点算法:Gray = R3.0+G0.59+B0.11
- 整数算法:Gray = (R30+G59+B11)/100
- 移位算法:Gray = (R76+G151+B28)>>8
- 平均算法:Gray = (R+G+B)/3
- 仅取绿色:Gray = G
为什么很多图像识别将彩色图像灰度化?
对颜色不敏感的需求将彩色图像灰度化可以降低计算成本,减少复杂性,节省存储空间。
8、RGB 值转化为浮点数
浮点数运算结果更精确,整数运算中会因丢弃小数部分可能导致颜色值严重失真,计算过程越多越失真。
将 RGB 值转化为0-1的浮点数:x/255 即可。
9、二值化
将图像转换为只包含两个像素值的过程。
10、常用视觉库
opencv:安装使用 pip install opencv-python,使用时用 import cv2
matplotlib:安装使用 pip install matplotlib,使用时用 import matplotlib.pyplot as plt
skimage:安装使用 pip install scikit-image,使用时用 import skimage
注意:
opencv 对于读进来的图片的通道排列是 BGR,而不是主流的 RGB!
opencv 读入的矩阵是 BGR,转为 RGB 方法:
img = cv2.imread(‘1.jpg’)
img = cv2.cvtColor(img,cv2.COLOR_BGR2RGB)
代码实现
- 图片灰度化(方式一)
import cv2 # 导入 OpenCV 库,用于图像处理
import numpy as np # 导入 NumPy 库,用于数组操作
# 读取图片
img = cv2.imread("img/lenna.png")
# 获取图片的 high 和 wide
h, w = img.shape[:2]
# 创建一张和当前图片大小一样的单通道图片
img_gray = np.zeros([h, w], img.dtype)
# 遍历图片的每一个像素
for i in range(h):
for j in range(w):
# 取出当前 high 和 wide 中的 BGR 坐标
m = img[i, j]
# 将 BGR 坐标转化为 gray 坐标并赋值给新图像
img_gray[i, j] = int(m[0] * 0.11 + m[1] * 0.59 + m[2] * 0.3)
# 打印灰度图像的值
print(img_gray)
# [[162 162 162 ... 169 155 128]
# [162 162 162 ... 169 155 128]
# [162 162 162 ... 169 155 128]
# ...
# [ 42 42 49 ... 104 100 98]
# [ 43 43 54 ... 103 105 108]
# [ 43 43 54 ... 103 105 108]]
# 在窗口中显示灰度图像
cv2.imshow("image show gray", img_gray)
# 0 表示一直等待,直到按下任意键
cv2.waitKey(0)
# 关闭所有窗口
cv2.destroyAllWindows()
- 图片灰度化(方式二)
import matplotlib.pyplot as plt # 导入 Matplotlib 库,用于绘制图表
from skimage.color import rgb2gray # 从 skimage.color 模块中导入 rgb2gray 函数
# import cv2 # 导入 OpenCV 库,用于图像处理
# 在2x2的图表中,选中第1个子图
plt.subplot(221)
# 读取图片并将其存储在变量 img 中
img = plt.imread("img/lenna.png")
# 也可以使用 OpenCV 库读取图片
# img = cv2.imread("img/lenna.png", False)
# 在当前选中的子图中显示图片
plt.imshow(img)
# 打印提示信息
print("---image lenna----")
# 打印图像的数值表示
print(img)
# [[[0.8862745 0.5372549 0.49019608]
# [0.8862745 0.5372549 0.49019608]
# [0.8745098 0.5372549 0.52156866]
# ...
# [0.9019608 0.5803922 0.47843137]
# [0.8666667 0.50980395 0.43137255]
# [0.78431374 0.3882353 0.3529412 ]]
#
# [[0.8862745 0.5372549 0.49019608]
# [0.8862745 0.5372549 0.49019608]
# [0.8745098 0.5372549 0.52156866]
# ...
# [0.9019608 0.5803922 0.47843137]
# [0.8666667 0.50980395 0.43137255]
# [0.78431374 0.3882353 0.3529412 ]]
#
# [[0.8862745 0.5372549 0.49019608]
# [0.8862745 0.5372549 0.49019608]
# [0.8745098 0.5372549 0.52156866]
# ...
# [0.9019608 0.5803922 0.47843137]
# [0.8666667 0.50980395 0.43137255]
# [0.78431374 0.3882353 0.3529412 ]]
#
# ...
#
# [[0.32941177 0.07058824 0.23529412]
# [0.32941177 0.07058824 0.23529412]
# [0.36078432 0.10588235 0.22745098]
# ...
# [0.6784314 0.28627452 0.32941177]
# [0.6745098 0.26666668 0.29803923]
# [0.69411767 0.24313726 0.30980393]]
#
# [[0.32156864 0.08627451 0.22352941]
# [0.32156864 0.08627451 0.22352941]
# [0.3764706 0.1254902 0.24313726]
# ...
# [0.7019608 0.27450982 0.30980393]
# [0.70980394 0.2784314 0.31764707]
# [0.7254902 0.2901961 0.31764707]]
#
# [[0.32156864 0.08627451 0.22352941]
# [0.32156864 0.08627451 0.22352941]
# [0.3764706 0.1254902 0.24313726]
# ...
# [0.7019608 0.27450982 0.30980393]
# [0.70980394 0.2784314 0.31764707]
# [0.7254902 0.2901961 0.31764707]]]
# 将彩色图像转换为灰度图像
img_gray = rgb2gray(img)
# 也可以使用 OpenCV 库的函数实现灰度转换
# img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 或者直接使用原始图像(未经灰度转换)
# img_gray = img
# 在2x2的图表中,选中第2个子图
plt.subplot(222)
# 在当前选中的子图中显示灰度图像,使用灰度的颜色映射(cmap)为 'gray'
plt.imshow(img_gray, cmap='gray')
# 打印提示信息
print("---image gray----")
# 打印灰度图像的数值表示
print(img_gray)
# [[0.60802865 0.60802865 0.60779065 ... 0.6413741 0.57998234 0.46985728]
# [0.60802865 0.60802865 0.60779065 ... 0.6413741 0.57998234 0.46985728]
# [0.60802865 0.60802865 0.60779065 ... 0.6413741 0.57998234 0.46985728]
# ...
# [0.13746354 0.13746354 0.16881412 ... 0.37271804 0.35559532 0.34377727]
# [0.14617059 0.14617059 0.1873059 ... 0.36788785 0.3729255 0.3846753 ]
# [0.14617059 0.14617059 0.1873059 ... 0.36788785 0.3729255 0.3846753 ]]
# 显示 Matplotlib 的图表
plt.show()
- 图片二值化
from skimage.color import rgb2gray # 从 skimage.color 模块中导入 rgb2gray 函数
import numpy as np # 导入 NumPy 库,用于数组操作
import matplotlib.pyplot as plt # 导入 Matplotlib 库,用于绘制图表
# 在2x2的图表中,选中第1个子图
plt.subplot(221)
# 读取图片
img = plt.imread("img/lenna.png")
# 也可以使用 OpenCV 库读取图像
# img = cv2.imread("img/lenna.png", False)
# 在当前选中的子图中显示图片
plt.imshow(img)
# 打印提示信息
print("---image lenna----")
# 打印图像的数值表示
print(img)
# [[[0.8862745 0.5372549 0.49019608]
# [0.8862745 0.5372549 0.49019608]
# [0.8745098 0.5372549 0.52156866]
# ...
# [0.9019608 0.5803922 0.47843137]
# [0.8666667 0.50980395 0.43137255]
# [0.78431374 0.3882353 0.3529412 ]]
#
# [[0.8862745 0.5372549 0.49019608]
# [0.8862745 0.5372549 0.49019608]
# [0.8745098 0.5372549 0.52156866]
# ...
# [0.9019608 0.5803922 0.47843137]
# [0.8666667 0.50980395 0.43137255]
# [0.78431374 0.3882353 0.3529412 ]]
#
# [[0.8862745 0.5372549 0.49019608]
# [0.8862745 0.5372549 0.49019608]
# [0.8745098 0.5372549 0.52156866]
# ...
# [0.9019608 0.5803922 0.47843137]
# [0.8666667 0.50980395 0.43137255]
# [0.78431374 0.3882353 0.3529412 ]]
#
# ...
#
# [[0.32941177 0.07058824 0.23529412]
# [0.32941177 0.07058824 0.23529412]
# [0.36078432 0.10588235 0.22745098]
# ...
# [0.6784314 0.28627452 0.32941177]
# [0.6745098 0.26666668 0.29803923]
# [0.69411767 0.24313726 0.30980393]]
#
# [[0.32156864 0.08627451 0.22352941]
# [0.32156864 0.08627451 0.22352941]
# [0.3764706 0.1254902 0.24313726]
# ...
# [0.7019608 0.27450982 0.30980393]
# [0.70980394 0.2784314 0.31764707]
# [0.7254902 0.2901961 0.31764707]]
#
# [[0.32156864 0.08627451 0.22352941]
# [0.32156864 0.08627451 0.22352941]
# [0.3764706 0.1254902 0.24313726]
# ...
# [0.7019608 0.27450982 0.30980393]
# [0.70980394 0.2784314 0.31764707]
# [0.7254902 0.2901961 0.31764707]]]
# 使用 rgb2gray 函数将彩色图像转换为灰度图像
img_gray = rgb2gray(img)
# rows, cols = img_gray.shape # 获取灰度图的行数和列数
# for i in range(rows): # 遍历灰度图的每一行
# for j in range(cols): # 遍历灰度图的每一列
# if (img_gray[i, j] <= 0.5): # 如果当前像素值小于等于0.5
# img_gray[i, j] = 0 # 将像素值设为0
# else:
# img_gray[i, j] = 1 # 否则将像素值设为1
# 使用 NumPy 的 where 函数实现二值化,大于等于0.5的像素值设为1,小于0.5的像素值设为0
img_binary = np.where(img_gray >= 0.5, 1, 0)
# 打印提示信息
print("-----imge_binary------")
# 打印二值化后的图像数组
print(img_binary)
# [[1 1 1 ... 1 1 0]
# [1 1 1 ... 1 1 0]
# [1 1 1 ... 1 1 0]
# ...
# [0 0 0 ... 0 0 0]
# [0 0 0 ... 0 0 0]
# [0 0 0 ... 0 0 0]]
# 打印二值化后的图像形状
print(img_binary.shape)
# (512, 512)
# 在2x2的图表中,选中第2个子图
plt.subplot(222)
# 在当前选中的子图中显示二值化后的图像,使用灰度的颜色映射(cmap)为 'gray'
plt.imshow(img_binary, cmap='gray')
# 显示 Matplotlib 的图表
plt.show()
11、频率
灰度值变化剧烈程度的指标,是灰度在平面空间上的梯度(高频、低频)。
12、幅值
幅值是在一个周期内,交流电瞬时出现的最大绝对值,也是一个正弦波,波峰到波谷的距离的一半。
二、图像的取样与量化
1、数字图像
计算机保存的图像都是一个一个的像素点,称为数字图像。
图像数字化过程由图像的取样与量化来完成。
2、取样
就是要用多少点来描述一幅图像,取样结果质量的高低就是用图像的分辨率来衡量的。
3、量化
是指要使用多大范围的数值来表示图像采样之后的一个点。
数字化坐标值称为取样,
数字化幅度值称为量化。
在取样时,若横向的像素数(列数)为 M ,纵向的像素数(行数)为 N,则图像总像素数为 M*N 个像素。
尺寸一致的情况下,像素点越多,图像越精致,像素点越少,图像越粗糙。
三、上采样与下采样
1、上采样(upsampling)
放大图像(或称为图像插值(interpolating))的主要目的是放大原图像,从而可以显示在更高分辨率的显示设备上。
原理:内插值
2、下采样(subsampled)
缩小图像(或称为降采样(downsampled))的主要目的有两个:1、使得图像符合显示区域的大小。2、生成对应图像的缩略图。
原理:(M/s)*(N/s)
3、插值方法
本质是求一个不存在的像素点的像素值,不一定是加或者减。
1、最邻近插值
(i,j),(i+1,j),(i,j+1),(i+1,j+1) 为原始图像上的四个像素点,如果想在这四个像素点中间插入一个像素点,插入的像素点落在 A,B,C,D 哪个区域内,那么,插入的像素点的像素值就等同于离得最近的那个像素点的像素值。
import cv2 # 导入 OpenCV 库,用于图像处理
import numpy as np # 导入 NumPy 库,用于数组操作
def function(img):
# 获取图像的高度、宽度和通道数
height, width, channels = img.shape
# 创建一个空白图像,尺寸为(800, 800),通道数与原图一致
emptyImage = np.zeros((800, 800, channels), np.uint8)
# 计算高度的缩放比例
sh = 800 / height
# 计算宽度的缩放比例
sw = 800 / width
# 遍历图片的每一个像素
for i in range(800):
for j in range(800):
# 计算新图像中的横坐标 x,使用最近邻插值,int() 转为整型,+0.5 是为了实现向最近的整数四舍五入
x = int(i / sh + 0.5)
# 计算新图像中的纵坐标 y,使用最近邻插值,int() 转为整型,+0.5 是为了实现向最近的整数四舍五入
y = int(j / sw + 0.5)
# 对新图像进行像素赋值
emptyImage[i, j] = img[x, y]
# 返回缩放后的图像
return emptyImage
# 读取图像
img = cv2.imread("img/lenna.png")
# 调用缩放函数进行图像缩放
zoom = function(img)
# 打印缩放后的图像数组
print(zoom)
# [[[125 137 226]
# [125 137 226]
# [125 137 226]
# ...
# [110 130 221]
# [ 90 99 200]
# [ 90 99 200]]
#
# [[125 137 226]
# [125 137 226]
# [125 137 226]
# ...
# [110 130 221]
# [ 90 99 200]
# [ 90 99 200]]
#
# [[125 137 226]
# [125 137 226]
# [125 137 226]
# ...
# [110 130 221]
# [ 90 99 200]
# [ 90 99 200]]
#
# ...
#
# [[ 57 22 82]
# [ 57 22 82]
# [ 57 22 82]
# ...
# [ 81 71 181]
# [ 81 74 185]
# [ 81 74 185]]
#
# [[ 57 22 82]
# [ 57 22 82]
# [ 57 22 82]
# ...
# [ 81 71 181]
# [ 81 74 185]
# [ 81 74 185]]
#
# [[ 57 22 82]
# [ 57 22 82]
# [ 57 22 82]
# ...
# [ 81 71 181]
# [ 81 74 185]
# [ 81 74 185]]]
# 打印缩放后的图像形状
print(zoom.shape)
# (800, 800, 3)
# 在窗口中显示缩放后的图像(最近邻插值)
cv2.imshow("nearest interp", zoom)
# 在窗口中显示原始图像
cv2.imshow("image", img)
# 等待按键事件
cv2.waitKey(0)
优点:简单粗暴易用。
缺点:精度不高。
对精度要求不高时可以使用。
2、 双线性插值
f(i+u, j+v) = (1-u) * (1-v) * f(i, j) + (1-u) * v * f(i, j+1) + u * (1-v) * f(i+1, j) + u * v * f(i+1, j+1)
- 在两点之间插值
- 在四点之间插值(双线性插值)
由于图像双线性插值只会用相邻的4个点,因此上述公式的分母都是1。
- 存在的问题
要通过双线性插值的方法算出目标图像(dst)中的每一个像素点的像素值,是通过目标图像(dst)像素点的坐标对应到源图像(src)图像当中的坐标,然后通过双线性插值的方法算出源图像(src)中相应坐标的像素值。
按比例对应
SrcX =(dstX)*(srcWidth/dstWidth)
SrcY =(dstY)*(srcHeight/dstHeight)
如果源图像和目标图像的原点(0,0)均选择左上角,然后根据插值公式计算目标图像每点像素。
假设需要将一幅 5 * 5 的图像缩小成 3 * 3 的图像,那么源图像和目标图像各个像素点之间的对应关系如下:
这样会使右下角的图片信息缺失,所以,最好的方法就是,两个图像的几何中心重合,并且目标图像的每个像素之间都是等间隔的,都和两边有一定的边距。
SrcX + 0.5 =(dstX + 0.5)*(srcWidth/dstWidth)
SrcY + 0.5 =(dstY + 0.5)*(srcHeight/dstHeight)
证明几何中心对称重合为什么要加0.5
源图像有 M × M 个像素点,目标图像有 N × N 个像素点 目标图像在源图像坐标系位置为 ( x , y ) 源图像坐标为 ( x m , y m ) m = 0 , 1 , . . . , M − 1 几何中心点为 ( x M − 1 2 , y M − 1 2 ) 目标图像坐标为 ( x n , y n ) n = 0 , 1 , . . . , N − 1 几何中心点为 ( x N − 1 2 , y N − 1 2 ) x = n M N ⇒ 使几何中心相同 M − 1 2 + z = ( N − 1 2 + z ) M N z = 1 2 源图像有 M × M 个像素点,目标图像有 N × N 个像素点\\ 目标图像在源图像坐标系位置为(x,y)\\ 源图像坐标为(x_m,y_m)\quad m=0,1,...,M-1\quad几何中心点为(x_\frac{M-1}{2},y_\frac{M-1}{2})\\ 目标图像坐标为(x_n,y_n)\quad n=0,1,...,N-1\quad几何中心点为(x_\frac{N-1}{2},y_\frac{N-1}{2})\\ x=n\frac{M}{N} \Rightarrow 使几何中心相同\\ \frac{M-1}{2}+z=(\frac{N-1}{2}+z)\frac{M}{N}\\ z=\frac{1}{2} 源图像有M×M个像素点,目标图像有N×N个像素点目标图像在源图像坐标系位置为(x,y)源图像坐标为(xm,ym)m=0,1,...,M−1几何中心点为(x2M−1,y2M−1)目标图像坐标为(xn,yn)n=0,1,...,N−1几何中心点为(x2N−1,y2N−1)x=nNM⇒使几何中心相同2M−1+z=(2N−1+z)NMz=21
import cv2 # 导入 OpenCV 库,用于图像处理
import numpy as np # 导入 NumPy 库,用于数组操作
def bilinear_interpolation(img, out_dim):
# 获取输入图像的高度、宽度和通道数
src_h, src_w, channel = img.shape
# 获取输出图像的高度和宽度
dst_h, dst_w = out_dim[1], out_dim[0]
# 打印输入图像的高度、宽度
print("src_h, src_w = ", src_h, src_w)
# src_h, src_w = 512 512
# 打印输出图像的高度、宽度
print("dst_h, dst_w = ", dst_h, dst_w)
# dst_h, dst_w = 700 700
# 如果输入图像和输出图像尺寸相同
if src_h == dst_h and src_w == dst_w:
# 则直接返回输入图像的副本
return img.copy()
# 创建一个空白的输出图像,尺寸为(dst_h, dst_w, 3)
dst_img = np.zeros((dst_h, dst_w, 3), dtype=np.uint8)
# 计算在水平和垂直方向上的缩放比例
scale_x, scale_y = float(src_w) / dst_w, float(src_h) / dst_h
# 对输出图像的每个通道进行插值
for i in range(3):
# 对输出图像的每个像素进行插值
for dst_y in range(dst_h):
for dst_x in range(dst_w):
# 计算在输入图像中的对应位置
src_x = (dst_x + 0.5) * scale_x - 0.5
src_y = (dst_y + 0.5) * scale_y - 0.5
# 计算在输入图像中的四个相邻像素的坐标
src_x0 = int(np.floor(src_x))
src_x1 = min(src_x0 + 1, src_w - 1)
src_y0 = int(np.floor(src_y))
src_y1 = min(src_y0 + 1, src_h - 1)
# 使用双线性插值计算输出图像中当前像素的值
temp0 = (src_x1 - src_x) * img[src_y0, src_x0, i] + (src_x - src_x0) * img[src_y0, src_x1, i]
temp1 = (src_x1 - src_x) * img[src_y1, src_x0, i] + (src_x - src_x0) * img[src_y1, src_x1, i]
dst_img[dst_y, dst_x, i] = int((src_y1 - src_y) * temp0 + (src_y - src_y0) * temp1)
# 返回双线性插值后的输出图像
return dst_img
if __name__ == '__main__':
# 读取图像文件
img = cv2.imread('img/lenna.png')
# 对图像进行双线性插值,将尺寸调整为(700, 700)
dst = bilinear_interpolation(img, (700, 700))
# 显示双线性插值结果
cv2.imshow('bilinear interp', dst)
# 等待用户按任意键,如果没有这一句,窗口会立即关闭
cv2.waitKey()
优点:精度高,图像看起来更光滑。
缺点:计算复杂,计算量较大。
四、直方图
1、概念
直方图是图像处理中一种用于描述图像像素值分布的工具。主要用于分析图像的亮度、对比度和颜色分布。包括颜色直方图、灰度直方图等。
灰度直方图
图像的灰度直方图就描述了图像中灰度分布情况,能够很直观的展示出图像中各个灰度级所占的多少。
图像的灰度直方图是灰度级的函数,描述的是图像中具有该灰度级的像素的个数。其中,横坐标是灰度级,纵坐标是该灰度级出现的频率。
2、性质
- 直方图反映了图像中的灰度分布规律。它描述每个灰度级具有的像素个数,但不包含这些像素在图像中的位置信息。
- 图像直方图不关心像素所处的空间位置,因此,不受图像旋转和平移变化的影响,可以作为图像的特征。
- 任何一幅特定的图像都有唯一的直方图与之对应,但不同的图像可以有相同的直方图。
- 如果一幅图像有两个不相连的区域组成,并且每个区域的直方图已知,则整幅图像的直方图是该两个区域的直方图之和。
3、应用
灰度图像的直方图
方法一:使用 Matplotlib 绘制直方图
import cv2 # 导入 OpenCV 库,用于图像处理
from matplotlib import pyplot as plt # 从 Matplotlib 库导入绘图模块 plt
# 读取彩色图像
img = cv2.imread("img/lenna.png", 1)
# 将彩色图像转换为灰度图像
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 显示灰度图像
# cv2.imshow("image_gray", gray)
# 创建一个新的图像
plt.figure()
# 绘制直方图
plt.hist(gray.ravel(), 256)
# 显示直方图
plt.show()
方法二:使用 OpenCV 的 calcHist 函数绘制直方图
import cv2 # 导入 OpenCV 库,用于图像处理
from matplotlib import pyplot as plt # 从 Matplotlib 库导入绘图模块 plt
'''
calcHist—计算图像直方图
函数原型:calcHist(images, channels, mask, histSize, ranges, hist=None, accumulate=None)
images:图像矩阵,例如:[image]
channels:通道数,例如:0
mask:掩膜,一般为:None
histSize:直方图大小,一般等于灰度级数
ranges:横轴范围
hist: 输出的直方图,默认为 None,创建一个空的直方图。如果传入一个已存在的直方图,会在已有的直方图上累积结果
accumulate: 默认为 None。如果设置为 True,函数会累积直方图的值。在处理多幅图像时,可以累积它们的直方图
'''
# 读取彩色图像
img = cv2.imread("img/lenna.png", 1)
# 将彩色图像转换为灰度图像
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 显示灰度图像
# cv2.imshow("image_gray", gray)
# 计算灰度图像的直方图
hist = cv2.calcHist([gray],[0],None,[256],[0,256])
# 创建一个新的图像
plt.figure()
# 设置图像标题
plt.title("Grayscale Histogram")
# 设置x轴标签
plt.xlabel("Bins")
# 设置y轴标签
plt.ylabel("# of Pixels")
# 绘制直方图
plt.plot(hist)
# 设置x轴范围
plt.xlim([0,256])
# 显示直方图
plt.show()
彩色图像的直方图
import cv2 # 导入 OpenCV 库,用于图像处理
from matplotlib import pyplot as plt # 从 Matplotlib 库导入绘图模块 plt
# 读取彩色图像
image = cv2.imread("img/lenna.png")
# 显示原始彩色图像
cv2.imshow("Original",image)
# 等待用户按键
cv2.waitKey(0)
# 分离彩色图像的通道
chans = cv2.split(image)
colors = ("b","g","r")
# 创建一个新的图像
plt.figure()
# 设置图像标题
plt.title("Flattened Color Histogram")
# 设置x轴标签
plt.xlabel("Bins")
# 设置y轴标签
plt.ylabel("# of Pixels")
# 遍历彩色通道和对应颜色
for (chan,color) in zip(chans,colors):
# 计算彩色通道的直方图
hist = cv2.calcHist([chan],[0],None,[256],[0,256])
# 绘制直方图
plt.plot(hist,color = color)
# 设置x轴范围
plt.xlim([0,256])
# 显示直方图
plt.show()
4、均衡化
1、概念
直方图均衡化是将原图像的直方图通过变换函数变为均匀的直方图,然后按均匀直方图修改原图像,从而获得一幅灰度分布均匀的新图像。
直方图均衡化就是用一定的算法使直方图大致平和的方法。
直方图均衡化的作用是图像增强。
2、方法
为了将原图像的亮度范围进行扩展,需要一个映射函数,将原图像的像素值均衡映射到新直方图中,这个映射函数有两个条件:
1、为了不打乱原有的顺序,映射后亮、暗的大小关系不能改变。
2、映射后必须在原有的范围内,比如(0-255)。
步骤
1、依次扫描原始灰度图像的每一个像素,计算出图像的灰度直方图 H。
2、计算灰度直方图的累加直方图。
3、根据累加直方图和直方图均衡化原理得到输入与输出之间的映射关系。
4、最后根据映射关系得到结果:dst(x,y) = H’(src(x,y)) 进行图像变换。
3、公式
1、对于输入图像的任意一个像素 p, p∈[0,255], 总能在输出图像里有对应的像素 q, q∈[0,255] 使得下面等式成立(输入和输出的像素总量相等):
累加直方图公式
∑
k
=
0
p
h
i
s
t
i
n
p
u
t
(
k
)
=
∑
k
=
0
q
h
i
s
t
i
o
u
t
(
k
)
累加直方图公式\\\sum_{k=0}^{p} hist_{input}(k) = \sum_{k=0}^{q} hist_{iout}(k)
累加直方图公式k=0∑phistinput(k)=k=0∑qhistiout(k)
2、其中,输出图像每个灰度级的个数:
h
i
s
t
i
o
u
t
(
k
)
≈
H
×
W
256
,
k
∈
[
0
,
255
]
hist_{iout}(k)\approx \frac{H \times W}{256} ,k \in [0,255]
histiout(k)≈256H×W,k∈[0,255]
3、代入累加直方图公式:
∑
k
=
0
p
h
i
s
t
i
n
p
u
t
(
k
)
≈
(
q
+
1
)
H
×
W
256
⇒
q
≈
∑
k
=
0
p
h
i
s
t
i
n
p
u
t
(
k
)
H
×
W
×
256
−
1
\sum_{k=0}^{p} hist_{input}(k)\approx (q+1)\frac{H \times W}{256}\Rightarrow q\approx\sum_{k=0}^{p}\frac{hist_{input}(k)}{H \times W}\times256-1
k=0∑phistinput(k)≈(q+1)256H×W⇒q≈k=0∑pH×Whistinput(k)×256−1
灰度图像直方图均衡化
import cv2 # 导入 OpenCV 库,用于图像处理
import numpy as np # 导入 NumPy 库,用于数组操作
from matplotlib import pyplot as plt # 从 Matplotlib 库导入绘图模块 plt
'''
equalizeHist—直方图均衡化
函数原型: equalizeHist(src, dst=None)
src:图像矩阵(单通道图像)
dst:默认即可
'''
# 读取彩色图像
img = cv2.imread("img/lenna.png", 1)
# 将彩色图像转换为灰度图像
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 显示灰度图像
# cv2.imshow("image_gray", gray)
# 对灰度图像进行直方图均衡化
dst = cv2.equalizeHist(gray)
# 计算均衡化后图像的直方图
hist = cv2.calcHist([dst], [0], None, [256], [0, 256])
# 使用 Matplotlib 绘制均衡化后图像的直方图
plt.figure()
# 绘制直方图,ravel()将图像矩阵展平为一维数组
plt.hist(dst.ravel(), 256)
# 显示直方图
plt.show()
# 将原始灰度图像和均衡化后的图像水平拼接
combined_img = np.hstack([gray, dst])
# 显示均衡化后的图像和直方图拼接图像
cv2.imshow("Histogram Equalization", combined_img)
# 等待用户按键
cv2.waitKey(0)
彩色图像直方图均衡化
import cv2 # 导入 OpenCV 库,用于图像处理
'''
equalizeHist—直方图均衡化
函数原型: equalizeHist(src, dst=None)
src:图像矩阵(单通道图像)
dst:默认即可
'''
# 读取彩色图像
img = cv2.imread("img/lenna.png", 1)
# 显示原始彩色图像
cv2.imshow("src", img)
# 分离彩色图像的通道(蓝、绿、红)
(b, g, r) = cv2.split(img)
# 对每个通道进行直方图均衡化
bH = cv2.equalizeHist(b)
gH = cv2.equalizeHist(g)
rH = cv2.equalizeHist(r)
# 合并均衡化后的通道,得到最终的彩色图像
result = cv2.merge((bH, gH, rH))
# 显示均衡化后的彩色图像
cv2.imshow("dst_rgb", result)
# 等待用户按键
cv2.waitKey(0)
五、滤波和卷积
1、概念
滤波
滤波是一种信号处理技术,线性滤波可以说是图像处理最基本的方法,其目的是通过一定的操作来改变信号的频率特性或者减弱信号中的某些成分。
在图像处理中,滤波通常用于去除图像中的噪声、平滑图像、增强图像特定的特征等。滤波操作可以通过一系列数学运算来实现。
卷积
卷积是一种数学运算,常被用于信号处理和图像处理中。
(
f
∗
g
)
(
t
)
=
∫
R
f
(
x
)
g
(
t
−
x
)
d
x
(f * g)(t) = \int_{R} f(x)g(t-x) \, dx
(f∗g)(t)=∫Rf(x)g(t−x)dx
一般称 g 为作用在 f 上的 filter 或 kernel
2、滤波器/过滤器/卷积核/Kernel
对于滤波器规则要求:
- 滤波器的大小应该是奇数,这样它才有中心和半径,例如 3x3,5x5 或者 7x7。5x5 大小的核的半径就是2。
- 滤波器矩阵所有的元素之和应该要等于1,这是为了保证滤波前后图像的亮度保持不变。但这不是硬性要求。
- 如果滤波器矩阵所有元素之和大于1,那么滤波后的图像就会比原图像更亮,反之,如果小于1,那么得到的图像就会变暗。如果和为0,图像不会变黑,但也会非常暗。
- 对于滤波后的结构,可能会出现负数或者大于255的数值。对这种情况,将它们直接截断到0和255之间即可。对于负数,也可以取绝对值。
在具体应用中,往往有多个卷积核,可以认为,每个卷积核代表了一种图像模式。
如果某个图像块与此卷积核卷积出的值大,则认为此图像块十分接近于此卷积核。
例如,如果设计了6个卷积核,可以理解为:我们认为这个图像上有6种底层纹理模式,也就是我们用6种基础模式就能描绘出一副图像。
下图为一张黑白相间的图片
有 Gx 和 Gy 两个卷积核
G
x
=
1
0
−
1
1
0
−
1
1
0
−
1
G
y
=
1
1
1
0
0
0
−
1
−
1
−
1
G_x = \begin{array}{|c|c|c|} \hline 1 & 0 & -1 \\ \hline 1 & 0 & -1 \\ \hline 1 & 0 & -1 \\ \hline \end{array}\quad G_y = \begin{array}{|c|c|c|} \hline 1 & 1 & 1 \\ \hline 0 & 0 & 0 \\ \hline -1 & -1 & -1 \\ \hline \end{array}
Gx=111000−1−1−1Gy=10−110−110−1
用 Gx 来卷积这张图的话,就会在纵向中间黑白边界处获得比较大的值,可以找到纵向的边界位置。
用 Gy 来卷积这张图的话,就会在横向中间黑白边界处获得比较大的值,可以找到横向的边界位置。
应用
1、一个没有任何效果的卷积
0
0
0
0
1
0
0
0
0
\begin{array}{|c|c|c|} \hline 0 & 0 & 0 \\ \hline 0 & 1 & 0 \\ \hline 0 & 0 & 0 \\ \hline \end{array}
000010000
将原像素中间像素值乘1,其余全部乘0。 显然像素值不会发生任何变化。
2、平滑均值滤波
1
9
1
9
1
9
1
9
1
9
1
9
1
9
1
9
1
9
\begin{array}{|c|c|c|} \hline \frac{1}{9} & \frac{1}{9} & \frac{1}{9} \\ \hline \frac{1}{9} & \frac{1}{9} & \frac{1}{9} \\ \hline \frac{1}{9} & \frac{1}{9} & \frac{1}{9} \\ \hline \end{array}
919191919191919191
取九个值的平均值代替中间像素值。起到平滑的效果。
3、高斯平滑
1
16
2
16
1
16
2
16
2
16
2
16
1
16
2
16
1
16
\begin{array}{|c|c|c|} \hline \frac{1}{16} & \frac{2}{16} & \frac{1}{16} \\ \hline \frac{2}{16} & \frac{2}{16} & \frac{2}{16} \\ \hline \frac{1}{16} & \frac{2}{16} & \frac{1}{16} \\ \hline \end{array}
161162161162162162161162161
高斯平滑水平和垂直方向呈现高斯分布,更突出了中心点在像素平滑后的权重,相比于均值滤波而言, 有着更好的平滑效果。
4、图像锐化
−
1
−
1
−
1
−
1
9
−
1
−
1
−
1
−
1
\begin{array}{|c|c|c|} \hline -1 & -1 & -1 \\ \hline -1 & 9 & -1 \\ \hline -1 & -1 & -1 \\ \hline \end{array}
−1−1−1−19−1−1−1−1
0 − 1 0 − 1 5 − 1 0 − 1 0 \begin{array}{|c|c|c|} \hline 0 & -1 & 0 \\ \hline -1 & 5 & -1 \\ \hline 0 & -1 & 0 \\ \hline \end{array} 0−10−15−10−10
图像锐化使用的是拉普拉斯变换核函数。
5、Soble 边缘检测
水平梯度卷积核
−
1
0
1
−
2
0
2
−
1
0
1
水平梯度卷积核\\ \begin{array}{|c|c|c|} \hline -1 & 0 & 1 \\ \hline -2 & 0 & 2 \\ \hline -1 & 0 & 1 \\ \hline \end{array}
水平梯度卷积核−1−2−1000121
垂直梯度卷积核 − 1 − 2 − 1 0 0 0 1 2 1 垂直梯度卷积核\\ \begin{array}{|c|c|c|} \hline -1 & -2 & -1 \\ \hline 0 & 0 & 0 \\ \hline 1 & 2 & 1 \\ \hline \end{array} 垂直梯度卷积核−101−202−101
Soble 更强调了和边缘相邻的像素点对边缘的影响。
3、卷积
卷积负责提取图像中的局部特征。
1、步长/Stride
如果用 (f, f) 的过滤器来卷积一张 (h, w) 大小的图片,每次移动一个像素的话,那么得出的结果就是 (h-f+1, w-f+1) 的输出结果。
f 是过滤器大小,h 和 w 分别是图片的高宽。 如果每次不止移动一个像素,而是 s 个像素,那么结果就会变为:
(
h
−
f
s
+
1
,
w
−
f
s
+
1
)
(\frac{h-f}{s}+1,\frac{w-f}{s}+1)
(sh−f+1,sw−f+1)
这个 s 就叫做步长。
存在的问题:
只要是 f 或 s 的值比1要大的话,那么每次卷积之后结果的长宽,要比卷积前小一些。
丢失信息。
2、填充/Pading
有了填充之后,每次卷积之后的图像大小:
(
h
−
f
s
+
1
,
w
−
f
s
+
1
)
⇒
(
h
−
f
+
2
p
s
+
1
,
w
−
f
+
2
p
s
+
1
)
(\frac{h-f}{s}+1,\frac{w-f}{s}+1) \Rightarrow (\frac{h-f+2p}{s}+1,\frac{w-f+2p}{s}+1)
(sh−f+1,sw−f+1)⇒(sh−f+2p+1,sw−f+2p+1)
此时如果想让高(宽)不变:
h
−
f
+
2
p
s
+
1
=
h
⇒
p
=
s
(
h
−
1
)
−
h
+
f
2
\frac{h-f+2p}{s}+1=h \Rightarrow p=\frac{s(h-1)-h+f}{2}
sh−f+2p+1=h⇒p=2s(h−1)−h+f
假设步长 s=1:
p = f − 1 2 p=\frac{f-1}{2} p=2f−1
填充模式
橙色部分为 image, 蓝色部分为 filter。
- full 模式
从 filter 和 image 刚相交开始做卷积。
- same 模式
filter 中心 (K) 与 image 的边角重合时, 开始做卷积。
- valid 模式
filter 全部在 image 里面时,进行卷积。
注意:
这里的 same 还有一个意思,卷积之后输出的 feature map 尺寸保持不变(相对于输入图片)。
当然,same 模式不代表完全输入输出尺寸一样,也跟卷积核的步长有关系。
same 模式也是最常见的模式,因为这种模式可以在卷积过程中让图的大小保持不变。
3、多通道卷积
-
卷积核数量(W0、W1,一共2个卷积核)= 输出通道数
-
每个卷积核代表一种特征,想要几种特征就设置几个卷积核
-
每个卷积核的通道数(W0 和 W1 都分别有3个通道)= 输入通道数(已知)
-
Bias:偏置(可以理解为加快收敛速度的一个参数),y=kx+b(bias)
记录学习过程,欢迎讨论交流,尊重原创,转载请注明出处~