在上一章搭建好开发环境之后,本章将带你深入理解数字图像的本质,并掌握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. 本章小结

通过本章的学习,你应该已经掌握了:

  1. 数字图像基础:理解了采样、量化、分辨率、位深度等核心概念
  2. 图像文件格式:知道了JPEG、PNG、TIFF等格式的区别和适用场景
  3. OpenCV核心操作:掌握了图像的读取、保存、显示
  4. 色彩空间:理解了BGR、RGB、HSV等色彩空间的转换和应用
  5. 几何变换:学会了缩放、旋转、平移、仿射变换和透视变换
  6. 图像运算:掌握了算术运算和位运算在图像处理中的应用
  7. 实际编程:能够编写实用的图像处理工具类

一句话总结:数字图像本质上是像素值的矩阵,OpenCV提供了丰富的工具来操作这个矩阵,理解这些基础操作是掌握高级算法的前提。


11. 练习与思考

  1. 格式转换:编写程序将一批JPEG图像转换为PNG格式,比较文件大小差异
  2. 图像金字塔:实现图像金字塔(多次缩放),观察图像质量变化
  3. 颜色提取:编写程序提取图像中的主要颜色(使用K-means聚类)
  4. 批量处理:扩展ImageProcessor类,支持批量处理整个文件夹的图像
  5. 文档扫描:实现一个自动文档扫描程序,检测文档边缘并进行透视校正

下一章预告:第3章《图像滤波与边缘检测》将带你学习如何使用各种滤波器去除图像噪声,以及如何使用边缘检测算法提取图像中的轮廓信息。这是图像特征提取的基础。


如果本章内容对你有帮助,欢迎点赞、收藏和关注。有任何问题可以在评论区留言。

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐