

大家好!今天给大家梳理《数字图像处理》第 2 章的核心内容 —— 数字图像基础。这一章是整个数字图像处理的入门基石,涵盖了从视觉感知到图像数字化、像素关系、数学工具等核心知识点。全文搭配可直接运行的 Python 代码、效果对比图和详细注释,帮大家直观理解抽象概念,新手也能轻松上手!

人眼是图像感知的核心器官,核心结构包括:角膜→虹膜→瞳孔→晶状体→视网膜(含视锥 / 视杆细胞)。

人眼类似凸透镜成像系统:光线经晶状体聚焦后,在视网膜上形成倒立的实像,视网膜上的感光细胞将光信号转化为神经电信号,经视神经传递到大脑视觉皮层,大脑最终将倒立的像 “矫正” 为正立的视觉感知。
代码案例:模拟人眼亮度分辨力(亮度差感知)
import numpy as np
import matplotlib.pyplot as plt
# 设置matplotlib支持中文显示
plt.rcParams['font.sans-serif'] = ['SimHei'] # 黑体
plt.rcParams['axes.unicode_minus'] = False # 解决负号显示问题
# 生成背景亮度为100的图像,中间添加不同亮度差的矩形块
def simulate_brightness_resolution():
# 构建500x500的背景图像,亮度值100(灰度范围0-255)
background = np.ones((500, 500)) * 100
# 定义不同的亮度差(1%、2%、5%、10%)
diffs = [1, 2, 5, 10] # 对应100的1%、2%、5%、10%
colors = ['r', 'g', 'b', 'y']
labels = ['1%亮度差', '2%亮度差', '5%亮度差', '10%亮度差']
fig, ax = plt.subplots(1, 1, figsize=(8, 8))
ax.imshow(background, cmap='gray', vmin=0, vmax=255)
ax.axis('off')
# 在图像中绘制不同亮度差的矩形
y_start = 100
for i, diff in enumerate(diffs):
# 计算矩形的亮度值
rect_brightness = 100 + diff
# 绘制矩形:x范围200-300,y范围按顺序排列
rect = np.ones((80, 100)) * rect_brightness
background[y_start+i*80 : y_start+(i+1)*80, 200:300] = rect
ax.imshow(background, cmap='gray', vmin=0, vmax=255)
# 添加图例
for i, label in enumerate(labels):
ax.text(310, y_start+i*80+40, label, color=colors[i], fontsize=12)
plt.title('人眼亮度分辨力模拟(背景亮度100)', fontsize=14)
plt.show()
# 运行函数
simulate_brightness_resolution()
效果说明:运行后能看到,1% 亮度差的矩形几乎和背景融为一体(人眼难以分辨),2% 开始隐约可见,5% 以上清晰可辨,直观体现人眼的亮度分辨特性。

图像的本质是光的分布,可见光只是电磁波谱中很小的一段(波长 400nm-700nm):
公式(光的波长与频率关系):c=λ×f
其中:c为光速(3×108 m/s),λ为波长,f为频率。

图像采集的核心是将光信号转化为电信号,再转化为数字信号。常见的传感器类型如下:
单个光敏传感器,通过机械扫描(如旋转 / 平移)逐点采集图像,优点是精度高,缺点是速度慢(如早期传真机、高精度光谱仪)。
由一行传感器组成,通过物体 / 传感器移动实现二维图像采集(如扫描仪、工业检测线扫相机),适合长条状物体成像(如纸张、布匹)。
由二维阵列的传感器组成(如 CCD/CMOS),直接一次性采集二维图像,速度快、操作简单,是目前数码相机、手机摄像头的主流方案。
数字图像可简化为光照(i)和反射(r)的乘积:

代码案例:模拟图像形成模型(光照 × 反射)
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# 生成光照图(中心亮、边缘暗)
x, y = np.meshgrid(np.linspace(-1, 1, 500), np.linspace(-1, 1, 500))
i = np.exp(-(x**2 + y**2)/0.5) # 高斯分布光照
i = i / i.max() # 归一化到0-1
# 生成反射图(矩形区域反射率高,其余低)
r = np.ones((500, 500)) * 0.2 # 背景反射率0.2
r[150:350, 150:350] = 0.8 # 中心矩形反射率0.8
# 生成最终图像
f = i * r
# 绘制对比图
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
axes[0].imshow(i, cmap='gray', vmin=0, vmax=1)
axes[0].set_title('光照分量 i(x,y)', fontsize=12)
axes[0].axis('off')
axes[1].imshow(r, cmap='gray', vmin=0, vmax=1)
axes[1].set_title('反射分量 r(x,y)', fontsize=12)
axes[1].axis('off')
axes[2].imshow(f, cmap='gray', vmin=0, vmax=1)
axes[2].set_title('最终图像 f(x,y)=i×r', fontsize=12)
axes[2].axis('off')
plt.tight_layout()
plt.show()

采样和量化后的数字图像是二维矩阵:



代码案例:空间分辨率+灰度分辨率对比
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.image as mpimg # 用于加载本地图像
import os # 用于检查文件是否存在
# 设置matplotlib支持中文显示
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# ===================== 加载本地图像 =====================
image_path = "../picture/CSGO.jpg"
# 检查文件是否存在
if not os.path.exists(image_path):
raise FileNotFoundError(f"未找到图像文件:{image_path}\n请检查路径是否正确!")
# 加载图像并转换为灰度图(兼容彩色/灰度图像)
img = mpimg.imread(image_path)
# 如果是彩色图像(3通道),转换为灰度图;如果已是灰度图则直接使用
if len(img.shape) == 3:
# 按RGB权重转换为灰度:Y = 0.2989R + 0.5870G + 0.1140B
img = np.dot(img[..., :3], [0.2989, 0.5870, 0.1140]).astype(np.uint8)
# 确保图像是8位灰度图(0-255)
img = img.astype(np.uint8)
# 打印图像基本信息,方便调试
print(f"图像尺寸:{img.shape}")
print(f"图像数据类型:{img.dtype}")
print(f"灰度值范围:{img.min()} ~ {img.max()}")
# ===================== 原有逻辑(保留不变) =====================
# 1. 空间分辨率对比(下采样)
# 动态计算下采样间隔,适配不同尺寸的图像(避免采样后尺寸过小/过大)
# 目标采样后尺寸约为原尺寸的1/16(32x32对应512x512)和1/4(128x128对应512x512)
h, w = img.shape
step_32 = max(1, h // 32) # 确保步长至少为1
step_128 = max(1, h // 128)
img_32 = img[::step_32, ::step_32] # 低分辨率(约32x32)
img_128 = img[::step_128, ::step_128] # 中分辨率(约128x128)
# 2. 灰度分辨率对比(量化)
def quantize(img, levels):
"""量化图像到指定灰度级"""
if levels == 1: # 避免除以0
return np.zeros_like(img)
img_normalized = img / 255.0 # 归一化到0-1
img_quantized = np.floor(img_normalized * (levels - 1)) / (levels - 1)
return (img_quantized * 255).astype(np.uint8)
img_2level = quantize(img, 2) # 2级灰度
img_16level = quantize(img, 16) # 16级灰度
# ===================== 绘制对比图(适配新图像尺寸) =====================
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
# 空间分辨率
axes[0,0].imshow(img, cmap='gray')
axes[0,0].set_title(f'原始图像({h}x{w})', fontsize=12)
axes[0,0].axis('off')
axes[0,1].imshow(img_32, cmap='gray')
axes[0,1].set_title(f'空间分辨率降低({img_32.shape[0]}x{img_32.shape[1]})', fontsize=12)
axes[0,1].axis('off')
# 灰度分辨率
axes[1,0].imshow(img_16level, cmap='gray')
axes[1,0].set_title('灰度分辨率16级', fontsize=12)
axes[1,0].axis('off')
axes[1,1].imshow(img_2level, cmap='gray')
axes[1,1].set_title('灰度分辨率2级', fontsize=12)
axes[1,1].axis('off')
plt.tight_layout()
plt.show()
插值是提升图像空间分辨率的常用方法(补全像素),常见方法:最近邻插值、双线性插值、双三次插值。
代码案例:不同插值方法效果对比
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.image as mpimg # 加载本地图像
import os # 检查文件是否存在
from scipy.ndimage import zoom
# 设置matplotlib支持中文显示
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# ===================== 加载自定义本地图像 =====================
# 请替换为你的本地图像路径(支持jpg/png/bmp等格式)
image_path = "../picture/TianHuoSanXuanBian.jpg"
# 检查文件是否存在
if not os.path.exists(image_path):
raise FileNotFoundError(f"未找到图像文件:{image_path}\n请检查路径是否正确!")
# 加载图像
img = mpimg.imread(image_path)
# 彩色图像转灰度图(兼容彩色/灰度图像)
if len(img.shape) == 3:
# 标准RGB转灰度公式:Y = 0.2989R + 0.5870G + 0.1140B
img = np.dot(img[..., :3], [0.2989, 0.5870, 0.1140]).astype(np.uint8)
# 确保图像为8位灰度图
img = img.astype(np.uint8)
# 打印图像基本信息,方便调试
h, w = img.shape
print(f"原始图像尺寸:{h}x{w}")
print(f"图像数据类型:{img.dtype}")
# ===================== 动态下采样(降低分辨率) =====================
# 动态计算下采样步长,使低分辨率图像约为64x64(适配不同尺寸图像)
step_h = max(1, h // 64)
step_w = max(1, w // 64)
img_low = img[::step_h, ::step_w] # 下采样到约64x64
low_h, low_w = img_low.shape
print(f"下采样后低分辨率图像尺寸:{low_h}x{low_w}")
# ===================== 不同插值方法放大(放大倍数适配低分辨率尺寸) =====================
# 计算放大倍数,使放大后图像接近原图像尺寸(或固定放大4倍)
scale_h = h / low_h # 按高度放大
scale_w = w / low_w # 按宽度放大
# 也可固定放大4倍:scale_h = scale_w = 4
# 1. 最近邻插值(order=0):速度快,块效应明显
img_nn = zoom(img_low, (scale_h, scale_w), order=0)
# 2. 双线性插值(order=1):平滑,细节损失
img_bl = zoom(img_low, (scale_h, scale_w), order=1)
# 3. 双三次插值(order=3):效果最好,细节保留完整
img_bc = zoom(img_low, (scale_h, scale_w), order=3)
# 裁剪到原图像尺寸(避免插值后尺寸微小偏差)
img_nn = img_nn[:h, :w]
img_bl = img_bl[:h, :w]
img_bc = img_bc[:h, :w]
# ===================== 绘制插值效果对比图 =====================
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
# 低分辨率原图
axes[0,0].imshow(img_low, cmap='gray')
axes[0,0].set_title(f'低分辨率图像({low_h}x{low_w})', fontsize=12)
axes[0,0].axis('off')
# 最近邻插值
axes[0,1].imshow(img_nn, cmap='gray')
axes[0,1].set_title('最近邻插值', fontsize=12)
axes[0,1].axis('off')
# 双线性插值
axes[1,0].imshow(img_bl, cmap='gray')
axes[1,0].set_title('双线性插值', fontsize=12)
axes[1,0].axis('off')
# 双三次插值
axes[1,1].imshow(img_bc, cmap='gray')
axes[1,1].set_title('双三次插值', fontsize=12)
axes[1,1].axis('off')
plt.tight_layout()
plt.show()
效果说明:最近邻插值最模糊(有明显块效应),双线性插值更平滑,双三次插值效果最好(细节保留最完整)。

像素(x,y)的邻域是围绕它的像素集合,常见类型:


常用的像素距离:
代码案例:像素距离计算与可视化
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as patches
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# 定义中心像素(5,5),计算不同距离的像素
center = (5,5)
x, y = np.meshgrid(np.arange(0,11), np.arange(0,11))
# 初始化距离矩阵
d_euclidean = np.sqrt((x-center[0])**2 + (y-center[1])**2)
d_manhattan = np.abs(x-center[0]) + np.abs(y-center[1])
d_chessboard = np.maximum(np.abs(x-center[0]), np.abs(y-center[1]))
# 绘制距离可视化
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
# 欧氏距离
im1 = axes[0].imshow(d_euclidean, cmap='viridis')
axes[0].scatter(center[0], center[1], color='red', s=100, label='中心像素')
axes[0].set_title('欧氏距离', fontsize=12)
axes[0].axis('off')
plt.colorbar(im1, ax=axes[0], shrink=0.8)
# 曼哈顿距离
im2 = axes[1].imshow(d_manhattan, cmap='viridis')
axes[1].scatter(center[0], center[1], color='red', s=100)
axes[1].set_title('曼哈顿距离', fontsize=12)
axes[1].axis('off')
plt.colorbar(im2, ax=axes[1], shrink=0.8)
# 棋盘距离
im3 = axes[2].imshow(d_chessboard, cmap='viridis')
axes[2].scatter(center[0], center[1], color='red', s=100)
axes[2].set_title('切比雪夫距离', fontsize=12)
axes[2].axis('off')
plt.colorbar(im3, ax=axes[2], shrink=0.8)
plt.tight_layout()
plt.show()
图像算术运算:加(去噪)、减(背景差分)、乘(掩膜)、除(归一化)。
直接对像素灰度值进行运算,如:
g(x,y)=T[f(x,y)]
其中T为变换函数(如灰度调整、滤波)。
图像可视为向量/矩阵,常用运算:转置、逆、特征值分解、奇异值分解(SVD)。
将图像从空间域转换到变换域(如傅里叶变换、离散余弦变换、小波变换),便于频域分析、压缩、滤波。
用统计特征描述图像:均值(亮度)、方差(对比度)、直方图(灰度分布)、协方差(相关性)。
代码案例:图像数学运算综合演示
import numpy as np
import matplotlib.pyplot as plt
from skimage import data
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# 加载两张灰度图
img1 = data.camera()
img2 = data.coins()
# 统一尺寸(取最小尺寸)
h, w = min(img1.shape[0], img2.shape[0]), min(img1.shape[1], img2.shape[1])
img1 = img1[:h, :w]
img2 = img2[:h, :w]
# 1. 算术运算(加、减)
img_add = np.clip((img1 + img2) / 2, 0, 255).astype(np.uint8) # 相加后归一化
img_sub = np.clip(img1 - img2 + 128, 0, 255).astype(np.uint8) # 相减后偏移
# 2. 逻辑运算(二值化后)
img1_bin = (img1 > 128).astype(np.uint8) * 255
img2_bin = (img2 > 128).astype(np.uint8) * 255
img_and = np.bitwise_and(img1_bin, img2_bin)
img_or = np.bitwise_or(img1_bin, img2_bin)
# 3. 空间域运算(灰度调整)
img_bright = np.clip(img1 * 1.5, 0, 255).astype(np.uint8) # 增亮
img_contrast = np.clip((img1 - 128) * 2 + 128, 0, 255).astype(np.uint8) # 增强对比度
# 4. 统计特征计算
mean = np.mean(img1)
var = np.var(img1)
hist, bins = np.histogram(img1, bins=256, range=(0,255))
# 绘制结果
fig, axes = plt.subplots(3, 2, figsize=(12, 15))
# 算术运算
axes[0,0].imshow(img_add, cmap='gray')
axes[0,0].set_title(f'算术运算:相加', fontsize=12)
axes[0,0].axis('off')
axes[0,1].imshow(img_sub, cmap='gray')
axes[0,1].set_title(f'算术运算:相减', fontsize=12)
axes[0,1].axis('off')
# 逻辑运算
axes[1,0].imshow(img_and, cmap='gray')
axes[1,0].set_title(f'逻辑运算:与', fontsize=12)
axes[1,0].axis('off')
axes[1,1].imshow(img_or, cmap='gray')
axes[1,1].set_title(f'逻辑运算:或', fontsize=12)
axes[1,1].axis('off')
# 空间域+统计
axes[2,0].imshow(img_contrast, cmap='gray')
axes[2,0].set_title(f'空间域:对比度增强(均值={mean:.2f},方差={var:.2f})', fontsize=10)
axes[2,0].axis('off')
axes[2,1].plot(hist)
axes[2,1].set_title('灰度直方图', fontsize=12)
axes[2,1].set_xlabel('灰度值')
axes[2,1].set_ylabel('像素数')
plt.tight_layout()
plt.show()