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


16. Простейший фильтр Illuminance Correction

Фильтр Illuminance Correction (коррекция освещённости) предназначен для выравнивания неравномерностей освещённости скана и приведению её вариаций к среднему значению.

Наиболее хорошо эту операцию умеет делать Book Restorer v4.1.

Также эту операцию умеют делать СканКромсатор v5.91 и программа ImCb от U235.

Я сделал Пример обработки неравномерно-освещённого скана (744 КБ), который демонстрирует качество выравнивания освещённости во всех этих 3-х программах. На этом примере видно, что BookRestorer 4.1 значительно превосходит обе другие программы по качеству выравнивания освещённости.

Данная реализация алгоритма выравнивания освещённости является простейшей. К сожалению, её качество далеко не идеально - но, в то же время, достаточно прилично.


Кстати, на мой взгляд, существует некая путаница в терминах, обозначающих данный алгоритм. Многие называют его как "Illumination Correction", в Book Restorer 4.1 он называется "Lighting Correction". На мой взгляд, оба названия не совсем точны. Я предлагаю более точное название для этого алгоритма - "Illuminance Correction". "Illumination" - это, на мой взгляд, "степень интенсивности собственного излучаемого объектом света", а "Lighting" - это "внешнее освещение". "Illuminance" - это как раз (по-моему) по смыслу "пассивная освещённость". А "Illumination" - это "активная" освещённость. Ещё более точным было бы слово "Illuminatedness" - но такого слова не существует. :)


Я написал простейшую консольную программу для демонстрации работы фильтра Illuminance Correction. На входе она принимает следующие параметры:

illum_corr <input_file> <radius (double)> <amount (double)> <threshold (int)> <rescale_mode (bool)> <rescale_ratio (int)>

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

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

Amount = 0 (по-моему, этот параметр ни на что не влияет)

Threshold  = 0 (по-моему, этот параметр ни на что не влияет)

Rescale_mode  = 0 (или 1) (необязательный параметр; использовать / не использовать уменьшение)

Rescale_ration =  3 (необязательный параметр; степень сжатия)

Поскольку алгоритм базируется на использовании 2-x-проходного Gaussian Blur из 13. Фильтр Unsharp Mask (на базе Gaussian Blur), то параметры Radius, Amount, Threshold влияют именно на Gaussian Blur.

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

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

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

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

Скачать пакет illum_corr (192 КБ)

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

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


// AForge Image Processing Library
// AForge.NET framework
//
// Copyright ©
//   Mladen Prajdic  (spirit1_fe@yahoo.com),
//   Andrew Kirillov (andrew.kirillov@gmail.com)
// 2005-2008
//
// This algorithm was taken from the AForge.NET sourcecodes
// and adopted for the FreeImage library
//
// Copyright (C) 2007-2008:
// monday2000	monday2000@yandex.ru

// Flat field correction filter.

// The goal of flat-field correction is to remove artifacts from 2-D images that
// are caused by variations in the pixel-to-pixel sensitivity of the detector and/or by distortions
// in the optical path. The filter requires two images for the input - source image, which represents
// acquisition of some objects (using microscope, for example), and background image, which is taken
// without any objects presented. The source image is corrected using the formula: src = bgMean * src / bg,
// where src - source image's pixel value, bg - background image's pixel value, bgMean - mean
// value of background image.
// 
// If background image is not provided, then it will be automatically generated on each filter run
// from source image. The automatically generated background image is produced running Gaussian Blur on the
// original image with (kernel size is set to 5). Before blurring the original image
// is resized to 1/3 of its original size and then the result of blurring is resized back to the original size.

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

typedef struct pal
{
	BYTE color;
	long c;
}
PAL;

int palLength;

PAL palette[255] = {0};

extern FIBITMAP* unsharp_mask (FIBITMAP* src_dib, double radius, double amount, int threshold, int only_blur);

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

void SetPalIndex(BYTE color)
{	
	// builds a histogram - to find out a dominant color later
	int i;
	
	PAL pal_color;
	
	for (i = 0; i < palLength; i++)
		if (palette[i].color == color)
		{
			palette[i].c++;
			return;
		}
		
		pal_color.color = color;
		pal_color.c = 0;
		
		palette[palLength++] = pal_color;
}

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

FIBITMAP* FI_filter(FIBITMAP* src_dib, FIBITMAP* bg_dib)
{
	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;
	
	
	
	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* bg_bits = (BYTE*)FreeImage_GetBits(bg_dib); // The image raster
	
	BYTE* dst_bits = (BYTE*)FreeImage_GetBits(dst_dib); // The image raster
	
	BYTE* lines, *lined, *lineb;
	
	for (row = 0; row < height; row++)
	{
		lineb = bg_bits + row * pitch;
		
		for (col = 0; col < width; col++)
			
			SetPalIndex(lineb[col]);			
	}
	
	// finding the dominant color
	int i;
	
	int max = 0;
	
	int max_pos = 0;
	
	for (i = 0; i < palLength; i++)
		
		if (palette[i].c > max)
		{
			max = palette[i].c;
			
			max_pos = i;
		}
		
		int mean = palette[max_pos].color; // dominant color
		
		
		
		for (row = 0; row < height; row++)
		{
			lines = src_bits + row * pitch;
			
			lineb = bg_bits + row * pitch;
			
			lined = dst_bits + row * dst_pitch;
			
			for (col = 0; col < width; col++)
			{
				if (lines[col] != 0)
				{
					double res = (double)lines[col]/lineb[col]*mean;
					
					// clamp and place result in destination pixel
					
					lined[col] = (BYTE)MIN(MAX((int)0, (int)(res + 0.5)), (int)255);
				}
				else				
					
					lined[col] = lines[col];
			}	
		}
		
		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;
}

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

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 < 5) {
		printf("Usage : illum_corr <input_file> <radius (double)> <amount (double)> <threshold (int)> <rescale_mode (bool)> <rescale_ratio (int)>\n");
		return 0;
	}
	
	FIBITMAP *dib = GenericLoader(argv[1], 0);
	
	double radius = atof(argv[2]);

	double amount = atof(argv[3]);

	int threshold = atof(argv[4]);

	int only_blur = 1;

	int rescale_mode = 0;

	if(argc == 6) rescale_mode = atof(argv[5]);

	int rescale_ratio = 3;

	if(argc == 7) rescale_ratio = atof(argv[6]);

	FIBITMAP* dst_dib;

	FIBITMAP* blurred_dib;


	if (dib)
	{		
		// bitmap is successfully loaded!
		
		if (FreeImage_GetImageType(dib) == FIT_BITMAP)
		{
			if (FreeImage_GetBPP(dib) == 8)
			{
				if (FreeImage_GetColorType(dib) != FIC_MINISBLACK)
				{
					printf("This is a palettized 8-bit bitmap - convert it to greyscale.");

					return NULL;				
				}

				if (!rescale_mode)
				{

				blurred_dib = unsharp_mask (dib, radius, amount, threshold, only_blur);				

				dst_dib = FI_filter(dib, blurred_dib);
				}

				else
				{

				FIBITMAP* rescaled_dib = FreeImage_Rescale(dib, FreeImage_GetWidth(dib)/rescale_ratio,
					FreeImage_GetHeight(dib)/rescale_ratio, FILTER_BICUBIC);				
				
				blurred_dib = unsharp_mask (rescaled_dib, radius, amount, threshold, only_blur);

				FreeImage_Unload(rescaled_dib);

				FIBITMAP* bg_dib = FreeImage_Rescale(blurred_dib, FreeImage_GetWidth(dib),
					FreeImage_GetHeight(dib), FILTER_BICUBIC);				

				dst_dib = FI_filter(dib, bg_dib);

				FreeImage_Unload(bg_dib);
				
				}
				
				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);
					
					FreeImage_Unload(blurred_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;
}

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

Реализация данного алгоритма позаимствована из программной графической библиотеки AForge.NET.

Краткое описание алгоритма:

Исходное изображение размывается посредством качественного Gausian Blur. Проходим в цикле по всем пикселям исходного изображения, делим текущий пиксель на соотв. размытый, умножаем результат на некую константу - и, если текущий пиксель не равен нулю (для деления важно), то помещаем результат этой пропорции в формируемую картинку. Если равен нулю - просто  копируем пиксель.

В качестве константы можно брать любое число от 0 до 255. Здесь вычисляется и используется доминантный цвет исходной картинки (чтобы итоговое изображение было близкО по яркости к исходному).

Опционально в алгоритме предусмотрен такой приём: исходный скан уменьшается в несколько раз (через Bicubic Rescale), размывается, увеличивается до прежних размеров и т.д. Качество результата от этого меняется - можете посмотреть самостоятельно.

В целом, данная реализация оказалась не настолько плохой, насколько я ожидал. Она, пожалуй, подойдёт по принципу "на безрыбье и рак рыба".

Hosted by uCoz