8

这个问题中,我询问了如何一步实现一系列模糊。

然后我从维基百科的高斯模糊页面中发现:

对图像应用多个连续的高斯模糊与应用单个更大的高斯模糊具有相同的效果,其半径是实际应用的模糊半径平方和的平方根。例如,应用半径为 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)。

为什么?

更新:

在要求更多信息的不同评论之后,我会更新答案。

我想要做的是并行化这段代码。特别是,我现在专注于提前计算所有级别的所有模糊。序列号(正常工作)如下:

   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 个模糊。

这是“单一模糊”版本,首先我计算每个必须计算的模糊的 sigma(遵循 Wikipedia 的公式),然后应用模糊(请注意,这仍然是串行的且不可并行化):

   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 个模糊。

signgleBlurs然后这是查看每个和之间区别的代码blurs

   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.jpg直到blurDiffs_29.jpg变得越来越白。由于某种原因,blurDiffs_30.jpg几乎完全是黑色的。

第一个(正确的)版本生成 1761 个描述符。第二个(不正确的)版本生成 >2.3k 描述符。

我不能发布blurDiffs矩阵,因为(尤其是第一个)非常大并且帖子的空间有限。我会发布一些样本。我不会发帖blurDiffs_1.jpgblurDiffs_2.jpg因为他们完全是黑人。请注意,由于halfImage图像变得越来越小(如预期的那样)。

blurDiffs_3.jpg:

在此处输入图像描述

blurDiffs_6.jpg:

在此处输入图像描述

blurDiffs_15.jpg:

在此处输入图像描述

blurDiffs_29.jpg:

在此处输入图像描述

如何读取图像:

  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;
  }

这里有人建议除以diff255 来查看真正的区别,但我不明白为什么我正确理解了他。

如果您需要更多详细信息,请告诉我

4

2 回答 2

2

预先警告:我没有使用OpenCV的经验,但以下内容通常与计算高斯模糊有关。它适用于我浏览OpenCV文档 wrt 边界处理和使用有限内核(FIR 过滤)。

  1. 顺便说一句:您的初始测试对四舍五入很敏感,但您已经解决了这个问题并显示错误要大得多。

  2. 当心图像边框效果。对于边缘附近的像素,使用提供的方法之一(BORDER_DEFAULT, BORDER_REPLICATE,等...)虚拟扩展图像。如果您的图像是|abcd|并且您使用BORDER_REPLICATE的是有效地过滤扩展图像 aaaa|abcd|dddd。结果是klmn|opqr|stuv。有新的像素值(k,l,m,n,s,t,u,v)会立即被丢弃以产生输出图像|opqr|。如果您现在应用另一个高斯模糊,这种模糊将对新扩展的图像进行操作oooo|opqr|rrrr- 与"true"中间结果不同,从而为您提供与具有更大 sigma 的单个高斯模糊获得的结果不同的结果。These extension methods are safe though: REFLECT, REFLECT_101, WRAP.

  3. 使用有限的内核大小,G(s1)*G(s2)=G(sqrt(s1^2+s2^2))由于内核的尾部被切断,该规则通常不成立。您可以通过增加相对于 sigma 的内核大小来减少由此引入的错误,例如:

    int size = (int)(2.0 * 10.0 * sigma + 1.0); if (size % 2 == 0) size++;
    

第 3 点似乎是“咬”你的问题。你真的关心G(s1)*G(s2)财产是否被保存下来。这两个结果在某种程度上都是错误的。它是否会在很大程度上影响对结果起作用的方法?请注意,10x sigma我给出的使用示例可能会解决差异,但会慢得多。

更新:我忘了添加最实用的解决方案。使用傅里叶变换计算高斯模糊。该计划将是:

  • 计算输入图像的傅里叶变换 (FFT)
  • 乘以高斯核的傅里叶变换并计算傅里叶逆变换。忽略复数输出的虚部。

您可以在维基百科上找到频域中的高斯方程

您可以为每个比例 (sigma) 单独(即并行)执行第二步。以这种方式计算模糊的边界条件是BORDER_WRAP。如果您愿意,您可以实现相同的效果,但BORDER_REFLECT如果您使用离散余弦变换 (DCT) 来代替。不知道OpenCV有没有提供。您将在DCT-II 之后

于 2017-04-28T21:56:22.063 回答
0

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

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

在此处输入图像描述

在哪里blursingle被四舍五入convertTo(...,CV8U)diff是他们不同的地方。因此,在 DSP 方面,它可能不是一个很好的协议。但在图像处理中,它并没有那么糟糕。

此外,我怀疑当您在更大的图像上执行高斯运算时,差异将不那么显着。

于 2017-04-14T17:03:25.643 回答