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