Вернуться к разделу "Реализация проекта BookScanLib".
Бинаризация Niblack Thresholding из проекта gamera применяется для преобразования серой (8-битной) растровой картинки в чёрно-белую (1-битная).
Алгоритм Niblack Thresholding анализирует обрабатываемую картинку и автоматически вычисляет порог бинаризации - индивидуальный для каждого пикселя (т.е это локальная или адаптивная бинаризация). Найденный порог используется для обыкновенной пороговой бинаризации в отношении текущего пикселя.
Алгоритм Niblack Thresholding чрезвычайно сильно похож на Sauvola Thresholding - только немного "грубее".
Я написал простейшую консольную программу для демонстрации работы Niblack Thresholding. На входе она принимает следующие параметры:
nib_thres <input_file> <region_size (int)> <sensitivity (double)> <lower_bound (int)> <upper_bound (int)>
region_size - это размер обрабатывающего окна . 15 по умолчанию.
sensitivity - чувствительность. -0.2 по умолчанию.
lower_bound - нижний глобальный порог. 20 по умолчанию.
upper_bound - верхний глобальный порог. 150 по умолчанию.
Точность дробных - до одной десятой.
На выходе программа выдаёт этот же файл, обработанный этим алгоритмом.
Программа работает только с серыми изображениями.
Всё необходимое для тестирования этой программы (компиляционный проект, готовый экзешник, файл-пример и bat-файлы для тестирования программы) я оформил в небольшой пакет:
Скачать пакет nib_thres (41 КБ)
(Для работы программы требуется FreeImage dll-библиотека из пакета FreeImage DLL v3.9.2 - см. статью 1. Знакомство с FreeImage).
Рассмотрим исходные коды этой программы:
/*
*
* Copyright (C) 2005 John Ashley Burgoyne and Ichiro Fujinaga
* 2007 Uma Kompella and Christoph Dalitz
*
* 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.
*/
/*
niblack_threshold
Creates a binary image using Niblack's adaptive algorithm.
Niblack, W. 1986. *An Introduction to Digital Image Processing.* Englewood
Cliffs, NJ: Prentice Hall.
Like the QGAR library, there are two extra global thresholds for
the lightest and darkest regions.
*region_size* : default = 15
The size of the region in which to calculate a threshold.
*sensitivity* : default = -0.2
The sensitivity weight on the variance.
*lower bound* : range=(0,255), default=20
A global threshold beneath which all pixels are considered black.
*upper bound* : range=(0,255), default=150
A global threshold above which all pixels are considered white.
*/
// This algorithm was taken from the gamera.sf.net sourcecodes
// and adopted for the FreeImage library
//
// Copyright (C) 2007-2008:
// monday2000 monday2000@yandex.ru
#include "FreeImage.h"
#include "Utilities.h"
////////////////////////////////////////////////////////////////////////////////
inline void SetPixel(BYTE *bits, unsigned x, BYTE* value)
{ // this function is simplified from FreeImage_SetPixelIndex
*value ? bits[x >> 3] |= (0x80 >> (x & 0x7)) : bits[x >> 3] &= (0xFF7F >> (x & 0x7));
}
////////////////////////////////////////////////////////////////////////////////
void mean_filter(FIBITMAP* src_dib, int region_size, double* p_mean)
{
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 x, y, i, j;
int k_index;
BYTE* src_bits = (BYTE*)FreeImage_GetBits(src_dib); // The image raster
BYTE* src_end_row = src_bits + (height-1) * pitch;
int end_col = width - 1;
BYTE* lines, *linek;
int sum;
unsigned area = region_size * region_size;
for (y = 0; y < height; y++)
{
lines = src_bits + y * pitch;
for (x = 0; x < width; x++)
{
sum = 0;
// kernel processing
for (i = 0; i < region_size; i++)
for (j = 0; j < region_size; j++)
{
linek = lines + (i-1) * pitch;
if (linek < src_bits) linek = src_bits;
if (linek > src_end_row) linek = src_end_row;
k_index = x+j-1;
if (k_index < 0) k_index = 0;
if (k_index > end_col) k_index = end_col;
sum += linek[k_index];
}
p_mean[y*width+x] = (double)sum / area;
}
}
}
////////////////////////////////////////////////////////////////////////////////
void variance_filter(FIBITMAP* src_dib, int region_size, double* p_mean, double* p_var)
{
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 y, x, i, j;
int k_index;
BYTE* src_bits = (BYTE*)FreeImage_GetBits(src_dib); // The image raster
BYTE* src_end_row = src_bits + (height-1) * pitch;
int end_col = width - 1;
BYTE* lines, *linek;
int sum;
unsigned area = region_size * region_size;
for (y = 0; y < height; y++)
{
lines = src_bits + y * pitch;
for (x = 0; x < width; x++)
{
sum = 0;
// kernel processing
for (i = 0; i < region_size; i++)
for (j = 0; j < region_size; j++)
{
linek = lines + (i-1) * pitch;
if (linek < src_bits) linek = src_bits;
if (linek > src_end_row) linek = src_end_row;
k_index = x+j-1;
if (k_index < 0) k_index = 0;
if (k_index > end_col) k_index = end_col;
sum += linek[k_index]*linek[k_index];
}
p_var[y*width+x] = (double)sum / area - p_mean[y*width+x] * p_mean[y*width+x];
}
}
}
////////////////////////////////////////////////////////////////////////////////
FIBITMAP* ProcessFilter(FIBITMAP* src_dib, int region_size,
double sensitivity, int lower_bound, int upper_bound)
{
unsigned width = FreeImage_GetWidth(src_dib);
unsigned height = FreeImage_GetHeight(src_dib);
unsigned src_pitch = FreeImage_GetPitch(src_dib);
unsigned bpp = FreeImage_GetBPP(src_dib);
FIBITMAP* dst_dib = FreeImage_Allocate(width, height, 1);
// Build a monochrome palette
RGBQUAD *pal = FreeImage_GetPalette(dst_dib);
pal[0].rgbRed = pal[0].rgbGreen = pal[0].rgbBlue = 0;
pal[1].rgbRed = pal[1].rgbGreen = pal[1].rgbBlue = 255;
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* lines, *lined;
unsigned x, y;
BYTE mean;
double deviation;
int threshold;
BYTE val;
int int_size = height*width*sizeof(double);
double* p_mean = (double*)malloc(int_size);
double* p_var = (double*)malloc(int_size);
memset(p_mean,0,int_size);
memset(p_var,0,int_size);
mean_filter(src_dib, region_size, p_mean);
variance_filter(src_dib, region_size, p_mean, p_var);
for (y = 0; y < height; y++)
{
lines = src_bits + y * src_pitch;
lined = dst_bits + y * dst_pitch;
for (x = 0; x < width; x++)
{
mean = p_mean[y*width+x];
deviation = sqrt(p_var[y*width+x]);
if (lines[x] < lower_bound) val = 0;
else if (lines[x] > upper_bound) val = 255;
else
{
threshold = (int)mean + sensitivity * deviation;
val = (BYTE) ( ( lines[x] >= threshold ) ? 255 : 0 );
}
SetPixel(lined, x, &val);
}
}
// Copying the DPI...
FreeImage_SetDotsPerMeterX(dst_dib, FreeImage_GetDotsPerMeterX(src_dib));
FreeImage_SetDotsPerMeterY(dst_dib, FreeImage_GetDotsPerMeterY(src_dib));
free(p_mean);
free(p_var);
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 != 6) {
printf("Usage : nib_thres <input_file> <region_size (int)> <sensitivity (double)> <lower_bound (int)> <upper_bound (int)>\n");
return 0;
}
FIBITMAP *dib = GenericLoader(argv[1], 0);
int region_size = atoi(argv[2]);
double sensitivity = atof(argv[3]);
int lower_bound = atoi(argv[4]);
int upper_bound = atoi(argv[5]);
if (dib)
{
// bitmap is successfully loaded!
if (FreeImage_GetImageType(dib) == FIT_BITMAP)
{
if (FreeImage_GetBPP(dib) == 8)
{
FIBITMAP* dst_dib = ProcessFilter(dib, region_size, sensitivity, lower_bound, upper_bound);
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;
}
|
Составляются 2 вспомогательных изображения. Первое заполняется так: квадратная апертура пробегает по всем пикселям, суммирует апертуру, делит на площадь и вставляет в результат. Получается усреднённое изображение.
Второе заполняется так: квадратная апертура пробегает по всем пикселям, суммирует квадраты в апертуре, делит на площадь и вычитает квадрат соответствующего пикселя из первого вспомогательного изображения и вставляет в результат. Получается отклонённое изображение.
Затем в основном цикле проходим по всем пикселям. Если цвет меньше глобального минимума - ставим чёрный, если больше глобального максимума - ставим белый. Если между глобальными порогами - делаем собственно адаптивную бинаризацию: при помощи соответствующих пикселей обоих вспомогательных изображений и чувствительности вычисляем по формуле порог для текущего пикселя. Бинаризуем текущий пиксель по этому порогу и вставляем его в результат.