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


11. Конволюционные фильтры

Конволюционные фильтры - это алгоритмы, построенные на принципе конволюции (Convolution).

Конволюция - это замена обрабатываемого пикселя на средневзвешенное (с различными весовыми коэффициентами) значение его малой окрестности.

Конволюционные фильтры могут обрабатывать только серые и цветные изображения (чёрно-белые - нет).

Проще говоря:

1. Окошко 3х3 пикселя ("апертура") пробегает по всем пикселям изображения.

2. Каждому из 9 соседних пикселей и центру назначается свой весовой коэффициент.

3. Номер цвета каждого пикселя умножается на соответствующий коэффициент и все 10 полученных значений складываются.

4. Найденная сумма делится на общий вес, к результату прибавляется добавка (иногда).

5. Результат вставляется на место обрабатываемого пикселя.

Существует целое семейство конволюционных фильтров, отличающихся друг от друга лишь матрицей весовых коэффициентов (алгоритм обработки - один и тот же).

В этой статье мы рассмотрим следующие фильтры:

1. Blur (Размытие):

1   1   1

1   1   1

1   1   1

/ 9

2. Gaussian Blur: (Гауссово размытие)

0   1   0

1   4   1

0   1   0

/ 8
3. Sharpening (Резкость):

0   -1   0

-1   9   -1

0   -1   0

/ 5
4. Laplasian (Лапласиан - выделение границ)

-1   -1   -1

-1   8   -1

-1   -1   -1

/ 1 + 128
5. Emboss 135 degrees (Рельеф 135 град.)

1   0   0

0   0   0

0   0   -1

/ 1 + 128

6. Emboss 90 degrees, 50% (Рельеф 90 град., 50% )

0 1 0

0 0 0

0 -1 0

/ 2 + 128

Я написал простейшую консольную программу для демонстрации работы семества конволюционных фильтров. На входе она принимает имя графического файла через командную строку и номер фильтра, а на выходе выдаёт этот же файл, обработанный выбранным фильтром.

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

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

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

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


//	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
//	http://www.gnu.org/copyleft/gpl.html

//	Copyright (C) 2007-2008:
//	monday2000	monday2000@yandex.ru

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

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

FIBITMAP* FI_filter(FIBITMAP* src_dib, int f[3][3], int weight, int add)
{
	unsigned width = FreeImage_GetWidth(src_dib);
	
	unsigned height = FreeImage_GetHeight(src_dib);
	
	unsigned pitch = FreeImage_GetPitch(src_dib);
	
	unsigned bpp = FreeImage_GetBPP(src_dib);

	unsigned btpp = bpp/8;
	
	unsigned row, col, i, j, d;
	
	int k_index;
	
	FIBITMAP* dst_dib = FreeImage_Allocate(width, height, bpp);

	unsigned dst_pitch = FreeImage_GetPitch(dst_dib);
	
	BYTE* src_bits = (BYTE*)FreeImage_GetBits(src_dib); // The image raster
	
	BYTE* dst_bits = (BYTE*)FreeImage_GetBits(dst_dib); // The image raster

	BYTE* src_end_row = src_bits + (height-1) * pitch;

	int end_col = width - 1;
	
	BYTE* lines, *linek, *lined;

	int* pval = new int[btpp];

	int* psum = new int[btpp];
	
	
	for (row = 0; row < height; row++)
	{
		lines = src_bits + row * pitch;
		
		lined = dst_bits + row * dst_pitch;
		
		for (col = 0; col < width; col++)
		{
			memset(psum, 0, sizeof(int)*btpp);

			// kernel processing
			for (i = 0; i < 3; i++)
				for (j = 0; j < 3; j++)
				{					
					linek = lines + (i-1) * pitch;

					if (linek < src_bits) linek = src_bits;
					if (linek > src_end_row) linek = src_end_row;

					for (d=0; d<btpp; d++)
					{
					k_index = col+j-1;

					if (k_index < 0) k_index = 0;
					if (k_index > end_col) k_index = end_col;

					psum[d] += linek[k_index * btpp + d] * f[i][j];
					}
				}
				
				for (d=0; d<btpp; d++)
				{
				pval[d] = psum[d] / weight + add;
				
				// clamp and place result in destination pixel
				lined[col * btpp + d] = (BYTE)MIN(MAX((int)0, (int)(pval[d] + 0.5)), (int)255);
				}	
		}
	}

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

	delete [] pval;

	delete [] psum;
	
	return dst_dib;
}

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

FIBITMAP* run_filter(FIBITMAP* dib, int choice)
{
	int f[3][3]; // filter
	
	int weight = 1; // divider
	
	int add = 0; // value sometimes added to the divider
	
	switch (choice)
	{
	case 1:
		{			
			//*********************************************
			// 1. Blur:
			
			f[0][0] = 1; f[0][1] = 1; f[0][2] = 1;
			
			f[1][0] = 1; f[1][1] = 1; f[1][2] = 1;
			
			f[2][0] = 1; f[2][1] = 1; f[2][2] = 1;  
			
			weight = 9;
			
			add = 0;
			
			//*********************************************
			return FI_filter(dib, f, weight, add);	
		}

	case 2:
		{			
			//*********************************************
			// 2. Gaussian Blur:
			
			f[0][0] = 0; f[0][1] = 1; f[0][2] = 0;
			
			f[1][0] = 1; f[1][1] = 4; f[1][2] = 1;
			
			f[2][0] = 0; f[2][1] = 1; f[2][2] = 0;  
			
			weight = 8;
			
			add = 0;
			
			//*********************************************
			return FI_filter(dib, f, weight, add);	
		}

	case 3:
		{			
			//*********************************************
			// 3. Sharpening:
			
			f[0][0] = 0; f[0][1] = -1; f[0][2] = 0;
			
			f[1][0] = -1; f[1][1] = 9; f[1][2] = -1;
			
			f[2][0] = 0; f[2][1] = -1; f[2][2] = 0;  
			
			weight = 5;
			
			add = 0;
			
			//*********************************************
			return FI_filter(dib, f, weight, add);	
		}

	case 4:
		{			
			//*********************************************
			// 4. Laplasian: (contour selection)
			
			f[0][0] = -1; f[0][1] = -1; f[0][2] = -1;
			
			f[1][0] = -1; f[1][1] = 8; f[1][2] = -1;
			
			f[2][0] = -1; f[2][1] = -1; f[2][2] = -1;  
			
			weight = 1;
			
			add = 128;
			
			//*********************************************
			return FI_filter(dib, f, weight, add);	
		}

	case 5:
		{			
			//*********************************************
			// 5. Emboss 135 degrees
			
			f[0][0] = 1; f[0][1] = 0; f[0][2] = 0;
			
			f[1][0] = 0; f[1][1] = 0; f[1][2] = 0;
			
			f[2][0] = 0; f[2][1] = 0; f[2][2] = -1;  
			
			weight = 1;
			
			add = 128;
			
			//*********************************************
			return FI_filter(dib, f, weight, add);	
		} 

	case 6:
		{			
			//*********************************************
			// 6. Emboss 90 degrees, 50%
			
			f[0][0] = 0; f[0][1] = 1; f[0][2] = 0;
			
			f[1][0] = 0; f[1][1] = 0; f[1][2] = 0;
			
			f[2][0] = 0; f[2][1] = -1; f[2][2] = 0;  
			
			weight = 2;
			
			add = 128;
			
			//*********************************************
			return FI_filter(dib, f, weight, add);	
		} 
	}
	return NULL;
}

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

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

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 != 3) {
		printf("Usage : filters <input_file> <filter_number>\n");
		return 0;
	}
	
	FIBITMAP *dib = GenericLoader(argv[1], 0);
	
	int choice = atoi(argv[2]);
	
	if (dib)
	{		
		// bitmap is successfully loaded!
		
		if (FreeImage_GetImageType(dib) == FIT_BITMAP)
		{
			if (FreeImage_GetBPP(dib) == 8 || FreeImage_GetBPP(dib) == 24)
			{
				FIBITMAP* dst_dib = run_filter(dib, choice);
				
				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;
}

Реализация этих алгоритмов взята из книги:

Фень Юань. Программирование графики для Windows. СПб.: Питер, 2002.

(Эта книга есть в Интернете в формате DjVu на русском языке и в формате CHM на английском языке)

Ниже я приведу выдержки из этой книги:


Пространственные фильтры

Фильтры сглаживания и резкости

На рис. 12.4 после первого исходного рисунка показаны результаты применения трех пространственных фильтров: сглаживания, сглаживания по Гауссу и резкости (для наглядности масштаб равен 3:1).

Вверху слева изображена исходная картинка. Справа от нее показан результат применения сглаживающего фильтра. Его матрица 3x3 состоит из одних единиц, а вес равен 9. Следовательно, этот фильтр присваивает пикселу среднее значение пикселов в блоке 3x3. Сглаживающий фильтр называется низкочастотным фильтром, поскольку он сохраняет низкочастотные участки и отфильтровывает высокочастотные искажения. В частности, он может использоваться для сглаживания линий, фигур и растров, выводимых средствами GDI. На рисунке видно, как сглаживающий фильтр маскирует зазубренные края исходной картинки. После применения сглаживающего фильтра на границах глифа появляются серые пикселы.

Фильтр сглаживания по Гауссу также относится к категории низкочастотных фильтров. Вместо равномерного распределения этот фильтр назначает больший весовой коэффициент центральному пикселу. Фильтры этого типа могут определяться и для большего радиуса; на рисунке показан фильтр 3x3.

Фильтр резкости вычитает соседние пикселы из текущего, чтобы подчеркнуть переходы в изображении. Он относится к категории высокочастотных фильтров, которые выделяют высокочастотные компоненты изображения и оставляют низкочастотные участки без изменений. Регулируя весовой коэффициент центрального пиксела, можно менять степень резкости. Для монохромного изображения, показанного на рисунке, результат применения фильтра резкости практически незаметен.

Выделение границ и рельеф

На рис. 12.5 показаны результаты применения фильтра Лапласа и двух рельефных фильтров.

По виду матрицы фильтр Лапласа похож на высокочастотный фильтр, но он генерирует абсолютно другое изображение. Фильтр Лапласа относится к категории фильтров выделения границ с нулевой суммой коэффициентов матрицы. Фильтр выделения границ заменяет равномерно окрашенные области черным цветом, а области с изменениями — цветом, отличным от черного. В приведенном примере фильтр прибавляет к каждому каналу 128, чтобы отрицательный результат не заменялся нулем. В результате прибавления 128 равномерно окрашенные области становятся серыми.

Следующие два фильтра, называемые рельефными фильтрами, преобразуют цветное изображение в оттенки серого со своеобразными объемными эффектами. В одном углу матрицы рельефного фильтра стоит 1, а элемент в противоположном углу равен -1. Применение рельефного фильтра можно рассматривать как вычитание изображения, смещенного на определенную величину от оригинала. Результат увеличивается на 128, чтобы нулевая точка переместилась в середину шкалы оттенков серого. Относительные позиции пикселов со значениями 1 и -1 определяют направление рельефного выделения. В нашем примере продемонстрированы два направления. Во втором примере результат умножения делится на 2, поэтому степень рельефности изображения уменьшается.

Hosted by uCoz