Вернуться к разделу "Реализация проекта BookScanLib".
Как известно теперь, любой алгоритм Despeckle работает только с чёрно-белыми изображениями. Такова его природа. На серые и цветные изображения он не оказывает никакого эффекта (или размывает их неприемлемым образом).
В прошлом пункте 9. Программная реализация базового алгоритма Despeckle мы создали канонический алгоритм Despeckle на базе медианного фильтра.
Однако, он оказался слишком медленным. В этом пункте мы сделаем более "быструю" реализацию алгоритма Despeckle. Его "убыстрение" будет достигнуто за счёт следующих моментов:
1. Использование вместо медианного фильтра (Median filter) усредняющего фильтра (Mean filter). Устредняющий фильтр - это вырожденный медианный фильтр для случая чёрно-белых изображений, поэтому алгоритм от этого нисколько не ухудшится.
2. Проходя в цикле по всем пикселям изображения, мы будем сразу же пропускать все белые пиксели. Это, конечно же, серьёзное отклонение от стандартной реализации алгоритма Despeckle. Ведь возможны ситуации, когда в рабочем окошке 3х3 пикселя в центре - белый пиксель, а вокруг него - чёрные. Эти случаи мы будем игнорировать (что не очень хорошо и правильно).
Зато это радикально, в десятки раз, увеличивает скорость работы алгоритма. При этом его качество падает незначительно (по сравнению с канонической "правильной" реализацией), а вычищающая способность даже увеличивается (правда, контуры буквы становятся чуть более "изъеденными").
Как оказалось, именно так работает алгоритм Normal Despeckle в СканКромсаторе. Это выяснилось случайно - одна и та же картинка, обработанная данным алгоритмом, и алгоритмом Normal Despeckle в СканКромсаторе, получилась абсолютно идентичной. Размер апертуры Normal Despeckle в СканКромсаторе оказался равным 3х3.
К слову, в СканКромсаторе ещё есть Fine Despeckle - это "геометрический" Despeckle, который удаляет в заданной области все объекты меньше заданного размера, и Safe Despeckle - который строит контуры букв и чистит за их пределами.
Я написал простейшую консольную программу для демонстрации работы медианного алгоритма Normal Despeckle. На входе она принимает имя графического файла через командную строку и размер обрабатывающего окна (т.н. апертура), а на выходе выдаёт этот же файл, обработанный алгоритмом Normal Despeckle.
Всё необходимое для тестирования этой программы (компиляционный проект, готовый экзешник, файл-пример и bat-файлы для тестирования программы) я оформил в небольшой пакет:
Скачать пакет despec2 (64 КБ)
(Для работы программы требуется 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
// ==========================================================
// Simple despeckle mean filter (for BW only) based on the FreeImage library
//
// This code is taken from the CxImage Library at http://www.xdp.it/cximage.htm
// and adapted for the FreeImage Library
//
#include <stdio.h>
#include <stdlib.h>
#include "FreeImage.h"
// ----------------------------------------------------------
BOOL AddPixel(BYTE *bits, unsigned bpp, unsigned x, BYTE *value)
{ // this function is simplified from FreeImage_GetPixelIndex
switch(bpp)
{
case 1:
*value += (bits[x >> 3] & (0x80 >> (x & 0x07))) != 0;
break;
default:
return FALSE;
}
return TRUE;
}
// ----------------------------------------------------------
BOOL SetPixel(BYTE *bits, unsigned bpp, unsigned x, BYTE *value)
{ // this function is simplified from FreeImage_SetPixelIndex
switch(bpp)
{
case 1:
*value ? bits[x >> 3] |= (0x80 >> (x & 0x7)) : bits[x >> 3] &= (0xFF7F >> (x & 0x7));
break;
default:
return FALSE;
}
return TRUE;
}
// ----------------------------------------------------------
/**
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);
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
FIBITMAP *dib = FreeImage_Load(fif, lpszPathName, flag);
// unless a bad file format, we are done !
return dib;
}
return NULL;
}
// ----------------------------------------------------------
FIBITMAP *CxImage_Mean(FIBITMAP *dib, unsigned Ksize)
{
// Ksize is a square sliding aperture side size and it must be ONLY an odd value
// greater than 1, e.g. 3, 5, 7, 9, 11, ...
if (!dib) return false;
FIBITMAP *tmp = FreeImage_Clone(dib);
// FreeImage_Clone - makes an exact reproduction of an existing bitmap, including
// metadata and attached profile if any
if (!tmp) return false;
// in this implementation we use only the BW-bitmaps - because despeckle
// does not produce any effect on non-BW images
//---------------------------
int k2 = Ksize/2;
int kmax = Ksize-k2;
int i,j,k;
int mean = Ksize*Ksize/2;
unsigned xmax,ymax, x1, y1;
xmax = FreeImage_GetWidth(dib);
ymax = FreeImage_GetHeight(dib);
unsigned bpp = FreeImage_GetBPP(dib);
unsigned pitch = FreeImage_GetPitch(dib);
BYTE *bits = (BYTE*)FreeImage_GetBits(dib);
BYTE *bits_tmp = (BYTE*)FreeImage_GetBits(tmp);
BYTE val;
//---------------------------
for(unsigned y = 0; y < ymax; y++)
for(unsigned x = 0; x < xmax; x++)
{
val = 0;
if (!AddPixel(bits + pitch * y, bpp, x, &val)) return false;
if (val < 1 ) continue; else val = 0;
// check if this is not a background pixel
for(k = -k2, i=0; k<kmax; k++)
for(j = -k2; j<kmax; j++, i++)
{
x1 = x+j; y1 = y+k;
if ((x1 < 0) || (y1 < 0) || (x1 > xmax) || (y1 > ymax)) continue;
// if this is a border pixel
if (!AddPixel(bits + pitch * y1, bpp, x1, &val)) return false;
}
if (val > mean) val = 1; else val = 0;
SetPixel(bits_tmp + pitch * y, bpp, x, &val);
}
return tmp;
}
// ----------------------------------------------------------
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 != 3) {
printf("Usage : despec2 <input file name> <aperture size>\n");
return 0;
}
unsigned Ksize = atoi(argv[2]);
FIBITMAP *dib = GenericLoader(argv[1], 0);
if (dib)
{
// bitmap successfully loaded!
// despeckle the loaded image
FIBITMAP *despeckled = CxImage_Mean(dib, Ksize);
// save the despeckled bitmap
const char *output_filename = "despeckled.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, despeckled, output_filename, 0);
}
// free the loaded FIBITMAP
FreeImage_Unload(dib);
// free the loaded FIBITMAP
FreeImage_Unload(despeckled);
}
// call this ONLY when linking with FreeImage as a static library
#ifdef FREEIMAGE_LIB
FreeImage_DeInitialise();
#endif // FREEIMAGE_LIB
return 0;
}
|
Алгоритм очень прост: проходим окошком 3х3 пикселя (апертурой) в цикле по всем пикселям изображения. Белые пиксели сразу пропускаем и обрабатываем только чёрные. Складываем номера цветов 9 пикселей апертуры, делим пополам. Если результат больше половины полностью-чёрной апертуры - то ставим взамен текущего пикселя чёрный, если нет - то белый. Края изображения игнорируем.