强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

OpenCV 计算机视觉完全教程 / 第 03 章 — 图像基础

第 03 章 — 图像基础

3.1 图像的本质

在 OpenCV 中,图像就是一个多维数组:

类型通道数dtype示例
灰度图1uint8shape=(H, W)
彩色图(BGR)3uint8shape=(H, W, 3)
带透明度(BGRA)4uint8shape=(H, W, 4)
浮点灰度图1float32shape=(H, W)
深度图1uint16/int16shape=(H, W)

注意: OpenCV 默认色彩顺序是 BGR,而非 RGB。这是与 PIL、Matplotlib 的主要区别。

像素坐标系:                内存布局 (H=3, W=4, C=3):
(0,0) → (0,W)              [B G R] [B G R] [B G R] [B G R]
 ↓        ↓                 [B G R] [B G R] [B G R] [B G R]
(H,0) → (H,W)              [B G R] [B G R] [B G R] [B G R]

3.2 图像读取:imread

import cv2

# 读取彩色图像(默认)
img = cv2.imread("photo.jpg")
print(f"默认读取: shape={img.shape}, dtype={img.dtype}")

# 读取灰度图
gray = cv2.imread("photo.jpg", cv2.IMREAD_GRAYSCALE)
print(f"灰度读取: shape={gray.shape}, dtype={gray.dtype}")

# 读取原始数据(含 alpha 通道,不裁剪位深)
raw = cv2.imread("photo.png", cv2.IMREAD_UNCHANGED)
print(f"原始读取: shape={raw.shape}, dtype={raw.dtype}")

# 读取为三通道(忽略 alpha)
color = cv2.imread("photo.png", cv2.IMREAD_COLOR)

# 读取标志一览
# cv2.IMREAD_UNCHANGED  = -1  # 原始数据
# cv2.IMREAD_GRAYSCALE  = 0   # 灰度
# cv2.IMREAD_COLOR      = 1   # BGR 彩色(默认)
# cv2.IMREAD_ANYDEPTH   = 2   # 保持原始位深
# cv2.IMREAD_ANYCOLOR   = 4   # 按文件格式读取
# cv2.IMREAD_REDUCED_*  = 多种  # 缩小读取
// C++ 读取
cv::Mat img = cv::imread("photo.jpg", cv::IMREAD_COLOR);
cv::Mat gray = cv::imread("photo.jpg", cv::IMREAD_GRAYSCALE);

if (img.empty()) {
    std::cerr << "无法读取图像!" << std::endl;
    return -1;
}

安全读取模式

def safe_imread(path, flags=cv2.IMREAD_COLOR):
    """安全读取,失败时返回 None 而非静默返回空矩阵"""
    img = cv2.imread(path, flags)
    if img is None:
        raise FileNotFoundError(f"无法读取图像: {path}")
    return img

注意: imread 在文件不存在时不会抛出异常,而是返回 None。务必检查返回值。


3.3 图像显示:imshow + waitKey

import cv2

img = cv2.imread("photo.jpg")

# 基本显示
cv2.imshow("窗口名称", img)
cv2.waitKey(0)           # 等待按键,0 = 无限等待
cv2.destroyAllWindows()  # 关闭所有窗口

# 带延迟的显示(视频场景)
cv2.imshow("预览", img)
key = cv2.waitKey(30)    # 等待 30ms
if key == ord('q'):      # 按 'q' 退出
    cv2.destroyAllWindows()

# 可调整大小的窗口
cv2.namedWindow("自适应", cv2.WINDOW_NORMAL)
cv2.imshow("自适应", img)
cv2.waitKey(0)
cv2.destroyAllWindows()

窗口标志

标志说明
WINDOW_NORMAL可调整大小
WINDOW_AUTOSIZE自动匹配图像大小(默认)
WINDOW_FULLSCREEN全屏模式
WINDOW_FREERATIO自由比例
WINDOW_KEEPRATIO保持比例

服务器环境替代方案

# 无法使用 imshow 时,用 Matplotlib 显示
import matplotlib.pyplot as plt

img_bgr = cv2.imread("photo.jpg")
img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)

plt.figure(figsize=(10, 6))
plt.imshow(img_rgb)
plt.title("OpenCV 图像")
plt.axis("off")
plt.tight_layout()
plt.savefig("preview.png", dpi=150)
plt.show()

3.4 图像保存:imwrite

import cv2
import numpy as np

img = cv2.imread("photo.jpg")

# 保存为不同格式
cv2.imwrite("output.png", img)         # PNG(无损)
cv2.imwrite("output.jpg", img)         # JPEG(有损)
cv2.imwrite("output.bmp", img)         # BMP
cv2.imwrite("output.tiff", img)        # TIFF

# JPEG 质量控制
cv2.imwrite("high_quality.jpg", img,
            [cv2.IMWRITE_JPEG_QUALITY, 95])
cv2.imwrite("low_quality.jpg", img,
            [cv2.IMWRITE_JPEG_QUALITY, 30])

# PNG 压缩级别(0-9,越大越慢但越小)
cv2.imwrite("compressed.png", img,
            [cv2.IMWRITE_PNG_COMPRESSION, 9])

# 保存到内存(bytes)
success, buffer = cv2.imencode(".jpg", img)
jpg_bytes = buffer.tobytes()

# 从内存读取
img_array = np.frombuffer(jpg_bytes, dtype=np.uint8)
img_decoded = cv2.imdecode(img_array, cv2.IMREAD_COLOR)

# 保存 ROI 裁剪区域
roi = img[100:300, 200:500]
cv2.imwrite("roi.jpg", roi)

3.5 图像属性

import cv2

img = cv2.imread("photo.jpg")

# 基本属性
print(f"高度 (行数):  {img.shape[0]}")      # H
print(f"宽度 (列数):  {img.shape[1]}")      # W
print(f"通道数:       {img.shape[2] if len(img.shape) == 3 else 1}")
print(f"总像素数:     {img.shape[0] * img.shape[1]}")
print(f"总元素数:     {img.size}")          # H * W * C
print(f"数据类型:     {img.dtype}")         # uint8
print(f"步长 (bytes): {img.strides}")       # (W*C, C, 1)
print(f"是否连续:     {img.flags['C_CONTIGUOUS']}")

# 像素值范围
print(f"最小值: {img.min()}, 最大值: {img.max()}, 均值: {img.mean():.1f}")

# 单像素访问(效率低,仅用于调试)
b, g, r = img[100, 200]        # y=100, x=200
print(f"像素 (200,100): R={r} G={g} B={b}")

# 批量像素访问(NumPy 方式,高效)
patch = img[100:200, 300:400]  # 裁剪区域
print(f"区域均值: B={patch[:,:,0].mean():.0f} "
      f"G={patch[:,:,1].mean():.0f} R={patch[:,:,2].mean():.0f}")

图像属性速查表

属性PythonC++
尺寸img.shape → (H, W, C)img.rows, img.cols, img.channels()
类型img.dtypeimg.type(), img.depth()
总元素img.sizeimg.total()
步长img.stridesimg.step
空检查img is Noneimg.empty()
连续性img.flags['C_CONTIGUOUS']img.isContinuous()

3.6 ROI(Region of Interest)裁剪

ROI 是图像处理的核心操作,用于聚焦感兴趣区域:

import cv2

img = cv2.imread("photo.jpg")

# NumPy 切片裁剪(最常用)
# img[y1:y2, x1:x2]
roi = img[100:400, 200:500]     # 裁剪矩形区域

# 带步长的裁剪
sub = img[::2, ::2]             # 每隔一行/列采样(降采样)
print(f"降采样: {img.shape}{sub.shape}")

# 使用坐标裁剪
x, y, w, h = 200, 100, 300, 300
roi = img[y:y+h, x:x+w]

# 修改 ROI(直接修改原图)
img[y:y+h, x:x+w] = (0, 255, 0)  # 填充绿色

# 复制 ROI(避免修改原图)
roi_copy = img[y:y+h, x:x+w].copy()

# 将一个 ROI 复制到另一位置
src_roi = img[0:100, 0:100]
img[200:300, 200:300] = src_roi
// C++ ROI
cv::Mat img = cv::imread("photo.jpg");

// 矩形 ROI
cv::Rect roi_rect(200, 100, 300, 300);  // x, y, width, height
cv::Mat roi = img(roi_rect);

// 修改 ROI
roi.setTo(cv::Scalar(0, 255, 0));  // 填充绿色

// 复制 ROI
cv::Mat roi_copy = img(roi_rect).clone();

3.7 通道操作

3.7.1 分离与合并

import cv2

img = cv2.imread("photo.jpg")

# 分离通道
b, g, r = cv2.split(img)
print(f"单通道形状: {b.shape}")       # (H, W)

# 合并通道
merged = cv2.merge([b, g, r])

# 交换红蓝通道(BGR → RGB)
rgb = cv2.merge([r, g, b])
# 或更简单的方式:
rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

# 只保留红色通道
red_only = img.copy()
red_only[:, :, 0] = 0   # 清除蓝色
red_only[:, :, 1] = 0   # 清除绿色

# 用 NumPy 索引方式(更高效)
blue_channel  = img[:, :, 0]
green_channel = img[:, :, 1]
red_channel   = img[:, :, 2]
// C++ 通道操作
std::vector<cv::Mat> channels;
cv::split(img, channels);           // 分离
cv::merge(channels, img);           // 合并

3.7.2 添加/删除 Alpha 通道

import numpy as np

# 添加 Alpha 通道(BGRA)
alpha = np.full(img.shape[:2], 255, dtype=np.uint8)  # 全不透明
bgra = cv2.merge([img[:, :, 0], img[:, :, 1], img[:, :, 2], alpha])

# 移除 Alpha 通道
bgr = cv2.cvtColor(bgra, cv2.COLOR_BGRA2BGR)

3.8 色彩空间转换

3.8.1 常用色彩空间

色彩空间通道用途
BGR蓝、绿、红OpenCV 默认
RGB红、绿、蓝显示、Matplotlib
GRAY灰度边缘检测、特征提取
HSV色相、饱和度、明度颜色过滤、物体追踪
HLS色相、亮度、饱和度光照不变分析
LAB亮度、a、b颜色迁移、肤色检测
YCrCb亮度、Cr、Cb肤色检测、压缩
LUV亮度、U、V均匀色彩空间

3.8.2 色彩空间转换代码

import cv2
import numpy as np

img = cv2.imread("photo.jpg")

# BGR → 各种色彩空间
gray  = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
hsv   = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
lab   = cv2.cvtColor(img, cv2.COLOR_BGR2Lab)
ycrcb = cv2.cvtColor(img, cv2.COLOR_BGR2YCrCb)
rgb   = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
hls   = cv2.cvtColor(img, cv2.COLOR_BGR2HLS)

# 反向转换
bgr_back = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)
bgr_back2 = cv2.cvtColor(lab, cv2.COLOR_Lab2BGR)

3.8.3 HSV 颜色过滤实战

"""
HSV 颜色过滤:检测蓝色物体
"""
import cv2
import numpy as np

img = cv2.imread("objects.jpg")
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)

# 定义蓝色的 HSV 范围
lower_blue = np.array([100, 50, 50])
upper_blue = np.array([130, 255, 255])

# 创建掩码
mask = cv2.inRange(hsv, lower_blue, upper_blue)

# 应用掩码
result = cv2.bitwise_and(img, img, mask=mask)

print(f"蓝色像素占比: {mask.sum() / mask.size * 100 / 255:.1f}%")

3.8.4 常见颜色 HSV 范围参考

颜色H 范围S 范围V 范围
红色0-10, 170-18050-25550-255
橙色10-2550-25550-255
黄色25-3550-25550-255
绿色35-8550-25550-255
青色85-10050-25550-255
蓝色100-13050-25550-255
紫色130-17050-25550-255

注意: OpenCV 中 H 通道范围是 0-179(不是 0-360),S 和 V 是 0-255。


3.9 图像基本变换

import cv2

img = cv2.imread("photo.jpg")
h, w = img.shape[:2]

# 调整大小
resized = cv2.resize(img, (640, 480))                     # 指定尺寸
resized2 = cv2.resize(img, (w//2, h//2))                  # 缩小一半
resized3 = cv2.resize(img, None, fx=0.5, fy=0.5)          # 按比例缩放

# 翻转
flip_h = cv2.flip(img, 1)    # 水平翻转
flip_v = cv2.flip(img, 0)    # 垂直翻转
flip_hv = cv2.flip(img, -1)  # 水平+垂直翻转

# 旋转(简单 90 度)
rot90  = cv2.rotate(img, cv2.ROTATE_90_CLOCKWISE)
rot180 = cv2.rotate(img, cv2.ROTATE_180)
rot270 = cv2.rotate(img, cv2.ROTATE_90_COUNTERCLOCKWISE)

# 填充边框
padded = cv2.copyMakeBorder(img, 50, 50, 50, 50,
                            cv2.BORDER_CONSTANT, value=(0, 0, 255))
reflect = cv2.copyMakeBorder(img, 50, 50, 50, 50,
                             cv2.BORDER_REFLECT)

3.10 实战:图像信息查看器

"""
image_info.py — 图像信息全面查看工具
"""
import cv2
import numpy as np
import os

def analyze_image(path):
    img = cv2.imread(path, cv2.IMREAD_UNCHANGED)
    if img is None:
        print(f"错误: 无法读取 {path}")
        return

    h, w = img.shape[:2]
    c = img.shape[2] if len(img.shape) == 3 else 1
    file_size = os.path.getsize(path)

    print(f"=== 图像分析: {os.path.basename(path)} ===")
    print(f"  尺寸:        {w} × {h}")
    print(f"  通道数:      {c}")
    print(f"  数据类型:    {img.dtype}")
    print(f"  像素范围:    [{img.min()}, {img.max()}]")
    print(f"  总元素数:    {img.size:,}")
    print(f"  文件大小:    {file_size / 1024:.1f} KB")
    print(f"  宽高比:      {w/h:.2f}")
    print(f"  百万像素:    {w * h / 1e6:.2f} MP")

    if c == 3:
        means = img.mean(axis=(0, 1))
        print(f"  通道均值:    B={means[0]:.0f} G={means[1]:.0f} R={means[2]:.0f}")

    # 色彩空间分布
    if c == 3:
        hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
        print(f"  平均亮度:    {hsv[:,:,2].mean():.0f}/255")
        print(f"  平均饱和度:  {hsv[:,:,1].mean():.0f}/255")

if __name__ == "__main__":
    import sys
    if len(sys.argv) > 1:
        analyze_image(sys.argv[1])
    else:
        print("用法: python image_info.py <图片路径>")

3.11 扩展阅读

资源链接说明
OpenCV imread 文档docs.opencv.org/4.x/d4/da8/group__imgcodecs编解码完整文档
色彩空间转换docs.opencv.org/4.x/d8/d01/group__imgproc__color所有颜色转换代码
下一章第 04 章 — 绘图与交互线条/矩形/鼠标事件

本章小结: 掌握了图像的读取、显示、保存全流程,理解了图像作为 NumPy 数组的本质,以及 ROI 裁剪、通道操作和色彩空间转换等核心概念。