Вернуться к разделу "Реализация проекта BookScanLib".
Под морфологическими фильтрами в данном случае понимаются следующие алгоритмы:
1. Расширение (Erosion)
2. Сжатие (Dilation).
3. Нахождение расширенного контура (Eroded Contour).
4. Нахождение сжатого контура (Dilated Contour).
Я написал простейшую консольную программу для демонстрации работы семества данных морфологических фильтров. На входе она принимает имя графического файла через командную строку, номер фильтра и размер апертуры (рекомендуется от 2 до 7), а на выходе выдаёт этот же файл, обработанный выбранным фильтром.
В зависимости от размера апертуры, степень утолщения / утоньшения символов будет больше / меньше.
Программа работает только с серыми и цветными изображениями. Обработку чёрно-белых изображений можно легко добавить при необходимости (она будет даже проще, т.к. будет представлять вырожденный случай имеющейся обработки).
Всё необходимое для тестирования этой программы (компиляционный проект, готовый экзешник, файл-пример и bat-файлы для тестирования программы) я оформил в небольшой пакет:
Скачать пакет morph_filters (45 КБ)
(Для работы программы требуется 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 choice, int size_k) { // choice = 1: Erosion // choice = 2: Dilation // choice = 3: Eroded Contour // choice = 4: Dilated Contour // size_k: kernel size = 2...7 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; BYTE* pval1 = new BYTE[btpp]; BYTE* pval2 = new BYTE[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(pval1, 0xFF, sizeof(BYTE)*btpp); // biggest possible memset(pval2, 0x00, sizeof(BYTE)*btpp); // smallest possible // kernel processing for (i = 0; i < size_k; i++) for (j = 0; j < size_k; 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; pval1[d] = MIN(pval1[d],linek[k_index * btpp + d]); // erosion pval2[d] = MAX(pval2[d],linek[k_index * btpp + d]); // dilation } } for (d=0; d<btpp; d++) { switch (choice) { case 1: lined[col * btpp + d] = pval1[d]; // Erosion break; case 2: lined[col * btpp + d] = pval2[d]; // Dilation break; case 3: lined[col * btpp + d] = lines[col * btpp + d] - pval1[d]; // Eroded Contour break; case 4: lined[col * btpp + d] = pval2[d] - lines[col * btpp + d]; // Dilated Contour } } } } 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 [] pval1; delete [] pval2; 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 != 4) { printf("Usage : morph_filters <input_file> <filter_number> <kernel_size>\n"); return 0; } FIBITMAP *dib = GenericLoader(argv[1], 0); int choice = atoi(argv[2]); int size_k = atoi(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 = FI_filter(dib, choice, size_k); 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.6 показаны три новых пространственных
фильтра: фильтры сжатия и расширения, а также
контурный фильтр. Чтобы результат был более
наглядным, изображения выводятся в масштабе 2:1.
Это так называемые морфологические фильтры. Они
отличаются от предыдущих фильтров, основанных на
линейной комбинации пикселов. Морфологический
фильтр использует матрицу N х N для проверки
соседних пикселов. Результат проверки
определяет цвет пиксела, находящегося в центре.
Фильтр сжатия генерирует черный цвет лишь в том случае, если все пикселы блока окрашены в черный цвет. В противном случае генерируется белый цвет. Таким образом, в результате применения фильтра сжатия белые участки изображения расширяются.
Фильтр расширения генерирует белый цвет лишь в том случае, если все пикселы блока окрашены в белый цвет. В противном случае генерируется черный цвет. Таким образом, в результате применения фильтра расширения белые участки изображения сужаются.
(Имеется в виду, что если в блоке почти все пиксели белые, и есть хоть один чёрный - то будет вставляться чёрный пиксел - и наоборот для белых - monday2000).
Контурный фильтр сначала выполняет сжатие, а затем вычитает из полученного изображения оригинал. Для равномерно окрашенных областей контурный фильтр генерирует черный цвет (0), поскольку сжатое изображение совпадает с оригиналом. Новые белые пикселы, возникшие в результате сжатия, остаются белыми. В результате возникает белый контур исходного изображения на черном фоне.
На рисунке показано, к каким результатам приводит применение всех трех фильтров к монохромному изображению текстового символа. Черный цвет является основным, а белый - фоновым, поэтому при сжатии белые фоновые участки увеличиваются, а основные черные - уменьшаются. Расширение приводит к обратным последствиям. В нашем примере линии буквы «т» при сжатии становятся тоньше, а при расширении - толще. Контурный фильтр оставляет белый контур буквы.
Эти морфологические фильтры создавались для работы с монохромными изображениями. При работе с цветными изображениями, разделенными на несколько каналов в оттенках серого, расширение имитируется через вычисление минимума, а сжатие - через вычисление максимума.
Для цветных изображений также можно было вычислить минимальное значение по восьми пикселам, окружающим центральный пиксел, и вернуть в качестве результата среднее арифметическое центрального пиксела и минимума. В этом варианте эффект расширения несколько снижается. Чтобы создать фильтр сжатия, достаточно вместо минимума вычислить максимум.