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