OpenCV快速入门:图像读取、绘制与几何变换完整指南

引言

OpenCV(Open Source Computer Vision Library)是计算机视觉领域事实标准级的开源工具库,不仅提供了从低阶像素操作到高阶视觉算法的全链路支持,还能跨平台、跨语言(C++/Python/Java等)调用,速度极快(底层为优化后的C/C++)。

本文聚焦传统CV的第一块基石:OpenCV基础IO、绘图、几何变换,所有代码都附有清晰注释和使用场景,看完就能直接上手写实用脚本。

📂 所属阶段:第一阶段 — 图像处理基石(传统 CV 篇)
🔗 相关章节:CV 概览与数字图像基础 · 图像增强与滤波


1. 环境搭建:5分钟上手OpenCV

1.1 库的选择与安装

OpenCV有3个主流Python包,根据你的需求选一个即可:

# ✅ 日常开发首选(包含常用功能)
pip install opencv-python

# ✅ 进阶/科研推荐(额外集成SIFT/SURF等非自由/实验算法)
pip install opencv-contrib-python

# ✅ 服务器环境专用(无GUI依赖,节省资源)
pip install opencv-python-headless

1.2 一键验证安装

复制这段代码运行,没问题就说明环境搭好了:

import cv2
import numpy as np
import matplotlib.pyplot as plt

def check_opencv_env():
    """检查OpenCV核心功能是否正常"""
    # 1. 打印版本
    print(f"✅ OpenCV版本: {cv2.__version__}")
    
    # 2. 检查NumPy兼容性(OpenCV图像本质是NumPy数组)
    print(f"✅ NumPy版本: {np.__version__}")
    
    # 3. 尝试创建空白画布(基础绘图+内存分配验证)
    test_canvas = np.zeros((200, 200, 3), dtype=np.uint8)
    cv2.putText(test_canvas, "OpenCV Ready!", (20, 110), 
                cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2)
    print("✅ 画布创建成功")
    
    # 4. 检查Matplotlib字体(用于Jupyter显示中文)
    plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'Arial Unicode MS']
    plt.rcParams['axes.unicode_minus'] = False
    print("✅ Matplotlib中文配置完成")

check_opencv_env()

2. 图像IO:读取→显示→保存的全套逻辑

💡 核心前置知识点

OpenCV存储图像的坑点必须先记牢:

  1. 彩色图像默认是 BGR格式(和Matplotlib、PIL的RGB相反)
  2. 图像数据本质是 NumPy uint8数组,取值范围 0-255
  3. 形状顺序是 (高度, 宽度, 通道数),不是直觉上的宽×高

2.1 图像读取:处理加载失败的场景

很多新手会卡在这里——因为文件路径写错但没加判断!

import cv2

# 三种常用读取模式
MODES = {
    "COLOR": cv2.IMREAD_COLOR,     # 彩色(默认,忽略alpha)
    "GRAY": cv2.IMREAD_GRAYSCALE,  # 灰度
    "UNCHANGED": cv2.IMREAD_UNCHANGED # 含alpha通道(PNG/GIF支持)
}

def load_image(image_path: str, mode: str = "COLOR"):
    """安全的图像加载函数"""
    img = cv2.imread(image_path, MODES.get(mode.upper(), MODES["COLOR"]))
    if img is None:
        raise FileNotFoundError(f"❌ 图像路径不存在或格式不支持: {image_path}")
    print(f"✅ 图像加载成功 | 形状: {img.shape} | 类型: {img.dtype}")
    return img

# 使用示例
# img = load_image("test.jpg", "GRAY")

2.2 图像显示:两种场景选对工具

场景1:本地脚本/调试(用OpenCV原生窗口)

原生窗口支持键盘、鼠标交互,适合快速测试:

def show_opencv_window(img, window_name: str = "OpenCV Window", wait_ms: int = 0):
    """
    显示OpenCV原生窗口
    wait_ms: 等待毫秒数,0=无限等待,直到按任意键
    """
    cv2.imshow(window_name, img)
    key = cv2.waitKey(wait_ms) & 0xFF  # 兼容64位系统
    cv2.destroyAllWindows()  # 退出后清理窗口,避免内存泄漏
    return key  # 返回按键ASCII码,可用于交互

# 使用示例:按ESC键退出,按S键保存
# key = show_opencv_window(img)
# if key == 27:  # ESC
#     print("退出")
# elif key == ord('s'):
#     cv2.imwrite("saved.jpg", img)

场景2:Jupyter Notebook/Lab(用Matplotlib)

原生窗口在Notebook里体验极差,用Matplotlib要注意BGR转RGB

import matplotlib.pyplot as plt

def show_plt(img, title: str = "图像", figsize: tuple = (8, 6), cmap: str = None):
    """
    Matplotlib显示OpenCV图像(自动处理BGR→RGB)
    cmap: 灰度图用'gray',彩色图留空
    """
    plt.figure(figsize=figsize)
    # 处理通道
    if len(img.shape) == 3:
        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    else:
        cmap = 'gray' if cmap is None else cmap
    # 显示
    plt.imshow(img, cmap=cmap)
    plt.title(title, fontsize=14)
    plt.axis('off')  # 隐藏坐标轴
    plt.tight_layout()  # 自动调整布局
    plt.show()

# 使用示例
# show_plt(img, "测试图像", figsize=(10, 7))

2.3 图像保存:控制压缩质量

def save_image(img, output_path: str, jpg_quality: int = 95, png_compression: int = 3):
    """
    智能保存图像,自动根据后缀调整参数
    jpg_quality: JPEG质量,0-100,越高越清晰
    png_compression: PNG压缩级别,0-9,越高文件越小但保存越慢
    """
    ext = output_path.split('.')[-1].lower()
    if ext in ['jpg', 'jpeg']:
        params = [cv2.IMWRITE_JPEG_QUALITY, jpg_quality]
    elif ext == 'png':
        params = [cv2.IMWRITE_PNG_COMPRESSION, png_compression]
    else:
        params = []
    
    success = cv2.imwrite(output_path, img, params)
    if success:
        print(f"✅ 图像保存成功: {output_path}")
    else:
        raise IOError(f"❌ 图像保存失败,请检查路径权限或格式: {output_path}")
    return success

3. 基础绘图:给图像加标注、画ROI(感兴趣区域)

OpenCV的绘图函数参数高度统一,记通用模板就行:

cv2.func(图像, 位置参数, 颜色(BGR), 线宽, 线型, 偏移)

🔍 注意:

  • 线宽 -1 表示填充
  • 颜色顺序是 (蓝, 绿, 红)
  • 所有坐标都是 (x, y) = (宽度方向, 高度方向)

3.1 常用图形绘制代码

import cv2
import numpy as np

# 创建400x600的白色画布(宽600,高400)
canvas = 255 * np.ones((400, 600, 3), dtype=np.uint8)

# ---------------------- 1. 直线 ----------------------
cv2.line(canvas, (50, 50), (550, 50), (0, 200, 0), 2)  # 绿色实线
cv2.line(canvas, (50, 100), (550, 300), (200, 0, 0), 3, cv2.LINE_AA)  # 蓝色抗锯齿粗线

# ---------------------- 2. 矩形 ----------------------
cv2.rectangle(canvas, (80, 120), (220, 220), (0, 0, 200), 2)  # 空心红框(ROI常用)
cv2.rectangle(canvas, (280, 120), (420, 220), (0, 200, 200), -1)  # 填充黄框

# ---------------------- 3. 圆形 ----------------------
cv2.circle(canvas, (500, 170), 50, (200, 0, 200), 3)  # 空心紫圆
cv2.circle(canvas, (150, 320), 30, (200, 200, 0), -1)  # 填充青圆

# ---------------------- 4. 椭圆 ----------------------
# 参数:中心, (长轴半径, 短轴半径), 旋转角度, 起始角度, 结束角度, ...
cv2.ellipse(canvas, (350, 320), (60, 30), 30, 0, 360, (100, 100, 100), 2)  # 倾斜空心灰椭圆
cv2.ellipse(canvas, (350, 320), (60, 30), 30, 0, 180, (100, 0, 100), -1)  # 倾斜填充半椭圆

# ---------------------- 5. 多边形 ----------------------
# 注意:多边形的点必须是numpy.int32的二维数组,形状为(n, 1, 2)
pts = np.array([[450, 260], [500, 230], [550, 260], [530, 310], [470, 310]], np.int32)
pts = pts.reshape((-1, 1, 2))  # 转换为OpenCV要求的格式
cv2.polylines(canvas, [pts], True, (255, 100, 0), 2)  # 封闭五边形
cv2.fillPoly(canvas, [pts], (255, 100, 0, 100))  # 半透明填充(如果画布有alpha通道)

# ---------------------- 6. 文字 ----------------------
# 支持的字体有限,推荐用cv2.FONT_HERSHEY_SIMPLEX
cv2.putText(canvas, "OpenCV Drawing Demo", (50, 370), 
            cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 0), 2, cv2.LINE_AA)

# 显示结果
show_plt(canvas, "绘图综合示例")

4. 几何变换:图像缩放、旋转、平移、仿射

几何变换的核心是变换矩阵,OpenCV提供了两个通用函数:

  1. cv2.warpAffine():处理2×3变换矩阵(仿射变换,缩放/旋转/平移/剪切)
  2. cv2.warpPerspective():处理3×3变换矩阵(透视变换,后续章节讲)

4.1 图像缩放:保持宽高比的实用函数

def resize(img, target_w=None, target_h=None, interp=cv2.INTER_AREA):
    """
    智能缩放:支持固定尺寸、固定比例、保持宽高比
    interp: 插值方法
        - 缩小推荐:cv2.INTER_AREA(区域插值,避免锯齿)
        - 放大推荐:cv2.INTER_CUBIC(三次样条,4x4邻域,慢但清晰)
        - 快速放大:cv2.INTER_LINEAR(双线性,2x2邻域)
    """
    h, w = img.shape[:2]
    
    # 自动计算缩放比例
    if target_w and target_h:
        # 按比例缩放不超出目标尺寸
        scale = min(target_w / w, target_h / h)
        new_w, new_h = int(w * scale), int(h * scale)
    elif target_w:
        new_w = target_w
        new_h = int(h * (target_w / w))
    elif target_h:
        new_h = target_h
        new_w = int(w * (target_h / h))
    else:
        raise ValueError("❌ 必须指定target_w或target_h或两者")
    
    return cv2.resize(img, (new_w, new_h), interpolation=interp)

4.2 图像旋转:不裁剪边缘的高级版

普通的旋转会把边缘切掉,这个函数会自动计算新画布尺寸保留全图:

def rotate(img, angle, center=None, scale=1.0):
    """
    旋转图像,支持不裁剪边缘
    angle: 旋转角度(正=逆时针,负=顺时针)
    """
    h, w = img.shape[:2]
    
    # 默认中心旋转
    if center is None:
        center = (w // 2, h // 2)
    
    # 获取旋转矩阵
    M = cv2.getRotationMatrix2D(center, angle, scale)
    
    # ---------------------- 不裁剪边缘的核心逻辑 ----------------------
    # 计算旋转后的画布尺寸
    cos = np.abs(M[0, 0])
    sin = np.abs(M[0, 1])
    new_w = int((h * sin) + (w * cos))
    new_h = int((h * cos) + (w * sin))
    
    # 调整旋转矩阵的平移部分,把图像移到新画布中心
    M[0, 2] += (new_w // 2) - center[0]
    M[1, 2] += (new_h // 2) - center[1]
    # --------------------------------------------------------------------
    
    # 应用旋转(背景默认黑色,可改M[2,2]相关,但warpAffine只接受2×3矩阵,改背景用borderValue)
    return cv2.warpAffine(img, M, (new_w, new_h), borderValue=(255, 255, 255))  # 白色背景

4.3 仿射变换:三点确定一个平行变换

仿射变换会保持直线的平行性(比如正方形变平行四边形),需要给定原图像的3个点目标图像的对应3个点

def affine_transform(img, src_pts, dst_pts, borderValue=(255, 255, 255)):
    """
    应用仿射变换
    src_pts/dst_pts: numpy数组,形状为(3, 2),(x, y)格式
    """
    h, w = img.shape[:2]
    # 获取2×3仿射变换矩阵
    M = cv2.getAffineTransform(src_pts.astype(np.float32), dst_pts.astype(np.float32))
    # 应用变换
    return cv2.warpAffine(img, M, (w, h), borderValue=borderValue)

# 使用示例:把图像左上角3个点变换成倾斜的
# src_pts = np.array([[0, 0], [w-1, 0], [0, h-1]], np.float32)  # 原图像左上角3个角点
# dst_pts = np.array([[50, 50], [w-100, 0], [0, h-50]], np.float32)  # 目标点
# transformed = affine_transform(img, src_pts, dst_pts)

5. 实战小工具:10行代码写一个图片批量缩放器

结合前面的load_imageresize函数,写一个批量处理文件夹内图片的工具:

import os
from tqdm import tqdm  # 进度条库,需要先pip install tqdm

def batch_resize(input_dir: str, output_dir: str, target_w=None, target_h=None, interp=cv2.INTER_AREA):
    """
    批量缩放文件夹内的所有图片
    """
    # 创建输出文件夹
    os.makedirs(output_dir, exist_ok=True)
    
    # 获取所有图片文件
    img_exts = ['.jpg', '.jpeg', '.png', '.bmp', '.tiff']
    img_files = [f for f in os.listdir(input_dir) if os.path.splitext(f)[-1].lower() in img_exts]
    
    # 批量处理
    for img_file in tqdm(img_files, desc="批量缩放中"):
        try:
            # 加载
            img = load_image(os.path.join(input_dir, img_file))
            # 缩放
            resized = resize(img, target_w=target_w, target_h=target_h, interp=interp)
            # 保存
            save_image(resized, os.path.join(output_dir, img_file))
        except Exception as e:
            print(f"⚠️ 处理失败 {img_file}: {e}")

# 使用示例:把input文件夹内的所有图片缩放到宽800px,保持宽高比
# batch_resize("input", "output_800w", target_w=800)

相关教程