OpenCV Python 图像基本操作详解:像素访问、图像属性、ROI、通道拆合与 copyMakeBorder 边界填充
本文以 OpenCV 官方 Python 教程“Basic Operations on Images”为主体,系统讲解图像像素的读写、图像属性(shape/size/dtype)的读取、感兴趣区域(ROI)的选取、BGR 通道的拆分与合并,以及 cv.copyMakeBorder() 的五种边界类型。读完后你将能够熟练使用 NumPy 索引与 OpenCV API 完成日常图像处理的基本操作,并理解这些操作在 OpenCV C++ 源码中的真实实现依据。
教程目标与前提
该教程(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 与通道处理部分。
访问图像属性
图像属性包括行数、列数、通道数、数据类型、总像素数等。
shape:img.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] 指定的目标区域,完成一次“区域复制”。
从底层看,这种切片赋值依赖 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 行),warpAffine 用 BORDER_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()
注意两点:
- 示例中
value=BLUE即[255,0,0]——因为 OpenCV 读取的图像是 BGR 顺序,[255,0,0]表示的是蓝色(虽然变量名沿用教程习惯)。 - 结果图由 matplotlib 显示,matplotlib 使用 RGB 顺序,因此红色与蓝色通道会互换,直接
imshowBGR 图像时颜色可能“看起来反了”。
小结与工程建议
结合本教程与源码可以归纳出以下实践准则:
- 优先 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,以及 borderInterpolate(copyMakeBorder 文档中的 @sa 关联函数,用于单像素边界外推)在 core.hpp 中的声明。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00

