<p>In <a href="https://stackoverflow.com/questions/43163337/parallelize-chain-of-gaussian-blurs">this</a> question I asked how to implement a chain of blurs in one single step. </p>

<p>Then I found out from the <a href="https://en.wikipedia.org/wiki/Gaussian_blur" rel="nofollow noreferrer">gaussian blur</a> page of Wikipedia that:</p>

<blockquote>
  <p>Applying multiple, successive gaussian blurs to an image has the same
  effect as applying a single, larger gaussian blur, whose radius is the
  square root of the sum of the squares of the blur radii that were
  actually applied. For example, applying successive gaussian blurs with
  radii of 6 and 8 gives the same results as applying a single gaussian
  blur of radius 10, since sqrt {6^{2}+8^{2}}=10.</p>
</blockquote>

<p>So I thought that <code>blur</code> and <code>singleBlur</code> were the same in the following code:</p>

<pre><code>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);
</code></pre>

<p>But this <code>assert</code> fails (and actually, for example, the first row of <code>blur</code> is different from the first one of <code>singleBlur</code>).</p>

<p>Why?</p>

<p><strong>UPDATE:</strong></p>

<p>After different comments asking for more information, I'll update the answer. </p>

<p>What I'm trying to do is to parallelize <a href="https://github.com/perdoch/hesaff" rel="nofollow noreferrer">this</a> code. In particular, I'm focusing now on computing all the blurs at all levels in advance. The serial code (<strong>which works correctly</strong>) is the following:</p>

<pre><code>   vector&lt;Mat&gt; 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&lt;levels; i++){
       blurs[i*scaleCycles+1] = octaveLayer.clone();
       for (int j = 1; j &lt; 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]);
       }
   }
</code></pre>

<p>I'm sorry for the horrible indexes above, but I tried to respect the original code system (which is horrible, like starting counting from <code>1</code> instead of <code>0</code>). The code above has <code>scaleCycles=5</code> and <code>levels=6</code>, so 30 blurs are generated in total.</p>

<p>This is the "single blur" version, where first I compute the sigmas for each blur that has to be computed (following Wikipedia's formula) and then I apply the blur (notice that this is still serial and not parallelizable):</p>

<pre><code>   vector&lt;Mat&gt; singleBlurs ((par.numberOfScales+3)*levels, Mat());
   vector&lt;float&gt; singleSigmas(scaleCycles);
   float acc = 0;
   for (int j = 1; j &lt; 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&lt;levels; i++){
       singleBlurs[i*scaleCycles+1] = octaveLayer.clone();
       for (int j = 1; j &lt; scaleCycles; j++){
           float sigma = singleSigmas[j];
           std::cout&lt;&lt;"j="&lt;&lt;j&lt;&lt;" sigma="&lt;&lt;sigma&lt;&lt;std::endl;
           singleBlurs[j+1+i*scaleCycles] = gaussianBlur(singleBlurs[j+i*scaleCycles], sigma);
           if(j == par.numberOfScales)
               octaveLayer = halfImage(singleBlurs[j+1+i*scaleCycles]);
       }
   }
</code></pre>

<p>Of course the code above generates 30 blurs also with the same parameters of the previous version.</p>

<p>And then this is the code to see the difference between each <code>signgleBlurs</code> and <code>blurs</code>:</p>

<pre><code>   assert(blurs.size() == singleBlurs.size());
   vector&lt;Mat&gt; blurDiffs(blurs.size());
   for(int i=1; i&lt;levels*scaleCycles; i++){
        cv::Mat diff;
        absdiff(blurs[i], singleBlurs[i], diff);
        std::cout&lt;&lt;"i="&lt;&lt;i&lt;&lt;"diff rows="&lt;&lt;diff.rows&lt;&lt;" cols="&lt;&lt;diff.cols&lt;&lt;std::endl;
        blurDiffs[i] = diff;
        std::cout&lt;&lt;"blurs rows="&lt;&lt;blurs[i].rows&lt;&lt;" cols="&lt;&lt;blurs[i].cols&lt;&lt;std::endl;
        std::cout&lt;&lt;"singleBlurs rows="&lt;&lt;singleBlurs[i].rows&lt;&lt;" cols="&lt;&lt;singleBlurs[i].cols&lt;&lt;std::endl;
        std::cout&lt;&lt;"blurDiffs rows="&lt;&lt;blurDiffs[i].rows&lt;&lt;" cols="&lt;&lt;blurDiffs[i].cols&lt;&lt;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&lt;&lt;"converting..."&lt;&lt;std::endl;
        blurDiffs[i].convertTo(imageF_8UC3, CV_8U, 255);
        std::cout&lt;&lt;"converted"&lt;&lt;std::endl;
        imwrite( "blurDiffs_"+std::to_string(i)+".jpg", imageF_8UC3);
   }
</code></pre>

<p>Now, what I've seen is that <code>blurDiffs_1.jpg</code> and <code>blurDiffs_2.jpg</code> are black, but suddendly from <code>blurDiffs_3.jpg</code> until the <code>blurDiffs_29.jpg</code> becomes whiter and whiter. For some reason, <code>blurDiffs_30.jpg</code> is almost completely black.</p>

<p><strong>The first (correct) version generates 1761 descriptors. The second  (uncorrect) version generates >2.3k descriptors.</strong></p>

<p>I can't post the <code>blurDiffs</code> matrices because (especially the first ones) are very big and post's space is limited. I'll post some samples. I'll not post <code>blurDiffs_1.jpg</code> and <code>blurDiffs_2.jpg</code> because they're totally blacks. Notice that because of <code>halfImage</code> the images become smaller and smaller (as expected).</p>

<p><strong>blurDiffs_3.jpg:</strong></p>

<p><a href="https://i.sstatic.net/e0xLz.jpg" rel="nofollow noreferrer"><img src="https://i.sstatic.net/e0xLz.jpg" alt="enter image description here"></a></p>

<p><strong>blurDiffs_6.jpg:</strong></p>

<p><a href="https://i.sstatic.net/58ciq.jpg" rel="nofollow noreferrer"><img src="https://i.sstatic.net/58ciq.jpg" alt="enter image description here"></a> </p>

<p><strong>blurDiffs_15.jpg:</strong></p>

<p><a href="https://i.sstatic.net/yv8kd.jpg" rel="nofollow noreferrer"><img src="https://i.sstatic.net/yv8kd.jpg" alt="enter image description here"></a></p>

<p><strong>blurDiffs_29.jpg:</strong></p>

<p><a href="https://i.sstatic.net/EeoDd.jpg" rel="nofollow noreferrer"><img src="https://i.sstatic.net/EeoDd.jpg" alt="enter image description here"></a></p>

<p><strong>If you need any more details, please let me know</strong>.</p>

CC BY-SA 3.0
2017-04-15T03:16:21.960Z
Can you post the resulting images? Is it a significant difference, or is it possible that the different ways of calculating the values lead to slight differences in rounding here and there and lead to images that are very similar, but not identical?
user1118321
CC BY-SA 3.0
2017-04-15T11:05:04.900Z
@user1118321 I've updated the question with many more details, please give a look at it
user6321
<gaussian-blur><blur>
Replace a chain of image blurs with one blur
user6321
# Replace a chain of image blurs with one blur

(Asked by: username_0 on 2017-04-14)

In [this](https://stackoverflow.com/questions/43163337/parallelize-chain-of-gaussian-blurs) question I asked how to implement a chain of blurs in one single step.

Then I found out from the [gaussian blur](https://en.wikipedia.org/wiki/Gaussian_blur) page of Wikipedia that:

> Applying multiple, successive gaussian blurs to an image has the same
> effect as applying a single, larger gaussian blur, whose radius is the
> square root of the sum of the squares of the blur radii that were
> actually applied. For example, applying successive gaussian blurs with
> radii of 6 and 8 gives the same results as applying a single gaussian
> blur of radius 10, since sqrt {6^{2}+8^{2}}=10.

So I thought that `blur` and `singleBlur` were the same in the following code:

```
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);

```

But this `assert` fails (and actually, for example, the first row of `blur` is different from the first one of `singleBlur`).

Why?

**UPDATE:**

After different comments asking for more information, I'll update the answer.

What I'm trying to do is to parallelize [this](https://github.com/perdoch/hesaff) code. In particular, I'm focusing now on computing all the blurs at all levels in advance. The serial code (**which works correctly**) is the following:

```
   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]);
       }
   }

```

I'm sorry for the horrible indexes above, but I tried to respect the original code system (which is horrible, like starting counting from `1` instead of `0`). The code above has `scaleCycles=5` and `levels=6`, so 30 blurs are generated in total.

This is the "single blur" version, where first I compute the sigmas for each blur that has to be computed (following Wikipedia's formula) and then I apply the blur (notice that this is still serial and not parallelizable):

```
   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]);
       }
   }

```

Of course the code above generates 30 blurs also with the same parameters of the previous version.

And then this is the code to see the difference between each `signgleBlurs` and `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);
   }

```

Now, what I've seen is that `blurDiffs_1.jpg` and `blurDiffs_2.jpg` are black, but suddendly from `blurDiffs_3.jpg` until the `blurDiffs_29.jpg` becomes whiter and whiter. For some reason, `blurDiffs_30.jpg` is almost completely black.

**The first (correct) version generates 1761 descriptors. The second (uncorrect) version generates >2.3k descriptors.**

I can't post the `blurDiffs` matrices because (especially the first ones) are very big and post's space is limited. I'll post some samples. I'll not post `blurDiffs_1.jpg` and `blurDiffs_2.jpg` because they're totally blacks. Notice that because of `halfImage` the images become smaller and smaller (as expected).

**blurDiffs\_3.jpg:**

[![enter image description here](https://i.sstatic.net/e0xLz.jpg)](https://i.sstatic.net/e0xLz.jpg)

**blurDiffs\_6.jpg:**

[![enter image description here](https://i.sstatic.net/58ciq.jpg)](https://i.sstatic.net/58ciq.jpg)

**blurDiffs\_15.jpg:**

[![enter image description here](https://i.sstatic.net/yv8kd.jpg)](https://i.sstatic.net/yv8kd.jpg)

**blurDiffs\_29.jpg:**

[![enter image description here](https://i.sstatic.net/EeoDd.jpg)](https://i.sstatic.net/EeoDd.jpg)

**If you need any more details, please let me know**.

**Tags:** gaussian-blur, blur

> **Comments:**
> **username_1**: Can you post the resulting images? Is it a significant difference, or is it possible that the different ways of calculating the values lead to slight differences in rounding here and there and lead to images that are very similar, but not identical?  
> **username_0**: @username_1 I've updated the question with many more details, please give a look at it  

---

## 0 answers

