尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

OpenCV 运动模糊图像复原实战:线性 PSF 建模与维纳滤波(Motion Deblur Filter)详解

OpenCV 运动模糊图像复原实战:线性 PSF 建模与维纳滤波(Motion Deblur Filter)详解 OpenCV 运动模糊图像复原实战线性 PSF 建模与维纳滤波Motion Deblur Filter详解【免费下载链接】opencvOpen Source Computer Vision Library项目地址: https://gitcode.com/GitHub_Trending/opencv31/opencv运动模糊motion blur是拍摄运动物体时最常见的降质来源之一例如行驶中的汽车导致车牌无法辨认。本教程以 OpenCV 官方示例 motion_deblur_filter.cpp 为蓝本系统讲解线性运动模糊的点扩散函数PSF如何用长度LEN与角度THETA建模以及如何通过频域维纳滤波Wiener filter与边缘渐变edgetaper技术复原出可辨识的图像。读完本文你将掌握一套完整的PSF 建模 → 维纳滤波构造 → 频域滤波 → 边缘防振铃的复原流程并能独立调整LEN、THETA、SNR三个参数获得最佳视觉效果。教程定位与配套资源本文对应的原始教程是 doc/tutorials/imgproc/motion_deblur_filter/motion_deblur_filter.markdown原作者为 Karpushin Vladislav要求OpenCV 3.0。该教程是 OpenCV 图像处理imgproc模块去模糊Deblur Filter系列的第二篇前置教程是散焦去模糊Out-of-focus Deblur Filter。仓库中与本文直接相关的文件包括教程文档motion_deblur_filter.markdown示例源码motion_deblur_filter.cpp理论前置教程out_of_focus_deblur_filter.markdown前置示例源码out_of_focus_deblur_filter.cpp本教程使用的实验图片位于 doc/tutorials/imgproc/motion_deblur_filter/images 目录下与散焦去模糊不同本教程针对的是线性运动模糊linear motion blur且用于实验的是一张真实世界拍摄的图像——模糊由运动中的物体汽车造成而非人工合成模糊因此更具实战参考价值。图像退化模型与维纳滤波理论前提在进行运动模糊复原之前需要先建立图像降质的数学模型。在频域中模糊图像可以表示为推导详见前一篇散焦去模糊教程 out_of_focus_deblur_filter.markdownS H·U N其中S模糊降质图像的频谱U原始清晰图像的频谱H点扩散函数PSF的频率响应光学传递函数N加性噪声的频谱。去模糊restoration的目标是得到原始图像U的估计U。若采用维纳滤波简化的复原公式为U H_w · S当 PSF 为实对称信号、且原始图像与噪声的功率谱未知时可对维纳滤波器做如下简化H_w H / (|H|^2 1/SNR)其中SNR为信噪比。因此要为一个运动模糊图像构造维纳滤波器只需知道三个量PSF 的长度LEN、PSF 的角度THETA、信噪比SNR。这正是本教程示例中三个命令行参数的由来。运动模糊的 PSF一条由 LEN 与 THETA 决定的线段运动模糊的 PSF 与散焦模糊圆形 PSF不同。当相机与物体之间存在相对匀速直线运动时图像上任一点的能量会沿运动方向被拖成一条线段因此线性运动模糊的 PSF 是一条直线段。这条 PSF 仅由两个参数完全确定LEN模糊轨迹的长度对应运动位移的大小THETA运动方向与水平方向的夹角单位为度。在示例源码 motion_deblur_filter.cpp 中calcPSF()函数用 OpenCV 的ellipse()画出一条旋转后的线段来构造这条 PSFvoid calcPSF(Mat outputImg, Size filterSize, int len, double theta) { Mat h(filterSize, CV_32F, Scalar(0)); Point point(filterSize.width / 2, filterSize.height / 2); ellipse(h, point, Size(0, cvRound(float(len) / 2.0)), 90.0 - theta, 0, 360, Scalar(255), FILLED); Scalar summa sum(h); outputImg h / summa[0]; }代码要点创建一块与待处理图像的偶数化 ROI同样大小的CV_32F全零矩阵h以图像中心为圆心绘制一个长轴半径约为LEN/2、短轴半径为 0 的椭圆等效于一条线段且椭圆旋转角度为90 - THETA即最终线段方向对应真实运动方向THETA用sum(h)求和后整体相除将 PSF 归一化保证 PSF 在空域的总能量为 1从而不会引入整体亮度偏移。复原流程概览与命令行参数main()函数是整个算法的调度核心位于 motion_deblur_filter.cpp。整体可归纳为三步PSF 生成 → 维纳滤波生成 → 频域滤波。示例使用 OpenCV 的CommandLineParser解析命令行参数参数定义如下命令行参数类型默认值含义imageStringinput.png输入图像文件名内部以IMREAD_GRAYSCALE读取为灰度图LENint125运动模糊轨迹长度像素级位移量THETAdouble0运动方向角度单位度SNRint700信噪比简化维纳公式中的比值main()中算法主流程如下对应源码注释//! [main]片段// it needs to process even image only Rect roi Rect(0, 0, imgIn.cols -2, imgIn.rows -2); //Hw calculation (start) Mat Hw, h; calcPSF(h, roi.size(), LEN, THETA); calcWnrFilter(h, Hw, 1.0 / double(snr)); //Hw calculation (stop) imgIn.convertTo(imgIn, CV_32F); edgetaper(imgIn, imgIn); // filtering (start) filter2DFreq(imgIn(roi), imgOut, Hw); // filtering (stop)流程细节取偶数 ROIimgIn.cols -2等价于把宽、高向下取整为偶数。由于后续 DFT/滤波要求 PSF 与图像尺寸对齐且偶数尺寸能避免频域处理中的奇偶不对称问题所以先裁出偶数 ROI计算 Hw先calcPSF()得到空域 PSFh再以nsr 1/SNR为参数调用calcWnrFilter()合成维纳滤波器的频域响应Hw边缘渐变防振铃图像转为CV_32F浮点后调用edgetaper()对边缘做渐变衰减频域滤波filter2DFreq()对 ROI 区域的浮点图像与滤波器Hw在频域相乘再反变换回空域得到结果输出前做类型转换与归一化convertTo到CV_8U并用NORM_MINMAX归一化到 0~255最终imwrite(result.jpg)保存到当前目录。四个核心函数的源码级解析calcWnrFilter()按简化公式合成维纳滤波器该函数实现上文公式H_w H / (|H|^2 1/SNR)位于 motion_deblur_filter.cppvoid calcWnrFilter(const Mat input_h_PSF, Mat output_G, double nsr) { Mat h_PSF_shifted; fftshift(input_h_PSF, h_PSF_shifted); Mat planes[2] { Mat_float(h_PSF_shifted.clone()), Mat::zeros(h_PSF_shifted.size(), CV_32F) }; Mat complexI; merge(planes, 2, complexI); dft(complexI, complexI); split(complexI, planes); Mat denom; pow(abs(planes[0]), 2, denom); denom nsr; divide(planes[0], denom, output_G); }关键步骤先用fftshift()把 PSF 的直流分量移到频谱中心保证后续 DFT 的相位正确构造复数频谱实部为 PSF、虚部为 0并做dft()abs(planes[0])^2得到|H|^2加上nsr即1/SNR作为分母用H / denom得到维纳滤波响应。注意nsr传入的是1.0 / double(snr)与简化公式中1/SNR严格对应——这也解释了为什么示例里SNR的取值如 700、300看起来很大因为它实际作为信噪比的倒数进入分母参与运算。fftshift()频谱重排fftshift()把低频分量移到中心与 out_of_focus_deblur_filter.cpp 及离散傅里叶变换教程中的实现一致本质是通过四个象限的对角互换完成坐标重排void fftshift(const Mat inputImg, Mat outputImg) { outputImg inputImg.clone(); int cx outputImg.cols / 2; int cy outputImg.rows / 2; Mat q0(outputImg, Rect(0, 0, cx, cy)); Mat q1(outputImg, Rect(cx, 0, cx, cy)); Mat q2(outputImg, Rect(0, cy, cx, cy)); Mat q3(outputImg, Rect(cx, cy, cx, cy)); Mat tmp; q0.copyTo(tmp); q3.copyTo(q0); tmp.copyTo(q3); q1.copyTo(tmp); q2.copyTo(q1); tmp.copyTo(q2); }filter2DFreq()频域滤波主体void filter2DFreq(const Mat inputImg, Mat outputImg, const Mat H) { Mat planes[2] { Mat_float(inputImg.clone()), Mat::zeros(inputImg.size(), CV_32F) }; Mat complexI; merge(planes, 2, complexI); dft(complexI, complexI, DFT_SCALE); Mat planesH[2] { Mat_float(H.clone()), Mat::zeros(H.size(), CV_32F) }; Mat complexH; merge(planesH, 2, complexH); Mat complexIH; mulSpectrums(complexI, complexH, complexIH, 0); idft(complexIH, complexIH); split(complexIH, planes); outputImg planes[0]; }要点输入图像与滤波器都按实部 零虚部组织成复数矩阵后各自做 DFT通过mulSpectrums(..., 0)完成频域逐点相乘等效于空域卷积注意输入图像的 DFT 带DFT_SCALE标志而滤波器的 DFT 不带——这样组合后配合idft()即可在结果中获得正确的幅值比例最后取反变换结果的实部planes[0]作为复原图像。edgetaper()抑制振铃效应DFT 隐含了周期性延拓图像边缘的不连续会在复原结果中引发明显的振铃ringing伪影。edgetaper()通过对图像边缘进行平滑衰减来缓解该问题函数原型与默认参数为edgetaper(input, output, gamma 5.0, beta 0.2)void edgetaper(const Mat inputImg, Mat outputImg, double gamma, double beta) { int Nx inputImg.cols; int Ny inputImg.rows; Mat w1(1, Nx, CV_32F, Scalar(0)); Mat w2(Ny, 1, CV_32F, Scalar(0)); float* p1 w1.ptrfloat(0); float* p2 w2.ptrfloat(0); float dx float(2.0 * CV_PI / Nx); float x float(-CV_PI); for (int i 0; i Nx; i) { p1[i] float(0.5 * (tanh((x gamma / 2) / beta) - tanh((x - gamma / 2) / beta))); x dx; } float dy float(2.0 * CV_PI / Ny); float y float(-CV_PI); for (int i 0; i Ny; i) { p2[i] float(0.5 * (tanh((y gamma / 2) / beta) - tanh((y - gamma / 2) / beta))); y dy; } Mat w w2 * w1; multiply(inputImg, w, outputImg); }原理说明在[-π, π]上对每个像素位置计算两个错位的tanh之差形成一个中部平坦、两侧平滑过渡到 0 的窗函数w1是 1×Nx 的水平窗w2是 Ny×1 的垂直窗二者做外积w2 * w1得到二维窗w再与输入图像逐点相乘multiply(inputImg, w, outputImg)gamma控制中心平坦区宽度beta控制过渡带的陡峭程度二者默认 5.0 与 0.2。调整时需权衡窗口过窄会损失太多有效像素过渡过陡又减弱抑制振铃的效果。编译与运行方式该示例是 OpenCV 标准 samples 工程的一员位于samples/cpp/tutorial_code/ImgProc/motion_deblur_filter/目录。推荐两种运行方式在完整 OpenCV 构建中启用 samples配置 CMake 时开启 samples 构建BUILD_EXAMPLES随后编译example_tutorial_motion_deblur_filter目标即可得到可执行文件独立编译手动用g或 CMake链接opencv_core、opencv_imgproc、opencv_imgcodecs三个库编译该.cpp。运行时通过命令行传入参数OpenCVCommandLineParser采用-keyvalue语法# 处理 black_car 场景LEN125, THETA0, SNR700 ./motion_deblur_filter -imagemotion_original.jpg -LEN125 -THETA0 -SNR700 # 处理 white_car 场景LEN78, THETA15, SNR300 ./motion_deblur_filter -imagemotion_original.jpg -LEN78 -THETA15 -SNR300输入图像会被强制按IMREAD_GRAYSCALE读入为灰度图若图像加载失败文件不存在等程序会打印ERROR : Image cannot be loaded..!!并返回 -1。复原结果统一写入当前目录的result.jpg。也可以直接运行并打印参数帮助传入-help、-h、-usage或-?会调用parser.printMessage()输出参数说明。实验结果与参数选择教程中的实验图像是一张真实拍摄的街道照片两辆汽车均在画面中发生运动模糊两辆车的车牌都无法辨认图中红色标记指出了车牌位置。采用上述流程复原后黑色汽车车牌的结果使用参数LEN 125、THETA 0、SNR 700白色汽车车牌的结果使用参数LEN 78、THETA 15、SNR 300仔细观察两辆车需要不同的LEN与THETA这恰恰反映了参数的物理含义THETA与汽车运动方向一致白色车相对相机存在 15° 左右的倾斜运动分量因此THETA 15LEN与汽车运动速度相关速度越快单帧曝光内位移越大需要的LEN越大黑色车LEN 125明显大于白色车的LEN 78。示例中这三个数值都是人工反复尝试后选取的目标是得到尽可能好的视觉效果。需要承认的是复原结果并不完美但足以提示出图像内容的轮廓——经过努力车牌字符已经能够辨识出来。这在实际取证、监控场景中具有明确的使用价值不追求完美去模糊而追求从不可读到可读。参数调优要点与算法局限教程在 motion_deblur_filter.markdown 末尾明确给出了一条重要调参经验LEN和THETA是最重要的两个参数。应先调整LEN和THETA再调整SNR。理由很直接LEN/THETA描述的是降质过程本身的几何特性运动距离与方向一旦与真实模糊不匹配滤波器相当于用错误的 PSF做反卷积无论SNR如何取值都无法有效复原只有在 PSF 估计准确的前提下SNR才起到平衡锐化细节与放大噪声的作用——SNR过大会导致过拟合噪声、振铃加剧过小又会让复原不彻底。从源码与实验还可以总结出本方法的几个适用前提与边界空不变假设算法为整幅图像使用同一个 PSFPSF 尺寸与图像 ROI 等大见calcPSF的filterSize参数即假设全图运动模糊一致。对画面内多个物体以不同速度、方向运动如本例两辆车同时存在的场景只能针对局部区域分别选择参数灰度处理输入被强制转为灰度图颜色信息在复原中被丢弃振铃抑制手段即便有edgetaper的边缘渐变处理当SNR设置不当时复原结果仍可能出现振铃需要配合调参人工估计参数当前示例并不自动估计LEN/THETA/SNR参数估计仍需结合运动速度、方向等先验知识或通过搜索试验获得。进一步阅读与代码验证同一作者配套的散焦失焦去模糊教程out_of_focus_deblur_filter.markdown其中完整给出了退化模型与维纳滤波的数学推导本页的calcWnrFilter()、fftshift()、filter2DFreq()三个函数即直接取自其示例 out_of_focus_deblur_filter.cpp完整可编译源码motion_deblur_filter.cpp实验图片原图与两辆车的复原结果doc/tutorials/imgproc/motion_deblur_filter/images。如果你是初次接触频域滤波建议先运行散焦去模糊示例对比圆形 PSF与线性 PSF两种建模的区别再回到本示例以motion_original.jpg为输入先固定SNR步进式扫描THETA与LEN即可直观观察 PSF 参数对复原质量的决定性影响从而建立对运动模糊复原的直觉。【免费下载链接】opencvOpen Source Computer Vision Library项目地址: https://gitcode.com/GitHub_Trending/opencv31/opencv创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表