用一个蓝色替换一串模糊的图像

Posted

技术标签:

【中文标题】用一个蓝色替换一串模糊的图像【英文标题】:Replace a chain of image blurs with one blur 【发布时间】:2017-09-10 18:48:10 【问题描述】:

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

然后我从***的gaussian blur页面发现:

对图像应用多个连续的高斯模糊具有相同的 效果为应用单个更大的高斯模糊,其半径为 模糊半径平方和的平方根 实际应用。例如,应用连续的高斯模糊 半径为 6 和 8 的结果与应用单个高斯的结果相同 半径为 10 的模糊,因为 sqrt 6^2+8^2=10。

所以我认为blursingleBlur在下面的代码中是一样的:

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 的第一行不同)。

为什么?

更新:

在不同的cmets询问更多信息后,我会更新答案。

我想要做的是并行化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个模糊。

这是“单一模糊”版本,首先我计算每个必须计算的模糊的 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个模糊,参数和之前版本一样。

然后这是查看signgleBlursblurs之间区别的代码:

   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;
  

有人 here 建议将 diff 除以 255 以查看真正的区别,但我不明白为什么我正确理解了他。

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

【问题讨论】:

代码中使用的cv::Mat 中的每个元素是什么类型?如果它是浮点类型,那么由于浮点值在 c++ 中的表示方式,差异精确为零的概率非常小。 @G.M.不,我们说的是大错误,而不是第 9 位数字 没有考虑到我已经这样做了,但是......我不记得我是怎么做到的而且我丢失了代码(我感到羞耻) 你能量化“大错误”吗?你的代码甚至不为我编译。我在 OpenCV 文档中找不到任何 gaussianBlur。只有具有不同参数列表的 GaussianBlur。您的 sigma 未使用任何值初始化...并且您的代码中有拼写错误,例如 singleBLur 与 singleBlur... 哦,你是对的,对不起,我用diff 的一部分(一堆255 偶尔0)和原始代码(和gaussianBlur)更新了问题 【参考方案1】:

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

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

    注意图像边框效果。对于边缘附近的像素,使用提供的方法之一(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.

    使用有限的内核大小,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) 乘以高斯核的傅里叶变换并计算傅里叶逆变换。忽略复数输出的虚部。

您可以在 wikipedia 上找到频域中的高斯方程

您可以为每个比例 (sigma) 单独(即并行)执行第二步。以这种方式计算模糊的边界条件是BORDER_WRAP。如果您愿意,如果您使用离散余弦变换 (DCT),则可以使用 BORDER_REFLECT 来实现相同的效果。不知道 OpenCV 是否提供了一个。你会关注DCT-II

【讨论】:

【参考方案2】:

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

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

其中blursingleconvertTo(...,CV8U) 舍入,diff 是它们不同的地方。因此,在 DSP 方面,这可能不是一个很好的协议。但在图像处理方面,它并没有那么糟糕。

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

【讨论】:

我注意到通过使用BORDER_DEFAULT 而不是BORDER_REPLICATE 结果是完全一样的,尽管最终的结果更糟甚至更慢... 我错了,使用BORDER_DEFAULT也不起作用 顺便说一句,这个方法看起来不对:通过使用多个模糊我正确检测到 1761 个关键点,而不是使用这种方法我检测到超过 2k,这显然是错误的 我用更多细节和示例图片更新了这个问题。请看一下。 你知道为什么会这样吗?我真的被困在这里,否则我无法继续我的项目

以上是关于用一个蓝色替换一串模糊的图像的主要内容,如果未能解决你的问题,请参考以下文章

在PS中,怎么更改图片中的黄色改成绿色、蓝色、橙色啊?

用另一种颜色替换图像中的特定颜色

使用 PHP 将特定的 RGB 颜色替换为另一个图像

请问谁知道用PS将蓝色图改成绿色图呀?

在 WPF 中的应用程序中创建可换肤图像的最佳方法

是否可以更改 NavigationButton 以显示图像而不是蓝色?