Вернуться к разделу "Реализация проекта BookScanLib".
Фильтр 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), размывается, увеличивается до прежних размеров и т.д. Качество результата от этого меняется - можете посмотреть самостоятельно.
В целом, данная реализация оказалась не настолько плохой, насколько я ожидал. Она, пожалуй, подойдёт по принципу "на безрыбье и рак рыба".