【计算机视觉实战】第2章 | 图像基础与OpenCV核心操作:理解数字图像的本质
本文介绍了数字图像的基本概念和OpenCV基础操作。首先讲解了图像的数字化过程(采样和量化)及基本属性(分辨率、位深度、通道数)。然后分析了常见图像格式的特点(JPEG有损压缩、PNG无损压缩等)。重点阐述了OpenCV的核心操作:图像读取与保存、BGR/RGB色彩空间转换、图像矩阵表示,以及HSV等色彩空间的原理与应用场景。通过代码示例展示了如何使用OpenCV进行图像处理和通道转换,为计算机视
在上一章搭建好开发环境之后,本章将带你深入理解数字图像的本质,并掌握OpenCV这个强大的计算机视觉库的核心操作。理解图像是如何被计算机"看见"的,是学习所有计算机视觉算法的基础。
1. 环境声明
- Python版本:
Python 3.12+ - PyTorch版本:
PyTorch 2.2+ - OpenCV版本:
OpenCV 4.10+ - 操作系统:Windows / macOS / Linux (通用)
2. 什么是数字图像
2.1 从模拟到数字:图像的数字化过程
想象一下你用手机拍摄一张照片。光线进入镜头,照射到传感器上,传感器将光信号转换为电信号,再通过模数转换器(ADC)将连续的模拟信号转换为离散的数字信号。这个过程就是图像的数字化。
采样(Sampling):
在空间上,我们将连续的图像平面划分为离散的网格,每个网格点称为一个像素(Pixel)。采样率决定了图像的分辨率。例如,1920x1080的图像意味着水平方向有1920个像素,垂直方向有1080个像素,总共约200万个像素。
量化(Quantization):
在数值上,我们将连续的亮度值划分为离散的级别。对于8位图像,每个像素可以表示0-255共256个亮度级别(2^8)。对于彩色图像,每个通道(R、G、B)各自进行8位量化。
2.2 图像的基本属性
分辨率(Resolution):
分辨率决定了图像的细节程度。常见的分辨率有:
- SD(标清):720x576
- HD(高清):1280x720
- FHD(全高清):1920x1080
- 4K UHD:3840x2160
- 8K UHD:7680x4320
分辨率越高,图像包含的信息越多,但处理所需的计算资源也越多。
位深度(Bit Depth):
每个像素用多少位来表示。常见有:
- 8位:256级灰度或每种颜色256级(24位真彩色)
- 16位:65536级(医学图像常用)
- 32位浮点:HDR图像处理
通道数(Channels):
- 灰度图:1通道(0-255)
- RGB彩色图:3通道(Red、Green、Blue)
- RGBA:4通道(增加Alpha透明度)
- CMYK:4通道(印刷用)
3. 图像文件格式详解
3.1 有损压缩 vs 无损压缩
有损压缩(Lossy Compression):
- JPEG/JPG:使用离散余弦变换(DCT)和量化去除人眼不敏感的高频信息
- 原理:将图像划分为8x8的块,对每个块进行DCT变换,然后量化高频系数
- 优点:压缩比高(通常10:1到100:1),文件小
- 缺点:有损压缩,多次编辑保存会导致质量下降
- 适用场景:照片、网页图片、不需要精确像素值的场景
无损压缩(Lossless Compression):
- PNG:使用DEFLATE算法(LZ77 + Huffman编码)
- 原理:通过预测相邻像素值,存储预测误差而非原始值
- 优点:完美保留所有像素信息,支持透明通道
- 缺点:压缩比相对较低(通常2:1到5:1)
- 适用场景:需要精确像素的图像、图标、带透明度的图像
3.2 其他常见格式
TIFF:
- 支持多种压缩方式(包括无损LZW和有损JPEG)
- 支持多页、多图层
- 常用于出版印刷、医学影像、遥感图像
BMP:
- 无压缩或简单RLE压缩
- 文件大,但解析简单
- 适合嵌入式系统或需要快速读取的场景
WebP:
- Google开发,支持有损和无损压缩
- 相同质量下文件大小通常比JPEG小25-35%,比PNG小26%
- 支持动画和透明度
- 现代浏览器的标准支持
RAW:
- 相机传感器原始数据
- 未经过任何处理(白平衡、降噪、锐化)
- 最大的后期处理空间
- 文件大,需要专用软件处理
4. OpenCV基础操作
4.1 图像的读取与保存
OpenCV使用cv2.imread()读取图像,使用cv2.imwrite()保存图像。
读取参数说明:
cv2.IMREAD_COLOR(1):以彩色模式读取,忽略alpha通道(默认)cv2.IMREAD_GRAYSCALE(0):以灰度模式读取cv2.IMREAD_UNCHANGED(-1):按原样读取,包括alpha通道
import cv2
import numpy as np
# 读取彩色图像
img_color = cv2.imread('image.jpg', cv2.IMREAD_COLOR)
# 读取灰度图像
img_gray = cv2.imread('image.jpg', cv2.IMREAD_GRAYSCALE)
# 读取原图(包含alpha通道)
img_unchanged = cv2.imread('image.png', cv2.IMREAD_UNCHANGED)
# 检查图像是否成功读取
if img_color is None:
print("错误:无法读取图像,请检查文件路径")
else:
print(f"图像尺寸: {img_color.shape}")
print(f"数据类型: {img_color.dtype}")
# 保存图像
# JPEG格式(质量参数0-100,默认95)
cv2.imwrite('output.jpg', img_color, [cv2.IMWRITE_JPEG_QUALITY, 90])
# PNG格式(压缩级别0-9,默认3)
cv2.imwrite('output.png', img_color, [cv2.IMWRITE_PNG_COMPRESSION, 3])
4.2 BGR vs RGB:OpenCV的色彩空间特殊性
这是OpenCV最常见的坑之一。OpenCV默认使用BGR(蓝-绿-红)通道顺序,而大多数其他库(如PIL、Matplotlib)使用RGB(红-绿-蓝)。
为什么OpenCV用BGR?
这源于历史原因。早期相机制造商和软件供应商使用BGR顺序,OpenCV为了保持兼容性沿用了这一传统。
转换方法:
import cv2
import matplotlib.pyplot as plt
# 读取图像(OpenCV默认BGR)
img_bgr = cv2.imread('image.jpg')
# 方法1:使用cv2.cvtColor转换
img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
# 方法2:手动调整通道顺序
img_rgb_manual = img_bgr[:, :, ::-1]
# 方法3:使用numpy索引
img_rgb_numpy = img_bgr[..., [2, 1, 0]]
# 使用matplotlib显示(需要RGB)
plt.figure(figsize=(12, 4))
plt.subplot(131)
plt.imshow(img_bgr) # 错误!会显示奇怪的蓝色
plt.title('BGR (Wrong)')
plt.axis('off')
plt.subplot(132)
plt.imshow(img_rgb) # 正确
plt.title('RGB (Correct)')
plt.axis('off')
plt.subplot(133)
# 显示单个通道
plt.imshow(img_bgr[:, :, 0], cmap='Blues') # B通道
plt.title('Blue Channel')
plt.axis('off')
plt.tight_layout()
plt.show()
4.3 图像的矩阵表示
在OpenCV中,图像本质上是numpy数组:
import cv2
import numpy as np
# 读取图像
img = cv2.imread('image.jpg')
print(f"类型: {type(img)}") # <class 'numpy.ndarray'>
print(f"形状: {img.shape}") # (高, 宽, 通道)
print(f"数据类型: {img.dtype}") # uint8
# 访问特定像素
# 注意:OpenCV使用(y, x)坐标,不是(x, y)
pixel = img[100, 200] # 获取第100行第200列的像素
print(f"位置(200, 100)的像素值: {pixel}") # [B, G, R]
# 修改像素值
img[100, 200] = [0, 0, 255] # 设为红色
# 访问特定通道
blue_channel = img[:, :, 0] # 或使用 img[..., 0]
green_channel = img[:, :, 1]
red_channel = img[:, :, 2]
# 创建图像副本(避免引用问题)
img_copy = img.copy() # 或 np.copy(img)
# 图像属性
height, width = img.shape[:2]
channels = img.shape[2] if len(img.shape) > 2 else 1
total_pixels = img.size
memory_size = img.nbytes
print(f"高度: {height}, 宽度: {width}, 通道: {channels}")
print(f"总像素数: {total_pixels}")
print(f"内存占用: {memory_size / 1024:.2f} KB")
5. 色彩空间转换
5.1 为什么需要不同的色彩空间
不同的应用场景需要不同的色彩空间:
- RGB:适合显示设备,与人眼感知的颜色对应
- HSV:适合颜色分割,因为人眼对色相(Hue)更敏感
- LAB:适合色彩恒常性,L通道表示亮度,AB表示色度
- YCrCb:适合视频压缩,Y是亮度,CrCb是色度
5.2 HSV色彩空间详解
HSV(Hue-Saturation-Value)是基于人眼感知的色彩模型:
H(Hue,色相):0-179(OpenCV中)或0-360(标准中)
- 表示颜色的种类,如红、黄、蓝
- 在OpenCV中,H范围是0-179(为了适配8位整数)
S(Saturation,饱和度):0-255
- 表示颜色的纯度
- 0表示灰色,255表示纯色
V(Value,明度):0-255
- 表示颜色的明暗程度
- 0表示纯黑,255表示最亮
为什么HSV适合颜色分割?
在RGB空间中,相同的颜色在不同亮度下R、G、B值都会变化,难以设定固定阈值。而在HSV空间中,H值基本保持不变,只需调整S和V的范围即可提取特定颜色。
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 读取图像
img = cv2.imread('image.jpg')
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# 转换为HSV
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# 提取红色物体
# 红色在HSV中跨越0度(180度=0度),所以需要两个范围
lower_red1 = np.array([0, 100, 100])
upper_red1 = np.array([10, 255, 255])
lower_red2 = np.array([160, 100, 100])
upper_red2 = np.array([179, 255, 255])
# 创建掩码
mask1 = cv2.inRange(hsv, lower_red1, upper_red1)
mask2 = cv2.inRange(hsv, lower_red2, upper_red2)
red_mask = cv2.bitwise_or(mask1, mask2)
# 应用掩码
red_objects = cv2.bitwise_and(img_rgb, img_rgb, mask=red_mask)
# 显示结果
plt.figure(figsize=(15, 5))
plt.subplot(131)
plt.imshow(img_rgb)
plt.title('Original')
plt.axis('off')
plt.subplot(132)
plt.imshow(red_mask, cmap='gray')
plt.title('Red Mask')
plt.axis('off')
plt.subplot(133)
plt.imshow(red_objects)
plt.title('Red Objects')
plt.axis('off')
plt.tight_layout()
plt.show()
5.3 灰度转换原理
将彩色图像转换为灰度图像使用以下公式:
Gray = 0.299 * R + 0.587 * G + 0.114 * B
这些系数基于人眼对不同颜色的敏感度。人眼对绿色最敏感,对蓝色最不敏感。
import cv2
import numpy as np
# 读取图像
img = cv2.imread('image.jpg')
# 方法1:OpenCV转换(推荐)
gray_cv = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 方法2:手动计算(验证公式)
# 注意OpenCV是BGR顺序
B, G, R = cv2.split(img)
gray_manual = (0.299 * R + 0.587 * G + 0.114 * B).astype(np.uint8)
# 验证两种方法结果相同
diff = np.abs(gray_cv.astype(float) - gray_manual.astype(float))
print(f"最大差异: {diff.max()} (应该为0或1,因为浮点舍入)")
# 保存灰度图
cv2.imwrite('gray_image.jpg', gray_cv)
6. 图像几何变换
6.1 图像缩放
插值方法:
cv2.INTER_NEAREST:最近邻插值,最快但质量差cv2.INTER_LINEAR:双线性插值,默认方法,平衡速度和质量cv2.INTER_CUBIC:双三次插值,质量更好但较慢cv2.INTER_LANCZOS4:Lanczos插值,质量最好但最慢cv2.INTER_AREA:区域插值,适合图像缩小
import cv2
# 读取图像
img = cv2.imread('image.jpg')
h, w = img.shape[:2]
# 方法1:指定新尺寸
resized = cv2.resize(img, (800, 600), interpolation=cv2.INTER_LINEAR)
# 方法2:指定缩放比例
resized_half = cv2.resize(img, None, fx=0.5, fy=0.5, interpolation=cv2.INTER_AREA)
resized_double = cv2.resize(img, None, fx=2, fy=2, interpolation=cv2.INTER_CUBIC)
# 方法3:保持宽高比
new_width = 800
ratio = new_width / w
new_height = int(h * ratio)
resized_keep_ratio = cv2.resize(img, (new_width, new_height))
print(f"原图尺寸: {w}x{h}")
print(f"缩放后: {new_width}x{new_height}")
6.2 图像平移
平移是图像在水平和垂直方向上的移动。需要构建一个2x3的变换矩阵:
[1 0 tx]
[0 1 ty]
其中tx是水平位移,ty是垂直位移。
import cv2
import numpy as np
# 读取图像
img = cv2.imread('image.jpg')
height, width = img.shape[:2]
# 定义平移矩阵:向右平移100,向下平移50
# 变换矩阵是2x3的浮点数组
M = np.float32([[1, 0, 100], # [1, 0, tx]
[0, 1, 50]]) # [0, 1, ty]
# 应用仿射变换
translated = cv2.warpAffine(img, M, (width, height))
# 如果平移后图像超出边界,可以扩大输出尺寸
M2 = np.float32([[1, 0, 100],
[0, 1, 50]])
new_width = width + 100
new_height = height + 50
translated_expanded = cv2.warpAffine(img, M2, (new_width, new_height))
# 保存结果
cv2.imwrite('translated.jpg', translated)
6.3 图像旋转
旋转需要一个2x2的旋转矩阵。OpenCV提供了cv2.getRotationMatrix2D函数来简化这个过程。
import cv2
import numpy as np
# 读取图像
img = cv2.imread('image.jpg')
height, width = img.shape[:2]
# 获取旋转矩阵
# 参数:旋转中心点(x,y),旋转角度(逆时针为正),缩放因子
center = (width // 2, height // 2)
angle = 45 # 逆时针旋转45度
scale = 1.0 # 不缩放
M = cv2.getRotationMatrix2D(center, angle, scale)
# 应用旋转
rotated = cv2.warpAffine(img, M, (width, height))
# 问题:旋转后图像会裁剪,如何解决?
# 计算旋转后的新边界框
# 旋转矩阵的数学推导...
# 方法:计算新的边界尺寸
cos_angle = abs(np.cos(np.radians(angle)))
sin_angle = abs(np.sin(np.radians(angle)))
new_w = int(height * sin_angle + width * cos_angle)
new_h = int(height * cos_angle + width * sin_angle)
# 调整旋转矩阵以包含完整图像
M[0, 2] += (new_w - width) / 2
M[1, 2] += (new_h - height) / 2
# 应用调整后的旋转
rotated_full = cv2.warpAffine(img, M, (new_w, new_h))
print(f"原图尺寸: {width}x{height}")
print(f"旋转后尺寸: {new_w}x{new_h}")
6.4 仿射变换
仿射变换包括平移、旋转、缩放、剪切等线性变换的组合。它保持直线性和平行性。
import cv2
import numpy as np
# 读取图像
img = cv2.imread('image.jpg')
rows, cols = img.shape[:2]
# 定义原图中的三个点
pts1 = np.float32([[50, 50], # 左上角
[200, 50], # 右上角
[50, 200]]) # 左下角
# 定义变换后的对应位置
pts2 = np.float32([[10, 100], # 新左上角
[200, 50], # 新右上角(与原来相同)
[100, 250]]) # 新左下角
# 计算仿射变换矩阵
M = cv2.getAffineTransform(pts1, pts2)
# 应用仿射变换
affine = cv2.warpAffine(img, M, (cols, rows))
# 可视化变换前后的点
img_visual = img.copy()
affine_visual = affine.copy()
for pt in pts1.astype(int):
cv2.circle(img_visual, tuple(pt), 5, (0, 0, 255), -1)
for pt in pts2.astype(int):
cv2.circle(affine_visual, tuple(pt), 5, (0, 255, 0), -1)
# 保存结果
result = np.hstack([img_visual, affine_visual])
cv2.imwrite('affine_transform.jpg', result)
6.5 透视变换
透视变换(Perspective Transform)可以模拟3D空间中的视角变化,例如将倾斜的文档校正为正视图。
import cv2
import numpy as np
# 读取图像
img = cv2.imread('document.jpg')
rows, cols = img.shape[:2]
# 定义文档的四个角点(在原图中的位置)
# 顺序:左上、右上、右下、左下
pts1 = np.float32([[100, 100], # 左上
[400, 80], # 右上
[420, 300], # 右下
[80, 320]]) # 左下
# 定义变换后的矩形位置
width = 400
height = 300
pts2 = np.float32([[0, 0], # 左上
[width, 0], # 右上
[width, height], # 右下
[0, height]]) # 左下
# 计算透视变换矩阵
M = cv2.getPerspectiveTransform(pts1, pts2)
# 应用透视变换
perspective = cv2.warpPerspective(img, M, (width, height))
# 可视化
img_visual = img.copy()
for i, pt in enumerate(pts1.astype(int)):
color = (0, 0, 255) if i < 2 else (255, 0, 0)
cv2.circle(img_visual, tuple(pt), 8, color, -1)
cv2.putText(img_visual, str(i), tuple(pt),
cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2)
# 保存结果
cv2.imwrite('perspective_original.jpg', img_visual)
cv2.imwrite('perspective_corrected.jpg', perspective)
7. 图像运算
7.1 算术运算
import cv2
import numpy as np
# 创建两个测试图像
img1 = np.ones((300, 300, 3), dtype=np.uint8) * 100 # 深灰色
img2 = np.ones((300, 300, 3), dtype=np.uint8) * 150 # 浅灰色
# 图像加法
# 方法1:numpy加法(模运算)
result_numpy = img1 + img2 # 250 + 100 = 350 % 256 = 94
# 方法2:OpenCV加法(饱和运算)
result_cv = cv2.add(img1, img2) # 250 + 100 = 255(饱和)
print(f"NumPy加法结果[0,0]: {result_numpy[0,0]}")
print(f"OpenCV加法结果[0,0]: {result_cv[0,0]}")
# 带权重的加法(图像混合)
# 公式:dst = alpha * img1 + beta * img2 + gamma
alpha = 0.7
beta = 0.3
gamma = 0
blended = cv2.addWeighted(img1, alpha, img2, beta, gamma)
# 图像减法
diff = cv2.subtract(img2, img1) # 饱和减法
# 图像乘法(用于mask)
mask = np.zeros((300, 300), dtype=np.uint8)
cv2.circle(mask, (150, 150), 100, 255, -1)
masked = cv2.bitwise_and(img1, img1, mask=mask)
7.2 位运算
位运算在图像处理中常用于掩码操作和形状提取。
import cv2
import numpy as np
# 创建两个二进制图像
img1 = np.zeros((300, 300), dtype=np.uint8)
img2 = np.zeros((300, 300), dtype=np.uint8)
# 在img1中画矩形
cv2.rectangle(img1, (50, 50), (250, 250), 255, -1)
# 在img2中画圆
cv2.circle(img2, (150, 150), 100, 255, -1)
# 位运算
bitwise_and = cv2.bitwise_and(img1, img2) # 交集
bitwise_or = cv2.bitwise_or(img1, img2) # 并集
bitwise_xor = cv2.bitwise_xor(img1, img2) # 异或
bitwise_not = cv2.bitwise_not(img1) # 非(取反)
# 应用:使用掩码提取ROI
roi = cv2.bitwise_and(img_color, img_color, mask=circle_mask)
8. 避坑小贴士
常见错误1:坐标顺序混淆
现象:访问img[x, y]而不是img[y, x]
原因:
- NumPy数组使用[row, column]即[y, x]
- 但在OpenCV的某些函数中(如cv2.circle),使用(x, y)
正确做法:
# 数组索引:行(y)在前,列(x)在后
pixel = img[y, x] # 获取位置(x, y)的像素
# 绘图函数:通常使用(x, y)
cv2.circle(img, (x, y), radius, color, thickness)
cv2.rectangle(img, (x1, y1), (x2, y2), color, thickness)
常见错误2:原地修改导致意外结果
现象:修改了ROI后,原图也发生了变化
原因:NumPy切片返回的是视图(view)而非副本
解决方案:
# 错误做法(修改roi会影响原图)
roi = img[100:200, 100:200]
roi[:] = 255 # 这会修改原图
# 正确做法1:使用copy()
roi = img[100:200, 100:200].copy()
roi[:] = 255 # 不影响原图
# 正确做法2:直接赋值
img[100:200, 100:200] = 255
常见错误3:数据类型溢出
现象:图像处理后像素值异常(如出现黑斑)
原因:uint8类型溢出,255 + 1 = 0
解决方案:
# 错误:直接相加会导致溢出
img_bright = img + 50 # 像素值可能溢出
# 正确1:使用cv2.add()(饱和运算)
img_bright = cv2.add(img, 50)
# 正确2:转换为更高位深的类型
img_float = img.astype(np.float32) + 50
img_bright = np.clip(img_float, 0, 255).astype(np.uint8)
常见错误4:忘记检查图像是否读取成功
现象:程序崩溃,提示NoneType错误
解决方案:
img = cv2.imread('image.jpg')
# 必须检查
if img is None:
print("错误:无法读取图像")
# 处理错误
else:
# 正常处理
pass
9. 实战项目:图像处理工具箱
创建一个实用的图像处理工具类:
import cv2
import numpy as np
from typing import Tuple, Optional
class ImageProcessor:
\"\"\"图像处理工具类\"\"\"
def __init__(self, image_path: str):
\"\"\"初始化,加载图像\"\"\"
self.image = cv2.imread(image_path)
if self.image is None:
raise ValueError(f"无法加载图像: {image_path}")
self.original = self.image.copy()
def resize(self, width: Optional[int] = None,
height: Optional[int] = None,
scale: Optional[float] = None) -> np.ndarray:
\"\"\"调整图像大小\"\"\"
if scale is not None:
return cv2.resize(self.image, None, fx=scale, fy=scale)
h, w = self.image.shape[:2]
if width is None and height is None:
return self.image.copy()
if width is None:
ratio = height / h
width = int(w * ratio)
elif height is None:
ratio = width / w
height = int(h * ratio)
return cv2.resize(self.image, (width, height))
def rotate(self, angle: float,
keep_size: bool = False) -> np.ndarray:
\"\"\"旋转图像\"\"\"
h, w = self.image.shape[:2]
center = (w // 2, h // 2)
M = cv2.getRotationMatrix2D(center, angle, 1.0)
if not keep_size:
return cv2.warpAffine(self.image, M, (w, h))
# 计算新尺寸
cos_a = abs(np.cos(np.radians(angle)))
sin_a = abs(np.sin(np.radians(angle)))
new_w = int(h * sin_a + w * cos_a)
new_h = int(h * cos_a + w * sin_a)
M[0, 2] += (new_w - w) / 2
M[1, 2] += (new_h - h) / 2
return cv2.warpAffine(self.image, M, (new_w, new_h))
def crop(self, x: int, y: int,
width: int, height: int) -> np.ndarray:
\"\"\"裁剪图像\"\"\"
return self.image[y:y+height, x:x+width].copy()
def adjust_brightness(self, value: int) -> np.ndarray:
\"\"\"调整亮度\"\"\"
return cv2.add(self.image, value)
def adjust_contrast(self, alpha: float) -> np.ndarray:
\"\"\"调整对比度\"\"\"
return cv2.convertScaleAbs(self.image, alpha=alpha, beta=0)
def flip(self, code: int) -> np.ndarray:
\"\"\"翻转图像\
code: 0=垂直翻转, 1=水平翻转, -1=同时翻转\"\"\"
return cv2.flip(self.image, code)
def to_grayscale(self) -> np.ndarray:
\"\"\"转换为灰度图\"\"\"
return cv2.cvtColor(self.image, cv2.COLOR_BGR2GRAY)
def get_info(self) -> dict:
\"\"\"获取图像信息\"\"\"
h, w = self.image.shape[:2]
channels = self.image.shape[2] if len(self.image.shape) > 2 else 1
return {
'width': w,
'height': h,
'channels': channels,
'dtype': str(self.image.dtype),
'size_kb': self.image.nbytes / 1024
}
def save(self, path: str, quality: int = 95) -> bool:
\"\"\"保存图像\"\"\"
if path.lower().endswith('.jpg') or path.lower().endswith('.jpeg'):
return cv2.imwrite(path, self.image,
[cv2.IMWRITE_JPEG_QUALITY, quality])
return cv2.imwrite(path, self.image)
def reset(self):
\"\"\"重置为原始图像\"\"\"
self.image = self.original.copy()
# 使用示例
if __name__ == "__main__":
# 创建处理器实例
processor = ImageProcessor('test_image.jpg')
# 获取图像信息
info = processor.get_info()
print(f"图像信息: {info}")
# 调整大小
resized = processor.resize(width=800)
cv2.imwrite('resized.jpg', resized)
# 旋转45度(保持完整图像)
rotated = processor.rotate(45, keep_size=True)
cv2.imwrite('rotated.jpg', rotated)
# 裁剪中心区域
h, w = processor.image.shape[:2]
crop_size = 200
x = (w - crop_size) // 2
y = (h - crop_size) // 2
cropped = processor.crop(x, y, crop_size, crop_size)
cv2.imwrite('cropped.jpg', cropped)
# 调整亮度和对比度
processor.reset()
bright = processor.adjust_brightness(50)
cv2.imwrite('bright.jpg', bright)
high_contrast = processor.adjust_contrast(1.5)
cv2.imwrite('high_contrast.jpg', high_contrast)
10. 本章小结
通过本章的学习,你应该已经掌握了:
- 数字图像基础:理解了采样、量化、分辨率、位深度等核心概念
- 图像文件格式:知道了JPEG、PNG、TIFF等格式的区别和适用场景
- OpenCV核心操作:掌握了图像的读取、保存、显示
- 色彩空间:理解了BGR、RGB、HSV等色彩空间的转换和应用
- 几何变换:学会了缩放、旋转、平移、仿射变换和透视变换
- 图像运算:掌握了算术运算和位运算在图像处理中的应用
- 实际编程:能够编写实用的图像处理工具类
一句话总结:数字图像本质上是像素值的矩阵,OpenCV提供了丰富的工具来操作这个矩阵,理解这些基础操作是掌握高级算法的前提。
11. 练习与思考
- 格式转换:编写程序将一批JPEG图像转换为PNG格式,比较文件大小差异
- 图像金字塔:实现图像金字塔(多次缩放),观察图像质量变化
- 颜色提取:编写程序提取图像中的主要颜色(使用K-means聚类)
- 批量处理:扩展ImageProcessor类,支持批量处理整个文件夹的图像
- 文档扫描:实现一个自动文档扫描程序,检测文档边缘并进行透视校正
下一章预告:第3章《图像滤波与边缘检测》将带你学习如何使用各种滤波器去除图像噪声,以及如何使用边缘检测算法提取图像中的轮廓信息。这是图像特征提取的基础。
如果本章内容对你有帮助,欢迎点赞、收藏和关注。有任何问题可以在评论区留言。
更多推荐
所有评论(0)