Вернуться к разделу "Реализация проекта BookScanLib".
Фильтр 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, пропускаются (не участвуют в размытии). Во-вторых, текущий пиксель размывается, только если сумма сверки соседних пикселей отличается от нуля.
Этот фильтр (с хорошо подобранными параметрами) размывает изображение, не теряя его детали.
Посмотрите прилагаемый пример: текст почти не меняется, зато фон заметно размывается.
На мой взгляд, это крайне любопытный алгоритм.