You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 22:35:54