# Blob detection

One approach for blob detection is the detection of local maxima of the Laplacian filter response in the scale space of the input image.

This means that you:

• you first construct a scale-space = image pyramid with N levels (= 'octaves') and on each level of the image pyramid you generate blurred version of the image with different blurring levels
• you filter each such blurred image with the Laplacian filter
• you detect local maxima (minima) of these filter responses, where a blob has to be a local maximum not only in the same image (8 neighbours), but also compared to neighboured scales (2×9 neighbours), i.e. in total 8+18=26 neighbours are considered

I recommend this very intuitive introduction by AIShack

## Scale Space

• the image is resized within an image pyramid (each resize level is called octave)
• on each image pyramid level the image is blurred with different sigmas for the Gaussian blur kernel (nr of blur levels)

For SIFT e.g. typically 4 octaves + 5 blur levels are used.

Intros to scale space

• 3 (with code)
• 4 (best intro I could find)

## Difference of Gaussians

• the Laplacian of an image can be approximated by the difference of two Gaussians with different sigmas

## Scale Space + Laplacian Filter Response Demo

input images:

blurred images:

Laplacian filter responses:

## C++ code for the Laplacian demo

```inline void getGaussKernelSizeAndSigmaForLevel(int l, int& gaussian_kernel_size, int& gauss_sigma)
{
gauss_sigma = l;
gaussian_kernel_size = gauss_sigma * 5;
if (gaussian_kernel_size % 2 == 0)
gaussian_kernel_size++;
}

void MyKeypointDetector::start()
{
cv::VideoCapture cap;
cap.open(1);

double minVal,maxVal;

const int NrLevels = 10;
cv::Mat scaleSpace_blurredImages[NrLevels];
cv::Mat scaleSpace_laplacianResponses[NrLevels];
cv::Mat scaleSpace_laplacianResponses_normalized[NrLevels];

int showLevelNr = 1;

int screenshot_counter = 0;

while (true)
{
// measure time
long start_time = clock();

// get next camera image + convert it to gray scale
cv::Mat frameRGB;
cap >> frameRGB;
if( frameRGB.empty() )
break;
cv::Mat frame;
cv::cvtColor( frameRGB, frame, CV_RGB2GRAY );

///
/// compute scale space + Laplacian filter responses
/// note: here only one octave = image pyramid level
///
for (int l=0; l<NrLevels; l++)
{
// compute gauss kernel size + sigma for blurring
int gauss_kernel_size;
int gauss_sigma;
getGaussKernelSizeAndSigmaForLevel(l, gauss_kernel_size, gauss_sigma);

// blur image
cv::GaussianBlur( frame, scaleSpace_blurredImages[l], cv::Size(gauss_kernel_size,gauss_kernel_size), gauss_sigma, gauss_sigma );

// do Laplacian transform
int ddepth = CV_16S;
int laplacian_kernel_size = 5;
int scale = 1;
int delta = 0;
cv::Laplacian( scaleSpace_blurredImages[l], scaleSpace_laplacianResponses[l], ddepth, laplacian_kernel_size, scale, delta );

// normalize image to 8 bit
cv::convertScaleAbs( scaleSpace_laplacianResponses[ l ], scaleSpace_laplacianResponses_normalized[ l ], (gauss_sigma+1)*0.25 );

}

// stop time
long elapsed_time = clock() - start_time;
float fps = 1000.0/(float)elapsed_time;
char txt[100];
sprintf_s(txt, "%d ms / %.1f fps", elapsed_time, fps);
cv::putText( frameRGB, txt, cv::Point(5,25), CV_FONT_HERSHEY_SIMPLEX, 0.7f, CV_RGB(0,255,0) );

///
/// show images
///

// show camera image
cv::imshow( "cam", frameRGB );

// show blurred image
int gauss_kernel_size;
int gauss_sigma;
getGaussKernelSizeAndSigmaForLevel(showLevelNr, gauss_kernel_size, gauss_sigma);
sprintf_s( txt, "smoothed with gauss_kernel_size=%d + gauss_sigma=%d", gauss_kernel_size, gauss_sigma );
cv::putText( scaleSpace_blurredImages[ showLevelNr ], txt, cv::Point(5,30), CV_FONT_HERSHEY_SIMPLEX, 0.5f, CV_RGB(0,255,0) );
cv::imshow( "blurred image", scaleSpace_blurredImages[ showLevelNr ] );

// show Laplacian filter response image
cv::imshow( "laplacian filter response", scaleSpace_laplacianResponses_normalized[ showLevelNr ] );

cv::waitKey(1);

///
/// save images
///

char fname[500];

sprintf_s( fname, "W:\\tmp\\lapdemo_input_img_%05d.png", screenshot_counter);
cv::imwrite( fname, frameRGB );

sprintf_s( fname, "W:\\tmp\\lapdemo_blurred_img_%05d.png", screenshot_counter);
cv::imwrite( fname, scaleSpace_blurredImages[ showLevelNr ] );

sprintf_s( fname, "W:\\tmp\\lapdemo_laplacian_response_%05d.png", screenshot_counter);
cv::imwrite( fname, scaleSpace_laplacianResponses_normalized[ showLevelNr ] );

screenshot_counter++;

// show another scale-space level in the next loop
showLevelNr++;
if (showLevelNr==NrLevels)
showLevelNr=1;

} // while (forever)

}```