首页
/ OpenCV Python 图像基本操作详解:像素访问、图像属性、ROI、通道拆合与 copyMakeBorder 边界填充

OpenCV Python 图像基本操作详解:像素访问、图像属性、ROI、通道拆合与 copyMakeBorder 边界填充

2026-09-06 17:10:52作者:尤峻淳Whitney

本文以 OpenCV 官方 Python 教程“Basic Operations on Images”为主体,系统讲解图像像素的读写、图像属性(shape/size/dtype)的读取、感兴趣区域(ROI)的选取、BGR 通道的拆分与合并,以及 cv.copyMakeBorder() 的五种边界类型。读完后你将能够熟练使用 NumPy 索引与 OpenCV API 完成日常图像处理的基本操作,并理解这些操作在 OpenCV C++ 源码中的真实实现依据。

ROI 提取示例:从图中选取足球区域并复制到另一位置的结果

教程目标与前提

该教程(py_basic_ops.markdown)要求读者学会四件事:

  • 访问并修改像素值(Access pixel values and modify them)
  • 访问图像属性(Access image properties)
  • 设置感兴趣区域 ROI(Region of Interest)
  • 拆分与合并图像通道(Split and merge images)

教程明确指出:本节几乎所有操作本质上都是 NumPy 数组操作,而非 OpenCV 专属功能。“Almost all the operations in this section are mainly related to Numpy rather than OpenCV. A good knowledge of Numpy is required to write better optimized code with OpenCV.”——扎实的 NumPy 功底是写出高效 OpenCV Python 代码的前提。示例均以 Python 交互终端(单行命令)形式给出。

访问与修改像素值

先加载一张彩色图像:

>>> import numpy as np
>>> import cv2 as cv

>>> img = cv.imread('messi5.jpg')
>>> assert img is not None, "file could not be read, check with os.path.exists()"

cv.imread() 读取成功后,图像就是一个 NumPy 数组。可以用行列坐标直接访问像素值:对 BGR 彩色图像,返回一个包含蓝、绿、红三个分量的一维数组;对灰度图像,则只返回对应的灰度强度值:

>>> px = img[100,100]
>>> print( px )
[157 166 200]

# accessing only blue pixel
>>> blue = img[100,100,0]
>>> print( blue )
157

注意 OpenCV 与 NumPy 的索引顺序是 img[行, 列, 通道],即 [y, x, c],这与某些库的 [x, y, c] 习惯不同。img[100,100,0] 取的是第 100 行、第 100 列像素的蓝色分量(BGR 顺序中第 0 通道)。

以完全相同的方式可以修改像素值:

>>> img[100,100] = [255,255,255]
>>> print( img[100,100] )
[255 255 255]

性能警告(原文强调):NumPy 是面向快速数组运算优化的库,逐像素地访问和修改每个像素会非常慢,教程明确不推荐("very slow and it is discouraged")。批量操作应使用切片、广播等向量化手段,见下文 ROI 与通道处理部分。

访问图像属性

图像属性包括行数、列数、通道数、数据类型、总像素数等。

shapeimg.shape 返回一个元组 (行数, 列数, 通道数),通道数仅在彩色图像时出现:

>>> print( img.shape )
(342, 548, 3)

注:如果图像是灰度图,返回的元组只有行数和列数两项(如 (342, 548))。因此 shape 的维度数是判断图像是灰度还是彩色的简便方法。

size:总像素数通过 img.size 获得(行 × 列 × 通道):

>>> print( img.size )
562248

dtype:图像数据类型通过 img.dtype 获得:

>>> print( img.dtype )
uint8

注:img.dtype 在调试中非常重要——OpenCV-Python 代码中出现的大量错误源于数据类型无效(例如把 float 型矩阵传给了只接受 uint8 的函数)。

图像 ROI(感兴趣区域)

实际应用中经常需要只处理图像的某个区域。典型场景是人脸/眼睛检测:先对整幅图做人脸检测,得到人脸后,只选取人脸区域在其中搜索眼睛,而不用搜索整幅图。这既提高了准确率(眼睛总是在脸上),也提升了性能(搜索范围更小)。

ROI 同样通过 NumPy 索引获得。下面的示例选取图中的“足球”区域,并把它复制到图像中的另一个位置(先切片取出,再赋值过去):

>>> ball = img[280:340, 330:390]
>>> img[273:333, 100:160] = ball

第一行用切片 img[280:340, 330:390] 取出 60×60 的球区域;第二行将其赋值到 img[273:333, 100:160] 指定的目标区域,完成一次“区域复制”。

ROI 复制结果:足球被复制到图像左下区域

从底层看,这种切片赋值依赖 NumPy 视图(view)语义:ball 与原图共享内存,对它的修改会反映到 img 上——这既是方便之处,也意味着赋值方向(img[...] = ball)与预期一致时才能安全使用。

拆分与合并图像通道

有时需要单独处理 B、G、R 中的某一个通道(例如只操作红色),此时需要将 BGR 图像拆分成单通道;反之也常需要把独立通道合并回 BGR 图像。

方式一:使用 OpenCV 的 split / merge

>>> b,g,r = cv.split(img)
>>> img = cv.merge((b,g,r))

方式二:直接使用 NumPy 索引取单通道

>>> b = img[:,:,0]

性能提示:如果你的目标只是修改某一个通道,根本不需要先拆分。例如要把所有红色像素置零,直接用 NumPy 索引更快:

>>> img[:,:,2] = 0

警告(原文强调):cv.split() 是耗时操作,仅在确实需要独立通道对象(例如对单个通道做滤波、统计)时才使用;否则优先用 NumPy 索引。

为图像创建边界:cv.copyMakeBorder()

若要给图像加一圈“相框”式的边界,可以使用 cv.copyMakeBorder()。但它在工程中有更广泛的应用:卷积操作的边界扩展、零填充(zero padding)等——从源码结构看,它本身就是 OpenCV 内部许多算法的基础设施,例如模板匹配在补齐模板块时调用它(templmatch.cpp 第 201 行附近),CLAHE 用 BORDER_REFLECT_101 扩展图像到 tile 边界(clahe.cpp 第 381 行),warpAffineBORDER_WRAP 处理角点(imgwarp.cpp 第 3130 行),轮廓检测与泛洪填充则用 BORDER_CONSTANT | BORDER_ISOLATED 隔离 ROI 边界(contours_new.cpp 第 703 行、floodfill.cpp 第 510 行)。

函数参数

copyMakeBorder() 的完整签名(见 core.hpp):

void copyMakeBorder(InputArray src, OutputArray dst,
                    int top, int bottom, int left, int right,
                    int borderType, const Scalar& value = Scalar() );

参数说明:

  • src — 输入图像
  • top, bottom, left, right — 上、下、左、右四个方向各扩展的像素数
  • borderType — 边界类型标志
  • value — 当 borderType 为 cv.BORDER_CONSTANT 时的边界颜色

输出图像 dst 的尺寸为 Size(src.cols+left+right, src.rows+top+bottom),与 src 类型相同。

边界类型 BorderTypes

教程中列出的五种边界类型,与 OpenCV 源码中的枚举定义一一对应,枚举及各类型的 ASCII 示意图在 base.hpp

enum BorderTypes {
    BORDER_CONSTANT    = 0, //!< `iiiiii|abcdefgh|iiiiiii`  with some specified `i`
    BORDER_REPLICATE   = 1, //!< `aaaaaa|abcdefgh|hhhhhhh`
    BORDER_REFLECT     = 2, //!< `fedcba|abcdefgh|hgfedcb`
    BORDER_WRAP        = 3, //!< `cdefgh|abcdefgh|abcdefg`
    BORDER_REFLECT_101 = 4, //!< `gfedcb|abcdefgh|gfedcba`
    ...
    BORDER_DEFAULT     = BORDER_REFLECT_101
};

各类型含义(| 为原图边界):

  • cv.BORDER_CONSTANT — 用指定常量色填充边界(颜色由 value 参数给出),形如 iiiiii|abcdefgh|iiiiiii
  • cv.BORDER_REPLICATE — 沿边缘无限复制最后一个像素,形如 aaaaaa|abcdefgh|hhhhhhh
  • cv.BORDER_REFLECT — 边界元素的镜面反射,形如 fedcba|abcdefgh|hgfedcb
  • cv.BORDER_REFLECT_101(即 cv.BORDER_DEFAULT,默认值)— 与 REFLECT 类似但略有不同,形如 gfedcb|abcdefgh|gfedcba
  • cv.BORDER_WRAP — 环绕式边界,形如 cdefgh|abcdefgh|abcdefg

另外源码枚举中还定义了 BORDER_TRANSPARENT(视越界像素为透明,形如 uvwxyz|abcdefgh|ijklmno)和 BORDER_ISOLATED(标志位 16,插值/外推被限制在 ROI 边界内——即上文的 note:当 src 本身是更大图像的一个 ROI 时,copyMakeBorder 默认会尝试利用 ROI 外部的真实像素来构造边界,加上 BORDER_ISOLATED 可禁用该行为)。

完整示例代码

下面示例对同一图像分别生成五种类型的边界并用 matplotlib 展示(边界宽度均为 10 像素):

import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt

BLUE = [255,0,0]

img1 = cv.imread('opencv-logo.png')
assert img1 is not None, "file could not be read, check with os.path.exists()"

replicate = cv.copyMakeBorder(img1,10,10,10,10,cv.BORDER_REPLICATE)
reflect = cv.copyMakeBorder(img1,10,10,10,10,cv.BORDER_REFLECT)
reflect101 = cv.copyMakeBorder(img1,10,10,10,10,cv.BORDER_REFLECT_101)
wrap = cv.copyMakeBorder(img1,10,10,10,10,cv.BORDER_WRAP)
constant= cv.copyMakeBorder(img1,10,10,10,10,cv.BORDER_CONSTANT,value=BLUE)

plt.subplot(231),plt.imshow(img1,'gray'),plt.title('ORIGINAL')
plt.subplot(232),plt.imshow(replicate,'gray'),plt.title('REPLICATE')
plt.subplot(233),plt.imshow(reflect,'gray'),plt.title('REFLECT')
plt.subplot(234),plt.imshow(reflect101,'gray'),plt.title('REFLECT_101')
plt.subplot(235),plt.imshow(wrap,'gray'),plt.title('WRAP')
plt.subplot(236),plt.imshow(constant,'gray'),plt.title('CONSTANT')

plt.show()

五种边界类型(REPLICATE/REFLECT/REFLECT_101/WRAP/CONSTANT)的对比效果

注意两点:

  1. 示例中 value=BLUE[255,0,0]——因为 OpenCV 读取的图像是 BGR 顺序,[255,0,0] 表示的是蓝色(虽然变量名沿用教程习惯)。
  2. 结果图由 matplotlib 显示,matplotlib 使用 RGB 顺序,因此红色与蓝色通道会互换,直接 imshow BGR 图像时颜色可能“看起来反了”。

小结与工程建议

结合本教程与源码可以归纳出以下实践准则:

  • 优先 NumPy 索引,慎用 cv.split/cv.merge:单通道读取 img[:,:,0]、单通道写入 img[:,:,2]=0 都更快;cv.split() 开销大,仅在需要独立通道对象时使用。
  • 避免 Python 级逐像素循环:像素读写用切片/广播完成,性能差异显著。
  • shape 三元组判断彩色/灰度(rows, cols) 为灰度,(rows, cols, 3) 为彩色。
  • dtype 是首要调试对象:OpenCV-Python 大量异常源于数据类型不匹配,报错时先检查 img.dtype
  • copyMakeBorder 是通用垫底工具:边界宽度四参数 + 五种(及以上)borderType,既可用作视觉“相框”,也是卷积、模板匹配、CLAHE 等算法内部零填充/边界外推的标准手段;需要隔离 ROI 外部像素时记得 BORDER_ISOLATED

延伸阅读:同系列教程中的图像算术运算 py_image_arithmetics.markdown 与优化 py_optimization.markdown,以及 borderInterpolatecopyMakeBorder 文档中的 @sa 关联函数,用于单像素边界外推)在 core.hpp 中的声明。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
915
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388