用一个模糊替换一串图像模糊。

8
this问题中,我问如何在一个步骤中实现一系列模糊。然后我从维基百科的高斯模糊页面中发现:
应用多个连续的高斯模糊到图像上,其效果与应用单个更大半径的高斯模糊相同,其半径为实际应用的模糊半径平方和的平方根。例如,连续应用半径为6和8的高斯模糊产生的结果与应用半径为10的单个高斯模糊相同,因为sqrt {6^{2}+8^{2}}=10。
所以我认为以下代码中的“blur”和“singleBlur”是相同的:
cv::Mat firstLevel;
float sigma1, sigma2;
//intialize firstLevel, sigma1 and sigma2
cv::Mat blur = gaussianBlur(firstLevel, sigma1);
        blur = gaussianBlur(blur, sigma2);
float singleSigma = std::sqrt(std::pow(sigma1,2)+std::pow(sigma2,2));
cv::Mat singleBlur = gaussianBlur(firstLevel, singleSigma);
cv::Mat diff = blur != singleBLur;
// Equal if no elements disagree
assert( cv::countNonZero(diff) == 0);

但是这个assert失败了(例如,blur的第一行与singleBlur的第一行不同)。
为什么?
更新:
在不同的评论要求更多信息后,我将更新答案。
我正在尝试并行化this代码。特别是,我现在专注于预先计算所有级别的所有模糊。串行代码(正确工作)如下:
   vector<Mat> blurs ((par.numberOfScales+3)*levels, Mat());
   cv::Mat octaveLayer = firstLevel;
   int scaleCycles = par.numberOfScales+2;

   //compute blurs at all layers (not parallelizable)
   for(int i=0; i<levels; i++){
       blurs[i*scaleCycles+1] = octaveLayer.clone();
       for (int j = 1; j < scaleCycles; j++){
           float sigma = par.sigmas[j]* sqrt(sigmaStep * sigmaStep - 1.0f);
           blurs[j+1+i*scaleCycles] = gaussianBlur(blurs[j+i*scaleCycles], sigma);
           if(j == par.numberOfScales)
               octaveLayer = halfImage(blurs[j+1+i*scaleCycles]);
       }
   }

在哪里:

Mat halfImage(const Mat &input)
{
   Mat n(input.rows/2, input.cols/2, input.type());
   float *out = n.ptr<float>(0);
   for (int r = 0, ri = 0; r < n.rows; r++, ri += 2)
      for (int c = 0, ci = 0; c < n.cols; c++, ci += 2)
         *out++ = input.at<float>(ri,ci);
   return n;
}

Mat gaussianBlur(const Mat input, const float sigma)
{
   Mat ret(input.rows, input.cols, input.type());
   int size = (int)(2.0 * 3.0 * sigma + 1.0); if (size % 2 == 0) size++;      
   GaussianBlur(input, ret, Size(size, size), sigma, sigma, BORDER_REPLICATE);
   return ret;
}

抱歉以上的索引很糟糕,但我尽力尊重原始代码系统(它很糟糕,比如从1而不是0开始计数)。上面的代码具有scaleCycles=5levels=6,因此总共生成30个模糊度。
这是“单次模糊”版本,首先我计算每个需要计算的模糊度的西格玛值(按照维基百科的公式),然后应用模糊度(请注意,这仍然是串行的,不能并行化)。
   vector<Mat> singleBlurs ((par.numberOfScales+3)*levels, Mat());
   vector<float> singleSigmas(scaleCycles);
   float acc = 0;
   for (int j = 1; j < scaleCycles; j++){
       float sigma = par.sigmas[j]* sqrt(sigmaStep * sigmaStep - 1.0f);
       acc += pow(sigma, 2);
       singleSigmas[j] = sqrt(acc);
   }

   octaveLayer = firstLevel;
   for(int i=0; i<levels; i++){
       singleBlurs[i*scaleCycles+1] = octaveLayer.clone();
       for (int j = 1; j < scaleCycles; j++){
           float sigma = singleSigmas[j];
           std::cout<<"j="<<j<<" sigma="<<sigma<<std::endl;
           singleBlurs[j+1+i*scaleCycles] = gaussianBlur(singleBlurs[j+i*scaleCycles], sigma);
           if(j == par.numberOfScales)
               octaveLayer = halfImage(singleBlurs[j+1+i*scaleCycles]);
       }
   }

当然,上面的代码还是使用相同的参数生成了30个模糊效果。
以下是用于查看每个singleBlursblurs之间差异的代码:
   assert(blurs.size() == singleBlurs.size());
   vector<Mat> blurDiffs(blurs.size());
   for(int i=1; i<levels*scaleCycles; i++){
        cv::Mat diff;
        absdiff(blurs[i], singleBlurs[i], diff);
        std::cout<<"i="<<i<<"diff rows="<<diff.rows<<" cols="<<diff.cols<<std::endl;
        blurDiffs[i] = diff;
        std::cout<<"blurs rows="<<blurs[i].rows<<" cols="<<blurs[i].cols<<std::endl;
        std::cout<<"singleBlurs rows="<<singleBlurs[i].rows<<" cols="<<singleBlurs[i].cols<<std::endl;
        std::cout<<"blurDiffs rows="<<blurDiffs[i].rows<<" cols="<<blurDiffs[i].cols<<std::endl;
        namedWindow( "blueDiffs["+std::to_string(i)+"]", WINDOW_AUTOSIZE );// Create a window for display.
        //imshow( "blueDiffs["+std::to_string(i)+"]", blurDiffs[i] );                   // Show our image inside it.
        //waitKey(0);                                          // Wait for a keystroke in the window
        Mat imageF_8UC3;
        std::cout<<"converting..."<<std::endl;
        blurDiffs[i].convertTo(imageF_8UC3, CV_8U, 255);
        std::cout<<"converted"<<std::endl;
        imwrite( "blurDiffs_"+std::to_string(i)+".jpg", imageF_8UC3);
   }

现在,我看到的是blurDiffs_1.jpgblurDiffs_2.jpg是黑色的,但突然从blurDiffs_3.jpgblurDiffs_29.jpg越来越白。由于某种原因,blurDiffs_30.jpg几乎完全是黑色的。
第一个(正确的)版本生成1761个描述符。第二个(不正确的)版本生成超过2.3k个描述符。
我无法发布blurDiffs矩阵,因为(特别是前面的几个)非常大,而帖子空间有限。我会发布一些样本。我不会发布blurDiffs_1.jpgblurDiffs_2.jpg,因为它们完全是黑色的。请注意,由于halfImage,图像变得越来越小(如预期)。

blurDiffs_3.jpg:

enter image description here

blurDiffs_6.jpg:

enter image description here

blurDiffs_15.jpg:

enter image description here

blurDiffs_29.jpg:

enter image description here

图片的读取方式:

  Mat tmp = imread(argv[1]);
  Mat image(tmp.rows, tmp.cols, CV_32FC1, Scalar(0));

  float *out = image.ptr<float>(0);
  unsigned char *in  = tmp.ptr<unsigned char>(0); 

  for (size_t i=tmp.rows*tmp.cols; i > 0; i--)
  {
     *out = (float(in[0]) + in[1] + in[2])/3.0f;
     out++;
     in+=3;
  }

有人在这里建议将diff除以255以查看真正的差异,但如果我理解正确的话,我不明白为什么。

如果您需要更多细节,请告诉我。


在代码中使用的cv::Mat中,每个元素的类型是什么?如果它是浮点类型,那么由于浮点值在c ++中的表示方式,差异恰好为零的概率非常小。 - G.M.
@G.M. 不,我们谈论的是大错误,而不是第九位数字。 - justHelloWorld
不考虑我已经做过这个,但是...我不记得我是怎么做的,而且我也把代码弄丢了(真丢人)。 - justHelloWorld
1
你能量化“大错误”吗? 你的代码甚至无法编译通过。我在OpenCV文档中找不到任何gaussianBlur,只有不同参数列表的GaussianBlur。你的sigmas没有初始化任何值...而且你的代码中有单个BLur vs singleBlur的拼写错误... - Piglet
根据Tatarize在这里的回答https://math.stackexchange.com/questions/1023984/combining-two-convolution-kernels,多个卷积的组合速度性能较差,因此也许您根本不想这样做... - Micka
显示剩余8条评论
2个回答

2
提前警告:我没有使用OpenCV的经验,但以下内容适用于一般情况下计算高斯模糊。并且它也适用于我浏览了OpenCV文档中关于边界处理和有限核(FIR滤波)使用的部分。
  1. As an aside: your initial test was sensitive to round-off, but you have cleared up that issue and shown the errors to be much larger.

  2. Beware image border effects. For pixels near the edge, the image is virtually extended using one of the offered methods (BORDER_DEFAULT, BORDER_REPLICATE, etc...). If your image is |abcd| and you use BORDER_REPLICATE you are effectively filtering an extended image aaaa|abcd|dddd. The result is klmn|opqr|stuv. There are new pixel values (k,l,m,n,s,t,u,v) that are immediately discarded to yield the output image |opqr|. If you now apply another Gaussian blur, this blur will operate on a newly extended image oooo|opqr|rrrr - different from the "true" intermediate result and thus giving you a result different from that obtained by a single Gaussian blur with a larger sigma. These extension methods are safe though: REFLECT, REFLECT_101, WRAP.

  3. Using a finite kernel size the G(s1)*G(s2)=G(sqrt(s1^2+s2^2)) rule does not hold in general due to the tails of the kernel being cut off. You can reduce the error thus introduced by increasing the kernel size relative to the sigma, e.g.:

    int size = (int)(2.0 * 10.0 * sigma + 1.0); if (size % 2 == 0) size++;
    
第三点似乎是困扰您的问题。您真的关心 G(s1)*G(s2) 属性是否被保留吗?两种结果都有些错误。这会对处理结果的方法产生重大影响吗?请注意,我提供的使用 10x sigma 的示例可能解决差异,但速度会慢得多。
更新:我忘记加上最实用的解决方案。使用傅里叶变换计算高斯模糊。步骤如下:
  • 计算输入图像的傅里叶变换(FFT)
  • 与高斯卷积核的傅里叶变换相乘,并计算反向傅里叶变换。忽略复杂输出的虚部。
您可以在 wikipedia 上找到频域中高斯函数的方程式。
您可以针对每个尺度(sigma)单独执行第二步(即并行执行)。所隐含的边界条件是使用此方法计算模糊:BORDER_WRAP。如果您喜欢,您可以通过使用离散余弦变换(DCT)来实现相同的结果,但使用BORDER_REFLECT。不知道OpenCV是否提供DCT。您可能需要查找DCT-II

0

基本上就像G.M.所说的那样。请记住,您不仅要通过浮点数进行舍入,还要仅查看整数点(在图像和高斯核上都是如此)。

这是我从一个小的(41x41)图像中得到的:

enter image description here

blursingle 通过 convertTo(...,CV8U) 进行舍入,diff 表示它们之间的差异。因此,在 DSP 方面,可能不是很好的协议。但在图像处理中,情况并不那么糟糕。

此外,我怀疑随着对更大图像进行高斯滤波,差异会变得不那么显著。


我注意到使用BORDER_DEFAULT而不是BORDER_REPLICATE,结果完全相同,尽管最终结果更差甚至更慢... - justHelloWorld
我错了,使用BORDER_DEFAULT也不起作用。 - justHelloWorld
顺便提一下,这个方法看起来有问题:使用多次模糊处理,我正确检测到了1761个关键点,而使用此方法检测到的关键点超过了2k,显然是错误的。 - justHelloWorld
我更新了问题,并附上了更多的详细信息和示例图片。请看一下。 - justHelloWorld
你有任何想法为什么会发生这种情况吗?我真的卡住了,否则我无法继续我的项目。 - justHelloWorld
我添加了关于如何读取图像的代码,并为此问题设置了赏金。这对我的项目非常重要。 - justHelloWorld

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接