基于OpenCV C++的移动平均模糊算法实现异常排查
我正在用OpenCV C++学习计算机视觉,入门练习想实现移动平均算法做图像模糊,但转换后的图像效果完全不对,不是预期的模糊效果。怀疑是OpenCV细节遗漏或者算法本身错误?另外发现打印pointVal时输出是char类型,是不是数据类型编码的问题?
原代码
#include <opencv2/opencv.hpp> #include <opencv2/core.hpp> #include <opencv2/imgcodecs.hpp> #include <opencv2/highgui.hpp> #include <opencv2/imgproc.hpp> #include <math.h> #include <algorithm> using namespace std; using namespace cv; void displayImage(Mat &img,unsigned int time=0){ imshow("frame",img); waitKey(time); } void movingAverageTransform(Mat &img){ displayImage(img); unsigned int winSize = 3; int trows = img.rows-winSize+1; int tcols = img.cols-winSize+1; Mat transformed(trows,tcols, CV_8UC1); // accumulate first column prefix sums for(int i =1;i<img.rows;i++) img.at<u_char>(i,0) += img.at<u_char>(i-1,0); // accumulate first row prefix sums for(int j = 1;j<img.cols;j++) img.at<u_char>(0,j)+= img.at<u_char>(0,j-1); // compute the rectangular submatrix prefix sums. for(int i =1;i<img.rows;i++) for(int j = 1;j<img.cols;j++) img.at<u_char>(i,j) += (img.at<u_char>(i-1,j) + img.at<u_char>(i,j-1) - img.at<u_char>(i-1,j-1)); // now use the formula for computing the moving average of a sub matrix of size winSize X winSize by sliding it across the image for(int i = 0;i<trows;i++){ for(int j = 0;j<tcols;j++){ u_char pointVal = img.at<u_char>(i+winSize -1,j+winSize-1); if(i>=1) pointVal -= img.at<u_char>(i-1,j+winSize-1); if(j>=1) pointVal -=img.at<u_char>(i+winSize -1,j-1); if(i>=1 and j>=1) pointVal += img.at<u_char>(i-1,j-1); transformed.at<u_char>(i,j) = pointVal/(winSize*winSize); } } displayImage(transformed); } int main(){ string imPath = "/home/panirpal/workspace/Learning/Courses/computer-vision/cpp/data/frm.png"; Mat img = imread(imPath,IMREAD_GRAYSCALE); if(!img.empty()) movingAverageTransform(img); return 0; }
转换公式说明
T为转换后图像,I为输入图像,(i,j)为转换图像中的坐标,I[x][y]表示输入图像I中从(0,0)到(x,y)的子矩阵和。
公式:
T(i,j) = [I(i+w-1,j+w-1) - I(i-1,j+w-1) - I(i+w-1,j-1) + I(i-1,j-1)] / (w²)
其中w为窗口大小(此处w=3),当i=0或j=0时,对应的I(-1,)或I(,-1)视为0。
图像说明
- 输入图像:常规灰度测试图
- 转换后图像:效果异常,无模糊效果,呈现杂乱图案
问题分析与解决
核心问题是数据类型溢出,直接在8位无符号灰度图(CV_8UC1)上计算前缀和,累加数值远超0-255范围,导致溢出截断,完全破坏了前缀和的正确性,这是效果异常的根本原因。此外还有几处细节问题,修正如下:
1. 前缀和计算的类型溢出
原图像是CV_8UC1(8位无符号字符),子矩阵和会远大于255(比如3x3窗口最大和为33255=2295),用u_char存储会溢出,得到错误数值。
解决:创建CV_32SC1(32位有符号整数)类型的前缀和矩阵,存储高精度累加值,不修改原图像。
2. 原图像被修改
原代码直接在输入图像上计算前缀和,破坏了原数据,后续操作会出错。应基于原图像计算前缀和,保留原图像完整。
3. 边界处理与数值越界
计算平均值后需确保结果在0-255范围内,用saturate_cast<uchar>可以自动截断越界值。
修正后的代码
#include <opencv2/opencv.hpp> #include <opencv2/core.hpp> #include <opencv2/imgcodecs.hpp> #include <opencv2/highgui.hpp> #include <opencv2/imgproc.hpp> #include <math.h> #include <algorithm> using namespace std; using namespace cv; void displayImage(Mat &img, unsigned int time=0){ imshow("frame", img); waitKey(time); } void movingAverageTransform(Mat &img){ displayImage(img); unsigned int winSize = 3; int trows = img.rows - winSize + 1; int tcols = img.cols - winSize + 1; Mat transformed(trows, tcols, CV_8UC1); // 创建32位整数前缀和矩阵,避免溢出 Mat prefixSum(img.rows, img.cols, CV_32SC1, Scalar(0)); // 复制原图像数据到前缀和矩阵(转换为int类型) for(int i = 0; i < img.rows; i++){ for(int j = 0; j < img.cols; j++){ prefixSum.at<int>(i, j) = img.at<uchar>(i, j); } } // 计算第一列前缀和 for(int i = 1; i < img.rows; i++) prefixSum.at<int>(i, 0) += prefixSum.at<int>(i-1, 0); // 计算第一行前缀和 for(int j = 1; j < img.cols; j++) prefixSum.at<int>(0, j) += prefixSum.at<int>(0, j-1); // 计算整体前缀和 for(int i = 1; i < img.rows; i++){ for(int j = 1; j < img.cols; j++){ prefixSum.at<int>(i, j) += prefixSum.at<int>(i-1, j) + prefixSum.at<int>(i, j-1) - prefixSum.at<int>(i-1, j-1); } } // 计算移动平均 int windowArea = winSize * winSize; for(int i = 0; i < trows; i++){ for(int j = 0; j < tcols; j++){ int x2 = i + winSize - 1; int y2 = j + winSize - 1; int sum = prefixSum.at<int>(x2, y2); if(i > 0) sum -= prefixSum.at<int>(i-1, y2); if(j > 0) sum -= prefixSum.at<int>(x2, j-1); if(i > 0 && j > 0) sum += prefixSum.at<int>(i-1, j-1); // 确保数值在0-255范围内 transformed.at<uchar>(i, j) = saturate_cast<uchar>(sum / windowArea); } } displayImage(transformed); } int main(){ string imPath = "/home/panirpal/workspace/Learning/Courses/computer-vision/cpp/data/frm.png"; Mat img = imread(imPath, IMREAD_GRAYSCALE); if(!img.empty()) movingAverageTransform(img); return 0; }
关键修正说明
- 用
CV_32SC1存储前缀和,避免累加溢出。 - 保留原图像数据,不修改输入图像。
- 使用
saturate_cast<uchar>确保最终像素值在0-255范围内,防止越界。 - 提取窗口右下角坐标为变量,代码更清晰易读。
修改后即可得到预期的移动平均模糊效果。
内容的提问来源于stack exchange,提问作者Pawan Nirpal

