Вернуться к разделу "Реализация проекта BookScanLib".
Бинаризация Maximum Entropy из проекта ImageJ применяется для преобразования серой (8-битной) растровой картинки в чёрно-белую (1-битная).
Алгоритм Maximum Entropy анализирует обрабатываемую картинку и автоматически вычисляет порог бинаризации - единый для всей картинки (т.е это глобальная бинаризация). Найденный порог подаётся на вход обыкновенной пороговой бинаризации (например, в библиотеке FreeImage для этого есть функция FreeImage_Threshold).
Я написал простейшую консольную программу для демонстрации работы Maximum Entropy Thresholding. На входе она принимает следующие параметры:
ent_thres <input_file>
На выходе программа выдаёт этот же файл, обработанный этим алгоритмом.
Программа работает только с серыми изображениями.
Всё необходимое для тестирования этой программы (компиляционный проект, готовый экзешник, файл-пример и bat-файлы для тестирования программы) я оформил в небольшой пакет:
Скачать пакет ent_thres (40 КБ)
(Для работы программы требуется 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
/*
* DAVID
*
* This file modified by David Foster to improve clarity.
*/
/**
* Automatic thresholding technique based on the entopy of the histogram.
* See: P.K. Sahoo, S. Soltani, K.C. Wong and, Y.C. Chen "A Survey of
* Thresholding Techniques", Computer Vision, Graphics, and Image
* Processing, Vol. 41, pp.233-260, 1988.
*
* @author Jarek Sacha
*/
/*
This plugin does automatic thresholding based on the entopy of the histogram.
The method is very similar to Otsu's method. Rather than maximising
the inter-class variance, it maximises the inter-class entropy.
Entropy is a measure of the uncertainity of an event taking place.
You can calculate it as: S = -(sum)p*log2(p) so it is very
straightforward to do using the histogram data.
(p is the probability of a pixel greyscale value in the image,
and (sum) is the greek capital sigma. It is customary to use log in base 2.
*/
// This algorithm was taken from the ImageJ sourcecodes
// http://rsb.info.nih.gov/ij/plugins/entropy.html
// 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));
}
////////////////////////////////////////////////////////////////////////////////
FIBITMAP* ProcessFilter(FIBITMAP* src_dib)
{
// get source image size
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);
unsigned btpp = bpp/8;
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;
int i, t;
int hist[256] = {0};
// build histogram first
// for each line
for ( unsigned y = 0; y < height; y++ )
{
lines = src_bits + y * src_pitch;
// for each pixel
for ( unsigned x = 0; x < width; x++)
{
hist[lines[x]]++;
}
}
/*
* DAVID
*
* For all i,
* normalizedHist[i] = (double) hist[i]/sum(hist)
*/
// Normalize histogram, that is makes the sum of all bins equal to 1.
double sum = 0;
for (i = 0; i < 256; ++i)
sum += hist[i];
if (sum == 0)
{
// This should not normally happen, but...
//throw new IllegalArgumentException("Empty histogram: sum of all bins is zero.");
printf("Empty histogram: sum of all bins is zero.\n");
return NULL;
}
double normalizedHist[256] = {0};
for (i = 0; i < 256; i++)
normalizedHist[i] = hist[i] / sum;
/*
* DAVID
*
* pT = cumulative_sum(normalizedHist)
*/
double pT[256] = {0};
pT[0] = normalizedHist[0];
for (i = 1; i < 256; i++)
pT[i] = pT[i - 1] + normalizedHist[i];
// Entropy for black and white parts of the histogram
double epsilon = 0;
double hB[256] = {0};
double hW[256] = {0};
for (t = 0; t < 256; t++)
{
// Black entropy
double pTB = pT[t]; // DAVID
if (pTB > epsilon)
{
double hhB = 0;
for (i = 0; i <= t; i++)
{
if (normalizedHist[i] > epsilon)
hhB -= normalizedHist[i] / pTB * log(normalizedHist[i] / pTB);
}
hB[t] = hhB;
}
else
hB[t] = 0;
// White entropy
double pTW = 1 - pT[t];
if (pTW > epsilon)
{
double hhW = 0;
for (i = t + 1; i < 256; ++i)
{
if (normalizedHist[i] > epsilon)
hhW -= normalizedHist[i] / pTW * log(normalizedHist[i] / pTW);
}
hW[t] = hhW;
}
else
hW[t] = 0;
}
// Find histogram index with maximum entropy
// DAVID: ...where entropy[i] is defined to be (black_entropy[i] + white_entropy[i])
double jMax = hB[0] + hW[0];
int threshold = 0;
for (t = 1; t < 256; ++t)
{
double j = hB[t] + hW[t];
if (j > jMax)
{
jMax = j;
threshold = t;
}
}
printf("threshold=%d\n", threshold);
BYTE val;
// for each line
for ( y = 0; y < height; y++ )
{
lined = dst_bits + y * dst_pitch;
lines = src_bits + y * src_pitch;
// for all pixels
for ( unsigned x = 0; x < width; x++)
{
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));
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 != 2) {
printf("Usage : ent_thres <input_file>\n");
return 0;
}
FIBITMAP *dib = GenericLoader(argv[1], 0);
if (dib)
{
// bitmap is successfully loaded!
if (FreeImage_GetImageType(dib) == FIT_BITMAP)
{
if (FreeImage_GetBPP(dib) == 8)
{
FIBITMAP* dst_dib = ProcessFilter(dib);
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 гистрограммы: по энтропии чёрного цвета и по энтропии белого цвета.
Проходим в цикле по обоим энтропийным гистограммам, находим максимальную сумму - это и есть глобальный порог бинаризации.
Делаем обычную пороговую бинаризацию по найденному порогу.