资讯详情

资讯详情

OpenCV 矩阵掩膜操作详解:手写核函数与 filter2D 的完整实战

OpenCV 矩阵掩膜操作详解手写核函数与 filter2D 的完整实战【免费下载链接】opencvOpen Source Computer Vision Library项目地址: https://gitcode.com/GitHub_Trending/opencv31/opencv掩膜mask亦称 kernel 卷积核操作是图像处理中最基础也最高频的局部运算按照一个掩膜矩阵对每个像素做加权平均从而控制邻域像素对新像素值的影响。本篇围绕 OpenCV 官方教程doc/tutorials/core/mat-mask-operations/mat_mask_operations.markdown原作者 Bernát Gábor适用 OpenCV 3.0以一个对比度增强锐化滤波器为测试用例完整讲清两件事一是如何用指针逐像素手写掩膜运算二是如何一行调用内置的filter2D()函数完成同样的工作并给出二者在代码结构与执行速度上的差异依据读完后你可以独立实现任意 3×3 线性滤波并理解 OpenCV 内置滤波接口的参数语义。一、掩膜操作的数学本质一个加权平均掩膜操作的核心思想是以掩膜矩阵中指定的权重重新计算图像中每个像素的值。从数学角度看就是对当前像素及其邻域像素做加权平均。官方教程选取的测试用例是对比度增强对图像的每一个像素应用如下公式I(i,j) 5*I(i,j) - [ I(i-1,j) I(i1,j) I(i,j-1) I(i,j1) ]它的紧凑掩膜写法等价于一个 3×3 核矩阵注意 i 为行、j 为列j: -1 0 1 i: -1 [ -1 0 1 ] 0 [ -1 0 -1 ] 1 [ 0 -1 5 ] [ 0 -1 0 ] 中心 0,0 处权重为 5两种写法完全等价把掩膜的中心上例中为 0,0 索引实际代码中是核的 (1,1) 位置对准要计算的像素将重叠区域的像素值乘以掩膜值再求和。对于大核矩阵掩膜写法远比长公式更易于审读这也是核这一抽象存在的意义。二、完整可运行示例代码C 版示例完整源码位于仓库 samples/cpp/tutorial_code/core/mat_mask_operations/mat_mask_operations.cpp命令行用法为mat_mask_operations [image_path -- 默认 lena.jpg] [G -- 灰度模式]程序主体先读入图像G参数时用IMREAD_GRAYSCALE否则IMREAD_COLOR然后分两次计时一次运行手写函数Sharpen()一次运行filter2D()直观对比两种实现的速度。//![kern] Mat kernel Mat_char({3,3}, { 0, -1, 0, -1, 5, -1, 0, -1, 0 }); //![kern] t (double)getTickCount(); //![filter2D] filter2D( src, dst1, src.depth(), kernel ); //![filter2D] t ((double)getTickCount() - t)/getTickFrequency(); cout Built-in filter2D time passed in seconds: t endl;手写部分basic_method片段是教程的纵深所在void Sharpen(const Mat myImage, Mat Result) { //![8_bit] CV_Assert(myImage.depth() CV_8U); // 只接受 uchar 图像 //![8_bit] //![create_channels] const int nChannels myImage.channels(); Result.create(myImage.size(), myImage.type()); //![create_channels] //![basic_method_loop] for(int j 1; j myImage.rows-1; j) { const uchar* previous myImage.ptruchar(j - 1); const uchar* current myImage.ptruchar(j ); const uchar* next myImage.ptruchar(j 1); uchar* output Result.ptruchar(j); for(int i nChannels; i nChannels*(myImage.cols-1); i) { output[i] saturate_castuchar(5*current[i] - current[i-nChannels] - current[inChannels] - previous[i] - next[i]); } } //![basic_method_loop] //![borders] Result.row(0).setTo(Scalar(0)); Result.row(Result.rows-1).setTo(Scalar(0)); Result.col(0).setTo(Scalar(0)); Result.col(Result.cols-1).setTo(Scalar(0)); //![borders] }Python 版示例对应源码见 samples/python/tutorial_code/core/mat_mask_operations/mat_mask_operations.py。Python 版的手写实现按行、列、通道逐层循环并用一个saturated()函数手工做 0~255 饱和截断C 中由saturate_castuchar内建完成def sharpen(my_image): if is_grayscale(my_image): height, width my_image.shape else: my_image cv.cvtColor(my_image, cv.CV_8U) height, width, n_channels my_image.shape result np.zeros(my_image.shape, my_image.dtype) for j in range(1, height - 1): for i in range(1, width - 1): if is_grayscale(my_image): sum_value 5 * my_image[j, i] - my_image[j 1, i] - my_image[j - 1, i] \ - my_image[j, i 1] - my_image[j, i - 1] result[j, i] saturated(sum_value) else: for k in range(0, n_channels): sum_value 5 * my_image[j, i, k] - my_image[j 1, i, k] \ - my_image[j - 1, i, k] - my_image[j, i 1, k]\ - my_image[j, i - 1, k] result[j, i, k] saturated(sum_value) return result滤波部分则与 C 同构kernel np.array([[0, -1, 0], [-1, 5, -1], [0, -1, 0]], np.float32) # 核应为浮点类型 dst1 cv.filter2D(src, -1, kernel) # ddepth -1 表示输出图像与输入图像深度相同Java 版示例Java 源码位于 samples/java/tutorial_code/core/mat_mask_operations/MatMaskOperations.java核用CvType.CV_8S的 3×3Mat构造Mat kern new Mat(3, 3, CvType.CV_8S); int row 0, col 0; kern.put(row, col, 0, -1, 0, -1, 5, -1, 0, -1, 0);Mat dst1 new Mat(); Imgproc.filter2D(src, dst1, src.depth(), kern);三、手写基本法Basic Method逐段解析1. 类型断言。C 版第一步用CV_Assert(myImage.depth() CV_8U)确保输入是 unsigned char 格式——这个宏函数在表达式为假时抛出异常。该断言并非可有可无本例公式中系数5*x - y - z - ...的中间结果可能远超 255 或低于 0uchar语义加上饱和转换才能保证结果落在合法灰度区间。2. 创建输出。Result.create(myImage.size(), myImage.type())创建与输入同尺寸、同类型的输出。由于Mat按行主序 连续通道排布多通道时每一列实际对应一个由多个子列subcolumn组成的块因此按行取指针后遍历步长必须按nChannels的倍数推进。3. 指针三行法。C 版没有逐像素调用atuchar(j,i)而是对每一行取出三个指针上一行previous、当前行current、下一行next外加一个输出行指针output。这样上下左右四个邻居就简化为纯指针算术上邻居 previous[i]下邻居 next[i]左邻居 current[i-nChannels]右邻居 current[inChannels]计算结果经saturate_castuchar()饱和截断后写回output[i]指针自然前移一个字节这是既有正确性又有缓存友好性的典型写法内层循环全部是顺序内存访问没有重复的地址计算。注意内层循环i从nChannels起、到nChannels*(cols-1)止正好跳过最左、最右各一列与外层j从 1 到rows-1配合天然避开了图像边框。4. 边框处理。在图像边缘公式会引用到不存在的像素位置如 (-1,-1)公式在这些点无定义。教程给出的简单方案是不在边框点应用核例如把结果图像的上下左右边框像素直接置零borders片段。这也是为什么结果图外圈会出现黑色边框——这不是 bug而是未定义区域不计算的显式策略。Java 版用Result.row(0).setTo(new Scalar(0))等四句做了同样的事。四、filter2D()内置掩膜应用函数正因为应用掩膜在图像处理中太常见OpenCV 提供了专门的filter2D()函数替你完成全部工作。从接口声明看见 modules/imgproc/include/opencv2/imgproc.hppCV_EXPORTS_W void filter2D( InputArray src, OutputArray dst, int ddepth, InputArray kernel, Point anchor Point(-1,-1), double delta 0, int borderType BORDER_DEFAULT );各参数含义参数含义src/dst输入/输出图像ddepth输出图像深度传-1表示与输入相同深度教程三种语言示例均按此语义调用C 显式传src.depth()Python 传-1kernel掩膜核矩阵anchor核的中心点默认(-1,-1)即核几何中心delta存入输出前加到每个滤波像素上的可选偏移量borderType未定义区域边框的外推方式见BorderTypesBORDER_WRAP不被支持核的定义就是几行初始化C 用Mat_char初始化列表Python 用np.array(..., np.float32)Java 用Mat(3,3,CV_8S)put()。随后一行调用即可完成filter2D( src, dst1, src.depth(), kernel );它的实现在 modules/imgproc/src/filter.dispatch.cpp 中按图像类型分发的filter2D入口位于该文件 1499/1533 行附近并有对应的 OpenCL 版本modules/imgproc/src/opencl/filter2d.cl供 GPU 后端使用。五、手写实现 vs filter2D差异与性能两个实现功能等价差别在工程层面代码量与可读性filter2D()一行调用替代了断言、输出创建、双层指针循环、边框置零等全部样板代码性能内置函数带有多种优化。官方教程给出的测试数据是同一张图上filter2D()耗时约13 毫秒手写函数约31 毫秒差距接近一倍行为细节手写示例对边框的策略是置零而filter2D()的边框策略由borderType参数决定默认BORDER_DEFAULT为复制边即内置版把未定义区域怎么办这一决策开放给了调用方。运行 C/Java/Python 任一示例都会看到两个窗口依次显示两种实现的结果控制台分别打印Hand written function time passed in seconds与Built-in filter2D time passed in seconds。用 filter2D 得到的锐化效果大致如下官方教程配图六、小结与延伸阅读掩膜操作 像素邻域的加权平均3×3 核[0,-1,0; -1,5,-1; 0,-1,0]是经典锐化/对比度增强核中心系数 5 与四个负邻居共同拉大相邻像素的差异手写实现的关键技巧按行取指针 通道步长 saturate_cast饱和 边框显式处理工程上应优先使用filter2D()更短、更不易错、更快且anchor/delta/borderType三个可选参数覆盖了绝大多数变体需求教程中提及的相关能力可继续在本仓库查阅核的分离形式sepFilter2D见 modules/imgproc/include/opencv2/imgproc.hpp 中相邻声明、逐像素遍历细节doc/tutorials/core/how-to-scan-images/下的存储小节以及下一节doc/tutorials/core/mat-operations/的矩阵运算教程三个语言的可运行示例分别位于 samples/cpp/tutorial_code/core/mat_mask_operations/mat_mask_operations.cpp、samples/python/tutorial_code/core/mat_mask_operations/mat_mask_operations.py 与 samples/java/tutorial_code/core/mat_mask_operations/MatMaskOperations.java可编译后替换任意本地图像验证效果。【免费下载链接】opencvOpen Source Computer Vision Library项目地址: https://gitcode.com/GitHub_Trending/opencv31/opencv创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →