Вернуться к разделу "Реализация проекта BookScanLib".


14. Фильтр Selective Gaussian Blur

Фильтр Selective Gaussian Blur (избирательное гауссово размытие) предназначен для размытия изображения с сохранением деталей.

Я написал простейшую консольную программу для демонстрации работы фильтра Gaussian Blur. Для размытия исходного изображения она использует алгоритм Gaussian Blur из GIMP. На входе она принимает следующие параметры:

sel_gauss <input_file> <radius (double)> <max_delta (int)>

По умолчанию рекомендуются следующие значения:

Radius = 5.0 (обратите внимание - это дробное, а не целое число)

Max-delta = 50 (целое)

Точность дробных чисел - до одной десятой.

На выходе программа выдаёт этот же файл, обработанный выбранным фильтром.

Программа работает только с серыми и цветными изображениями.

Всё необходимое для тестирования этой программы (компиляционный проект, готовый экзешник, файл-пример и bat-файлы для тестирования программы) я оформил в небольшой пакет:

Скачать пакет sel_gauss (51 КБ)

(Для работы программы требуется FreeImage dll-библиотека из пакета FreeImage DLL v3.9.2 - см. статью 1. Знакомство с FreeImage).

Рассмотрим исходные коды этой программы:


/* Selective gaussian blur filter for GIMP, version 0.1
* Adapted from the original gaussian blur filter by Spencer Kimball and
* Peter Mattis.
*
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
* Copyright (C) 1999 Thom van Os <thom@vanos.com>
* Copyright (C) 2006 Loren Merritt
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* To do:
*      - support for horizontal or vertical only blur
*      - use memory more efficiently, smaller regions at a time
*      - integrating with other convolution matrix based filters ?
*      - create more selective and adaptive filters
*      - threading
*      - optimization
*/

// This algorithm was taken from the GIMP v2.4.5 sourcecodes
// and adopted for the FreeImage library
//
//  Copyright (C) 2007-2008:
//  monday2000  monday2000@yandex.ru

#include "FreeImage.h"
#include "Utilities.h"

#define PI  ((double)3.14159265358979323846264338327950288419716939937510)

/////////////////////////////////////////////////////////////////////////////////////////////

static void init_matrix (double radius, double *mat, int num)
{
    int    dx;
    double sd, c1, c2;
    
    // This formula isn't really correct, but it'll do
    sd = radius / 3.329042969;
    c1 = 1.0 / sqrt (2.0 * PI * sd);
    c2 = -2.0 * (sd * sd);
    
    for (dx = 0; dx < num; dx++)
        mat[dx] = c1 * exp ((dx * dx)/ c2);
}

/////////////////////////////////////////////////////////////////////////////////////////////

static FIBITMAP* sel_gauss (FIBITMAP* src_dib, double radius, int maxdelta)
{
	double	   *mat;
	int 		numrad;
	
	unsigned bpp = FreeImage_GetBPP(src_dib);
	
	unsigned btpp = bpp/8;
	
	unsigned width = FreeImage_GetWidth(src_dib);
	
	unsigned height = FreeImage_GetHeight(src_dib);
	
	unsigned pitch = FreeImage_GetPitch(src_dib);
	
	unsigned d;
	
	FIBITMAP* dst_dib = FreeImage_Allocate(width, height, bpp);	
	
	BYTE* src_bits = (BYTE*)FreeImage_GetBits(src_dib); // The image raster
	
	BYTE* dst_bits = (BYTE*)FreeImage_GetBits(dst_dib); // The image raster
	
	BYTE* lines, *linek, *lined;
	
	
	numrad = (int)(radius + 1.0);
	
	mat = (double*) calloc (numrad, sizeof(double));
	
	init_matrix(radius, mat, numrad);
	
    unsigned short    *imat;
    double     fsum, fscale;
    int        i, j, x, y;
	
    
    imat = (unsigned short*)calloc (2 * numrad, sizeof(unsigned short));
    
    fsum = 0.0;
    for (y = 1 - numrad; y < numrad; y++)
        fsum += mat[abs(y)];
    
    // Ensure that the sum fits in 32bits.
    fscale = 0x1000 / fsum;
    for (y = 0; y < numrad; y++)
        imat[numrad - y] = imat[numrad + y] = mat[y] * fscale;
	
	/////////////////////////////////////////
	
    unsigned* p_sum = new unsigned[btpp];
    unsigned* p_fact = new unsigned[btpp];
	
    unsigned* p_rowsum = new unsigned[btpp];
    unsigned* p_rowfact = new unsigned[btpp];
	
	int di;
	
	int tmp;
	
	int flag = 0;
	
	/////////////////////////////////////////
    
    for (y = 0; y < height; y++)
    {
		lines = src_bits + y * pitch;
		
		lined = dst_bits + y * pitch;
		
        for (x = 0; x < width; x++)
        {	
			memset(p_sum, 0, sizeof(unsigned)*btpp);
			memset(p_fact, 0, sizeof(unsigned)*btpp);
			
			linek = src_bits + pitch * (y - numrad);		
			
			for (j = 1 - numrad; j < numrad; j++)
			{
				memset(p_rowsum, 0, sizeof(unsigned)*btpp);
				memset(p_rowfact, 0, sizeof(unsigned)*btpp);
				
				linek += pitch;
				
				if (y + j < 0 || y + j >= height)
					continue;				
				
				for (i = 1 - numrad; i < numrad; i++)
				{
					if ((x + i) < 0 || (x + i) >= width)					
						continue;
					
					for (d=0; d<btpp; d++)				
					{					
						
						tmp = lines[x * btpp + d] - linek[(x + i) * btpp + d];
						
						if (tmp > maxdelta || tmp < -maxdelta)
						{
							continue;
						}
						
						di = imat[numrad+i];
						
						p_rowsum[d] += di * linek[(x + i) * btpp + d];
						p_rowfact[d] += di;			
						
					}
				}
				
				di = imat[numrad+j];
				
				for (d=0; d<btpp; d++)			
				{				
					p_sum[d] += di * p_rowsum[d];
					p_fact[d] += di * p_rowfact[d];
				}
			}
			
			for (d=0; d<btpp; d++)			
			{
				if (p_fact[d] == 0)
					lined[x * btpp + d] = lines[x * btpp + d];
				else
					lined[x * btpp + d] = p_sum[d] / p_fact[d];
			}
		}
    }
    
	// free up buffers
    free (imat);
	free (mat);
	
	delete [] p_sum;
	
	delete [] p_fact;
	
	delete [] p_rowsum;
	
	delete [] p_rowfact;
	
	if(bpp == 8)
	{
		// copy the original palette to the destination bitmap
		
		RGBQUAD *src_pal = FreeImage_GetPalette(src_dib);
		RGBQUAD *dst_pal = FreeImage_GetPalette(dst_dib);
		memcpy(&dst_pal[0], &src_pal[0], 256 * sizeof(RGBQUAD));		
	}
	
	// Copying the DPI...
	
	FreeImage_SetDotsPerMeterX(dst_dib, FreeImage_GetDotsPerMeterX(src_dib));
	
	FreeImage_SetDotsPerMeterY(dst_dib, FreeImage_GetDotsPerMeterY(src_dib));
	
	return dst_dib;
}

/////////////////////////////////////////////////////////////////////////////////////////////
// ----------------------------------------------------------
/**
FreeImage error handler
@param fif Format / Plugin responsible for the error 
@param message Error message
*/
void FreeImageErrorHandler(FREE_IMAGE_FORMAT fif, const char *message) {
	printf("\n*** "); 
	printf("%s Format\n", FreeImage_GetFormatFromFIF(fif));
	printf(message);
	printf(" ***\n");
}

/////////////////////////////////////////////////////////////////////////////////////////////
// ----------------------------------------------------------

/** Generic image loader

  @param lpszPathName Pointer to the full file name
  @param flag Optional load flag constant
  @return Returns the loaded dib if successful, returns NULL otherwise
*/

FIBITMAP* GenericLoader(const char* lpszPathName, int flag)
{	
	FREE_IMAGE_FORMAT fif = FIF_UNKNOWN;
	// check the file signature and deduce its format
	// (the second argument is currently not used by FreeImage)
	
	fif = FreeImage_GetFileType(lpszPathName, 0);
	
	FIBITMAP* dib;
	
	if(fif == FIF_UNKNOWN)
	{
		// no signature ?
		// try to guess the file format from the file extension
		fif = FreeImage_GetFIFFromFilename(lpszPathName);
	}
	
	// check that the plugin has reading capabilities ...
	if((fif != FIF_UNKNOWN) && FreeImage_FIFSupportsReading(fif))
	{
		// ok, let's load the file
		dib = FreeImage_Load(fif, lpszPathName, flag);
		
		// unless a bad file format, we are done !
		if (!dib)
		{
			printf("%s%s%s\n","File \"", lpszPathName, "\" not found.");
			return NULL;
		}
	}	
	
	return dib;
}

/////////////////////////////////////////////////////////////////////////////////////////////

// Selective Gaussian Blur

// Blur neighboring pixels, but only in low-contrast areas,
// This filter functions similar to the regular
// gaussian blur filter except that neighbouring
// pixels that differ more than the given maxdelta
// parameter will not be blended with. This way with
// the correct parameters, an image can be smoothed
// out without losing details. However, this filter
// can be rather slow.
// Thom van Os,
// 1999

// double radius    // Radius of gaussian blur (in pixels, > 0)
// int max-delta  // Maximum delta

// 5.0  // default radius
// 50   // default max-delta

int main(int argc, char *argv[])
{
	
	// call this ONLY when linking with FreeImage as a static library
#ifdef FREEIMAGE_LIB
	FreeImage_Initialise();
#endif // FREEIMAGE_LIB
	
	// initialize your own FreeImage error handler
	
	FreeImage_SetOutputMessage(FreeImageErrorHandler);
	
	if(argc !=4) {
		printf("Usage : sel_gauss <input_file> <radius (double)> <max_delta (int)>\n");
		return 0;
	}
	
	FIBITMAP *dib = GenericLoader(argv[1], 0);
	
	double radius = atof(argv[2]);
	
	int maxdelta = atof(argv[3]);
	
	if (dib)
	{		
		// bitmap is successfully loaded!
		
		if (FreeImage_GetImageType(dib) == FIT_BITMAP)
		{
			if (FreeImage_GetBPP(dib) == 8 || FreeImage_GetBPP(dib) == 24)
			{
				FIBITMAP* dst_dib = sel_gauss (dib, radius, maxdelta);				
				
				if (dst_dib)
				{					
					// save the filtered bitmap
					const char *output_filename = "filtered.tif";
					
					// first, check the output format from the file name or file extension
					FREE_IMAGE_FORMAT out_fif = FreeImage_GetFIFFromFilename(output_filename);
					
					if(out_fif != FIF_UNKNOWN)
					{
						// then save the file
						FreeImage_Save(out_fif, dst_dib, output_filename, 0);
					}
					
					// free the loaded FIBITMAP
					FreeImage_Unload(dst_dib);					
				}
			}
			
			else
				
				printf("%s\n", "Unsupported color mode.");
		}
		
		else // non-FIT_BITMAP images are not supported.
			
			printf("%s\n", "Unsupported color mode.");
		
		FreeImage_Unload(dib);
	}	 
	
	// call this ONLY when linking with FreeImage as a static library
#ifdef FREEIMAGE_LIB
	FreeImage_DeInitialise();
#endif // FREEIMAGE_LIB
	
	return 0;

Реализация данного алгоритма позаимствована из графического редактора GIMP v2.4.5.

Размывание осуществляется посредством гауссового размытия (Gaussian Blur). Это однопроходный фильтр, напоминающий Gaussian Blur, который был описан в 12. Морфологические фильтры. Разница в том, что этот Gaussian Blur строит таблицу сверки - которая задаёт точность размытия (я не слишком разбирался в этом алгоритме - подробнее рассказать не могу).

Как видно из исходных кодов, параметр Radius влияет на формирование 2-х вещей: значений таблицы сверки и размера апертуры (квадратное окошко, пробегающее по всем пикселям).

Теперь о том, в чём проявляется селективность данного фильтра.

Во-первых, соседние пиксели, отличающиеся от обрабатываемого на величину бОльшую, чем Max-delta, пропускаются (не участвуют в размытии). Во-вторых, текущий пиксель размывается, только если сумма сверки соседних пикселей отличается от нуля.

Этот фильтр (с хорошо подобранными параметрами) размывает изображение, не теряя его детали.

Посмотрите прилагаемый пример: текст почти не меняется, зато фон заметно размывается.

На мой взгляд, это крайне любопытный алгоритм.

Hosted by uCoz