OpenCV 图像金字塔教程:在 Web 端用 cv.pyrDown() 与 cv.pyrUp() 做多尺度处理
【免费下载链接】opencvOpen Source Computer Vision Library项目地址: https://gitcode.com/GitHub_Trending/opencv31/opencv
本篇围绕 OpenCV 官方 JS 教程中的“Image Pyramids(图像金字塔)”一文展开:先讲清图像金字塔为何存在、高斯金字塔与拉普拉斯金字塔的构造原理,再给出浏览器环境中cv.pyrDown()(降采样)与cv.pyrUp()(升采样)的完整参数说明和可运行的 JavaScript 代码,并结合 OpenCV 仓库中modules/imgproc/src/pyramids.cpp的源码实现,剖析 5×5 高斯核、尺寸约束、支持的像素类型与后端加速路径,帮助你把多尺度图像处理的原理与实战一次打通。
为什么需要图像金字塔
在常规图像处理中,我们通常使用固定尺寸的一幅图像工作。但在某些场景下,需要对同一幅图像以不同分辨率处理:最典型的例子是目标检测——当在图像中搜索人脸等目标时,你无法预知目标会以多大的尺度出现。解决办法是构造一组同一图像、不同分辨率的图像集合,并在每一层上分别搜索。这个集合就是图像金字塔(Image Pyramids):若把最高分辨率的图像放在底部、最低分辨率的放在顶部堆叠起来,整体形状正像一座金字塔。
金字塔分为两种:
- 高斯金字塔(Gaussian Pyramid)——通过“先平滑、再抽取偶数行列”逐层缩小;
- 拉普拉斯金字塔(Laplacian Pyramid)——由相邻两层高斯金字塔的差值构成,形似边缘图。
高斯金字塔的构造
高斯金字塔中,上一层(低分辨率)由下一层(高分辨率)经过“删除相邻行/列”的方式生成;上一层的每个像素由下一层 5 个像素按高斯权重贡献而成。这样一幅 M×N 的图像会变成 M/2×N/2,面积缩小为原来的四分之一——这样一次尺寸减半称为一个Octave(倍频程)。向上每升一层分辨率继续减半,向下(扩展方向)则每层面积变为 4 倍。高斯金字塔正是通过cv.pyrDown()和cv.pyrUp()两个函数获得。
拉普拉斯金字塔的构造
拉普拉斯金字塔从高斯金字塔推导而来,OpenCV 没有为其提供专门的独立函数。拉普拉斯金字塔的每一层图像近似一张“只有边缘”的图:大多数元素为零,因此常用于图像压缩。某一层拉普拉斯金字塔 = 该层的高斯金字塔图像 − 其上层高斯金字塔图像经扩展(pyrUp)后的版本。也就是说,你只需组合pyrDown与pyrUp加一次逐像素减法即可手工构造。
降采样:cv.pyrDown()
函数签名与参数
在 JS 环境中,函数原型为:
cv.pyrDown(src, dst, dstsize = new cv.Size(0, 0), borderType = cv.BORDER_DEFAULT)参数说明(与 C++ 接口cv::pyrDown完全对应):
| 参数 | 说明 |
|---|---|
src | 输入图像 |
dst | 输出图像;具有指定大小,类型与src相同 |
dstsize | 输出图像大小;默认(0,0)表示自动计算为Size((src.cols+1)/2, (src.rows+1)/2) |
borderType | 像素外推方式,见cv.BorderTypes;不支持cv.BORDER_CONSTANT |
dstsize并非可以任意指定。无论取何值,都必须满足约束:
|dstsize.width * 2 - src.cols| <= 2|dstsize.height * 2 - src.rows| <= 2
即输出尺寸必须与“源尺寸的一半”相差不超过 1 个像素,否则底层实现会直接触发断言失败。
JS 代码示例
以下代码取自教程配套的在线示例页面 js_pyramids_pyrDown.html。该页面已准备好canvasInput(默认加载 lena.jpg,也可通过文件选择器换图)和canvasOutput两个<canvas>元素,点击 “Try it” 按钮即可执行,textarea 中的代码也可以自由修改实验:
let src = cv.imread('canvasInput'); let dst = new cv.Mat(); // You can try more different parameters cv.pyrDown(src, dst, new cv.Size(0, 0), cv.BORDER_DEFAULT); cv.imshow('canvasOutput', dst); src.delete(); dst.delete();注意两个内存管理细节:cv.imread与new cv.Mat()在堆上分配,使用完毕后必须delete()释放,避免 WebAssembly 堆泄漏。
底层实现:5×5 高斯核与“抽取偶数行列”
结合 modules/imgproc/include/opencv2/imgproc.hpp 中pyrDown的接口文档与 modules/imgproc/src/pyramids.cpp 的实现可以看到:pyrDown执行的是高斯金字塔构造中的降采样步骤——先与一个 5×5 高斯核做卷积:
1/256 * [ 1 4 6 4 1 4 16 24 16 4 6 24 36 24 6 4 16 24 16 4 1 4 6 4 1 ]然后丢弃偶数行和偶数列完成下采样。源码中pyrDown_模板函数(约 L880 起)用环形缓冲区先做水平方向卷积+隔列抽取,再做垂直方向卷积+隔行抽取,并按通道数 1/2/3/4 分别走标量或向量化(SSE/NEON)路径,外层还通过cv::parallel_for_按行范围多线程并行(cv::PyrDownInvoker)。
入口函数cv::pyrDown(L1266 起)还揭示了几个实用事实:
- 首先
CV_Assert(borderType != BORDER_CONSTANT),这就是“不支持 BORDER_CONSTANT”的原因——常量填充会破坏高斯卷积的对称性,接口文档因此明确排除该模式; - 默认
dstsize为空时计算为Size((src.cols+1)/2, (src.rows+1)/2),即向上取整,所以 400×400 的输入会得到 200×200,而 401×400 会得到 201×200; - 支持的深度为
CV_8U / CV_16S / CV_16U / CV_32F / CV_64F,其他类型会抛出StsUnsupportedFormat错误; - 若输出为 UMat 且设备支持,会优先走 OpenCL 后端
ocl_pyrDown(pyrDownkernel);同时预留了 HAL 扩展点(cv_hal_pyrdown)与 IPP 加速路径。
升采样:cv.pyrUp()
函数签名与参数
JS 环境中函数原型为:
cv.pyrUp(src, dst, dstsize = new cv.Size(0, 0), borderType = cv.BORDER_DEFAULT)| 参数 | 说明 |
|---|---|
src | 输入图像 |
dst | 输出图像;具有指定大小,类型与src相同 |
dstsize | 输出图像大小;默认(0,0)表示自动计算为Size(src.cols*2, src.rows*2) |
borderType | 像素外推方式;仅支持cv.BORDER_DEFAULT |
dstsize的约束条件是:
|dstsize.width - src.cols*2| <= (dstsize.width mod 2)|dstsize.height - src.rows*2| <= (dstsize.height mod 2)
即输出尺寸可以比“源尺寸的两倍”多出 0 或 1 个像素(奇数尺寸场景)。
JS 代码示例
以下代码同样取自教程配套的在线示例页面 js_pyramids_pyrUp.html,页面结构与 pyrDown 示例一致(canvasInput/canvasOutput+ 可编辑代码区):
let src = cv.imread('canvasInput'); let dst = new cv.Mat(); // You can try more different parameters cv.pyrUp(src, dst, new cv.Size(0, 0), cv.BORDER_DEFAULT); cv.imshow('canvasOutput', dst); src.delete(); dst.delete();可以看到升采样后的图像在视觉上更“软”:因为它是零填充+模糊的结果,并不包含新的高频细节,只适合做金字塔层级间的过渡,而非真正意义上的超分辨率放大。
底层实现:注入零行零列,再用同一核×4 卷积
从接口文档与pyrUp_模板函数(modules/imgproc/src/pyramids.cpp L1038 起)可以看出,pyrUp执行高斯金字塔构造的升采样步骤,但也可以用来构造拉普拉斯金字塔:先把源图像注入偶数的零行和零列(即两像素之间插入一个零像素,图像面积变为 4 倍),再与pyrDown相同的 5×5 高斯核相乘 4 后卷积。源码中可见src[x]*6 + src[x+cn]*2与(src[x] + src[x+cn])*4这类系数组合,正是该核经 4 倍放大并简化后的水平/垂直分解卷积系数(PU_SZ=3 的行缓存只保留必要的 3 行邻域)。
入口函数cv::pyrUp(L1374 起)的事实要点:
CV_Assert(borderType == BORDER_DEFAULT)——升采样只接受默认边界模式(实现内部实际按BORDER_REFLECT_101语义处理,见 L1077 的borderInterpolate调用),传入其他模式会直接断言失败;- 默认输出尺寸为
Size(src.cols*2, src.rows*2),比pyrDown的向上取整更直接——pyrDown 是 5×5 卷积所以奇数尺寸要进位,而 pyrUp 是 2 倍拉伸,奇数目标尺寸允许相差 1 像素; - 支持的深度同样是
CV_8U / CV_16S / CV_16U / CV_32F / CV_64F,固定点类型用FixPtCast、浮点类型用FltCast完成卷积中间值的溢出安全转换; - 编译启用 IPP 时(
HAVE_IPP且 IPP >= 8.1),对 8UC1/8UC3/32FC1/32FC3 的默认情形会优先调用ippiPyrUp_Gauss5x5_*硬件优化例程;UMat 场景同样有ocl_pyrUp的 OpenCL 路径(含pyrUp_cols2与pyrUp_unrolled两个 kernel)。
相关接口与配套示例
- buildPyramid:modules/imgproc/include/opencv2/imgproc.hpp 中定义了
cv::buildPyramid(src, dst, maxlevel, borderType = BORDER_DEFAULT),它把dst[0] == src之后递归地逐层应用pyrDown,一次性构造出maxlevel + 1层的高斯金字塔向量,适合需要整组金字塔层级的批量场景。 - C++ 交互示例:仓库提供了同一套函数的 C++ 演示程序 samples/cpp/tutorial_code/ImgProc/Pyramids/Pyramids.cpp,加载图像后按
i键调用pyrUp(src, src, Size(src.cols*2, src.rows*2))放大、按o键调用pyrDown(src, src, Size(src.cols/2, src.rows/2))缩小,是“图像变焦”玩法的最小实现。注意其中对pyrDown传入了整数除法得到的src.cols/2, src.rows/2——只有满足上文尺寸约束(奇数尺寸向下取整)时才合法。 - 性能测试:
modules/imgproc/perf/perf_pyramids.cpp对pyrDown、pyrUp覆盖多种尺寸与像素类型组合的基准测试,其中DISABLED_pyrDown_ovx用例验证了pyrDown(src, dst, cv::Size(), BORDER_REPLICATE)这一“默认尺寸 + 非默认边界”的组合。
小结
| 函数 | 作用 | 默认输出尺寸 | 边界模式 | 核心算法 |
|---|---|---|---|---|
cv.pyrDown() | 模糊 + 降采样(金字塔上行一层) | (src.cols+1)/2, (src.rows+1)/2 | 任意,但BORDER_CONSTANT不支持 | 1/256 归一 5×5 高斯卷积后抽取偶数行列 |
cv.pyrUp() | 升采样 + 模糊(金字塔下行一层) | src.cols*2, src.rows*2 | 仅BORDER_DEFAULT | 注入零行零列后以同核×4 卷积 |
掌握这两个函数后,你已经在 Web 端具备了完整的多尺度处理工具箱:用pyrDown循环可搭出高斯金字塔用于多尺度检测;用“本层高斯层 − pyrUp(上层)”的差值可手工搭出拉普拉斯金字塔用于图像融合与压缩类任务。参数细节以本仓库 modules/imgproc/include/opencv2/imgproc.hpp 的接口文档为准,行为差异可通过 doc/js_tutorials/js_assets/js_pyramids_pyrDown.html 与 doc/js_tutorials/js_assets/js_pyramids_pyrUp.html 两个在线示例边改边验。
【免费下载链接】opencvOpen Source Computer Vision Library项目地址: https://gitcode.com/GitHub_Trending/opencv31/opencv
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考