kopia lustrzana https://gitlab.com/sane-project/backends
1207 wiersze
30 KiB
C
1207 wiersze
30 KiB
C
/** @file sanei_ir.c
|
|
*
|
|
* sanei_ir - functions for utilizing the infrared plane
|
|
*
|
|
* Copyright (C) 2012 Michael Rickmann <mrickma@gwdg.de>
|
|
*
|
|
* This file is part of the SANE package.
|
|
*
|
|
* 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.
|
|
*
|
|
* The threshold yen, otsu and max_entropy routines have been
|
|
* adapted from the FOURIER 0.8 library by M. Emre Celebi,
|
|
* http://sourceforge.net/projects/fourier-ipal/ which is
|
|
* licensed under the GNU General Public License version 2 or later.
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <float.h>
|
|
#include <limits.h>
|
|
#include <math.h>
|
|
|
|
#define BACKEND_NAME sanei_ir /* name of this module for debugging */
|
|
|
|
#include "../include/sane/sane.h"
|
|
#include "../include/sane/sanei_debug.h"
|
|
#include "../include/sane/sanei_ir.h"
|
|
#include "../include/sane/sanei_magic.h"
|
|
|
|
|
|
double *
|
|
sanei_ir_create_norm_histo (const SANE_Parameters * params, const SANE_Uint *img_data);
|
|
double * sanei_ir_accumulate_norm_histo (double * histo_data);
|
|
|
|
|
|
/* Initialize sanei_ir
|
|
*/
|
|
void
|
|
sanei_ir_init (void)
|
|
{
|
|
DBG_INIT ();
|
|
}
|
|
|
|
|
|
/* Create a normalized histogram of a grayscale image, internal
|
|
*/
|
|
double *
|
|
sanei_ir_create_norm_histo (const SANE_Parameters * params,
|
|
const SANE_Uint *img_data)
|
|
{
|
|
int is, i;
|
|
int num_pixels;
|
|
int *histo_data;
|
|
double *histo;
|
|
double term;
|
|
|
|
DBG (10, "sanei_ir_create_norm_histo\n");
|
|
|
|
if ((params->format != SANE_FRAME_GRAY)
|
|
&& (params->format != SANE_FRAME_RED)
|
|
&& (params->format != SANE_FRAME_GREEN)
|
|
&& (params->format != SANE_FRAME_BLUE))
|
|
{
|
|
DBG (5, "sanei_ir_create_norm_histo: invalid format\n");
|
|
return NULL;
|
|
}
|
|
|
|
/* Allocate storage for the histogram */
|
|
histo_data = calloc (HISTOGRAM_SIZE, sizeof (int));
|
|
histo = malloc (HISTOGRAM_SIZE * sizeof (double));
|
|
if ((histo == NULL) || (histo_data == NULL))
|
|
{
|
|
DBG (5, "sanei_ir_create_norm_histo: no buffers\n");
|
|
if (histo) free (histo);
|
|
if (histo_data) free (histo_data);
|
|
return NULL;
|
|
}
|
|
|
|
num_pixels = params->pixels_per_line * params->lines;
|
|
|
|
DBG (1, "sanei_ir_create_norm_histo: %d pixels_per_line, %d lines => %d num_pixels\n", params->pixels_per_line, params->lines, num_pixels);
|
|
DBG (1, "sanei_ir_create_norm_histo: histo_data[] with %d x %ld bytes\n", HISTOGRAM_SIZE, sizeof(int));
|
|
/* Populate the histogram */
|
|
is = 16 - HISTOGRAM_SHIFT; /* Number of data bits to ignore */
|
|
DBG (1, "sanei_ir_create_norm_histo: depth %d, HISTOGRAM_SHIFT %d => ignore %d bits\n", params->depth, HISTOGRAM_SHIFT, is);
|
|
for (i = num_pixels; i > 0; i--) {
|
|
histo_data[*img_data++ >> is]++;
|
|
}
|
|
|
|
/* Calculate the normalized histogram */
|
|
term = 1.0 / (double) num_pixels;
|
|
for (i = 0; i < HISTOGRAM_SIZE; i++)
|
|
histo[i] = term * (double) histo_data[i];
|
|
|
|
free (histo_data);
|
|
return histo;
|
|
}
|
|
|
|
|
|
/* Create the normalized histogram of a grayscale image
|
|
*/
|
|
SANE_Status
|
|
sanei_ir_create_norm_histogram (const SANE_Parameters * params,
|
|
const SANE_Uint *img_data,
|
|
double ** histogram)
|
|
{
|
|
double *histo;
|
|
|
|
DBG (10, "sanei_ir_create_norm_histogram\n");
|
|
|
|
histo = sanei_ir_create_norm_histo (params, img_data);
|
|
if (!histo)
|
|
return SANE_STATUS_NO_MEM;
|
|
|
|
*histogram = histo;
|
|
return SANE_STATUS_GOOD;
|
|
}
|
|
|
|
/* Accumulate a normalized histogram, internal
|
|
*/
|
|
double *
|
|
sanei_ir_accumulate_norm_histo (double * histo_data)
|
|
{
|
|
int i;
|
|
double *accum_data;
|
|
|
|
accum_data = malloc (HISTOGRAM_SIZE * sizeof (double));
|
|
if (accum_data == NULL)
|
|
{
|
|
DBG (5, "sanei_ir_accumulate_norm_histo: Insufficient memory !\n");
|
|
return NULL;
|
|
}
|
|
|
|
accum_data[0] = histo_data[0];
|
|
for (i = 1; i < HISTOGRAM_SIZE; i++)
|
|
accum_data[i] = accum_data[i - 1] + histo_data[i];
|
|
|
|
return accum_data;
|
|
}
|
|
|
|
/* Implements Yen's thresholding method
|
|
*/
|
|
SANE_Status
|
|
sanei_ir_threshold_yen (const SANE_Parameters * params,
|
|
double * norm_histo, int *thresh)
|
|
{
|
|
double *P1 = NULL; /* cumulative normalized histogram */
|
|
double *P1_sq = NULL; /* cumulative normalized histogram */
|
|
double *P2_sq = NULL;
|
|
double crit, max_crit;
|
|
int threshold, i;
|
|
SANE_Status ret = SANE_STATUS_NO_MEM;
|
|
|
|
DBG (10, "sanei_ir_threshold_yen\n");
|
|
|
|
P1 = sanei_ir_accumulate_norm_histo (norm_histo);
|
|
P1_sq = malloc (HISTOGRAM_SIZE * sizeof (double));
|
|
P2_sq = malloc (HISTOGRAM_SIZE * sizeof (double));
|
|
if (!P1 || !P1_sq || !P2_sq)
|
|
{
|
|
DBG (5, "sanei_ir_threshold_yen: no buffers\n");
|
|
goto cleanup;
|
|
}
|
|
|
|
/* calculate cumulative squares */
|
|
P1_sq[0] = norm_histo[0] * norm_histo[0];
|
|
for (i = 1; i < HISTOGRAM_SIZE; i++)
|
|
P1_sq[i] = P1_sq[i - 1] + norm_histo[i] * norm_histo[i];
|
|
P2_sq[HISTOGRAM_SIZE - 1] = 0.0;
|
|
for (i = HISTOGRAM_SIZE - 2; i >= 0; i--)
|
|
P2_sq[i] = P2_sq[i + 1] + norm_histo[i + 1] * norm_histo[i + 1];
|
|
|
|
/* Find the threshold that maximizes the criterion */
|
|
threshold = INT_MIN;
|
|
max_crit = DBL_MIN;
|
|
for (i = 0; i < HISTOGRAM_SIZE; i++)
|
|
{
|
|
crit =
|
|
-1.0 * SAFE_LOG (P1_sq[i] * P2_sq[i]) +
|
|
2 * SAFE_LOG (P1[i] * (1.0 - P1[i]));
|
|
if (crit > max_crit)
|
|
{
|
|
max_crit = crit;
|
|
threshold = i;
|
|
}
|
|
}
|
|
|
|
if (threshold == INT_MIN)
|
|
{
|
|
DBG (5, "sanei_ir_threshold_yen: no threshold found\n");
|
|
ret = SANE_STATUS_INVAL;
|
|
}
|
|
else
|
|
{
|
|
ret = SANE_STATUS_GOOD;
|
|
if (params->depth > 8)
|
|
{
|
|
i = 1 << (params->depth - HISTOGRAM_SHIFT);
|
|
*thresh = threshold * i + i / 2;
|
|
}
|
|
else
|
|
*thresh = threshold;
|
|
DBG (10, "sanei_ir_threshold_yen: threshold %d\n", *thresh);
|
|
}
|
|
|
|
cleanup:
|
|
if (P1)
|
|
free (P1);
|
|
if (P1_sq)
|
|
free (P1_sq);
|
|
if (P2_sq)
|
|
free (P2_sq);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* Implements Otsu's thresholding method
|
|
*/
|
|
SANE_Status
|
|
sanei_ir_threshold_otsu (const SANE_Parameters * params,
|
|
double * norm_histo, int *thresh)
|
|
{
|
|
double *cnh = NULL;
|
|
double *mean = NULL;
|
|
double total_mean;
|
|
double bcv, max_bcv;
|
|
int first_bin, last_bin;
|
|
int threshold, i;
|
|
SANE_Status ret = SANE_STATUS_NO_MEM;
|
|
|
|
DBG (10, "sanei_ir_threshold_otsu\n");
|
|
|
|
cnh = sanei_ir_accumulate_norm_histo (norm_histo);
|
|
mean = malloc (HISTOGRAM_SIZE * sizeof (double));
|
|
if (!cnh || !mean)
|
|
{
|
|
DBG (5, "sanei_ir_threshold_otsu: no buffers\n");
|
|
goto cleanup;
|
|
}
|
|
|
|
mean[0] = 0.0;
|
|
for (i = 1; i < HISTOGRAM_SIZE; i++)
|
|
mean[i] = mean[i - 1] + i * norm_histo[i];
|
|
total_mean = mean[HISTOGRAM_SIZE - 1];
|
|
|
|
first_bin = 0;
|
|
for (i = 0; i < HISTOGRAM_SIZE; i++)
|
|
if (cnh[i] != 0)
|
|
{
|
|
first_bin = i;
|
|
break;
|
|
}
|
|
last_bin = HISTOGRAM_SIZE - 1;
|
|
for (i = HISTOGRAM_SIZE - 1; i >= first_bin; i--)
|
|
if (1.0 - cnh[i] != 0)
|
|
{
|
|
last_bin = i;
|
|
break;
|
|
}
|
|
|
|
threshold = INT_MIN;
|
|
max_bcv = 0.0;
|
|
for (i = first_bin; i <= last_bin; i++)
|
|
{
|
|
bcv = total_mean * cnh[i] - mean[i];
|
|
bcv *= bcv / (cnh[i] * (1.0 - cnh[i]));
|
|
if (max_bcv < bcv)
|
|
{
|
|
max_bcv = bcv;
|
|
threshold = i;
|
|
}
|
|
}
|
|
|
|
if (threshold == INT_MIN)
|
|
{
|
|
DBG (5, "sanei_ir_threshold_otsu: no threshold found\n");
|
|
ret = SANE_STATUS_INVAL;
|
|
}
|
|
else
|
|
{
|
|
ret = SANE_STATUS_GOOD;
|
|
if (params->depth > 8)
|
|
{
|
|
i = 1 << (params->depth - HISTOGRAM_SHIFT);
|
|
*thresh = threshold * i + i / 2;
|
|
}
|
|
else
|
|
*thresh = threshold;
|
|
DBG (10, "sanei_ir_threshold_otsu: threshold %d\n", *thresh);
|
|
}
|
|
cleanup:
|
|
if (cnh)
|
|
free (cnh);
|
|
if (mean)
|
|
free (mean);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* Implements a Maximum Entropy thresholding method
|
|
*/
|
|
SANE_Status
|
|
sanei_ir_threshold_maxentropy (const SANE_Parameters * params,
|
|
double * norm_histo, int *thresh)
|
|
{
|
|
int ih, it;
|
|
int threshold;
|
|
int first_bin;
|
|
int last_bin;
|
|
double tot_ent, max_ent; /* entropies */
|
|
double ent_back, ent_obj;
|
|
double *P1; /* cumulative normalized histogram */
|
|
double *P2;
|
|
SANE_Status ret = SANE_STATUS_NO_MEM;
|
|
|
|
DBG (10, "sanei_ir_threshold_maxentropy\n");
|
|
|
|
/* Calculate the cumulative normalized histogram */
|
|
P1 = sanei_ir_accumulate_norm_histo (norm_histo);
|
|
P2 = malloc (HISTOGRAM_SIZE * sizeof (double));
|
|
if (!P1 || !P2)
|
|
{
|
|
DBG (5, "sanei_ir_threshold_maxentropy: no buffers\n");
|
|
goto cleanup;
|
|
}
|
|
|
|
for ( ih = 0; ih < HISTOGRAM_SIZE; ih++ )
|
|
P2[ih] = 1.0 - P1[ih];
|
|
|
|
first_bin = 0;
|
|
for ( ih = 0; ih < HISTOGRAM_SIZE; ih++ )
|
|
if (P1[ih] != 0)
|
|
{
|
|
first_bin = ih;
|
|
break;
|
|
}
|
|
last_bin = HISTOGRAM_SIZE - 1;
|
|
for ( ih = HISTOGRAM_SIZE - 1; ih >= first_bin; ih-- )
|
|
if (P2[ih] != 0)
|
|
{
|
|
last_bin = ih;
|
|
break;
|
|
}
|
|
|
|
/* Calculate the total entropy each gray-level
|
|
* and find the threshold that maximizes it
|
|
*/
|
|
threshold = INT_MIN;
|
|
max_ent = DBL_MIN;
|
|
for ( it = first_bin; it <= last_bin; it++ )
|
|
{
|
|
/* Entropy of the background pixels */
|
|
ent_back = 0.0;
|
|
for ( ih = 0; ih <= it; ih++ )
|
|
if (norm_histo[ih] != 0)
|
|
ent_back -= ( norm_histo[ih] / P1[it] ) * log ( norm_histo[ih] / P1[it] );
|
|
|
|
/* Entropy of the object pixels */
|
|
ent_obj = 0.0;
|
|
for ( ih = it + 1; ih < HISTOGRAM_SIZE; ih++ )
|
|
if (norm_histo[ih] != 0)
|
|
ent_obj -= ( norm_histo[ih] / P2[it] ) * log ( norm_histo[ih] / P2[it] );
|
|
|
|
/* Total entropy */
|
|
tot_ent = ent_back + ent_obj;
|
|
|
|
if ( max_ent < tot_ent )
|
|
{
|
|
max_ent = tot_ent;
|
|
threshold = it;
|
|
}
|
|
}
|
|
|
|
if (threshold == INT_MIN)
|
|
{
|
|
DBG (5, "sanei_ir_threshold_maxentropy: no threshold found\n");
|
|
ret = SANE_STATUS_INVAL;
|
|
}
|
|
else
|
|
{
|
|
ret = SANE_STATUS_GOOD;
|
|
if (params->depth > 8)
|
|
{
|
|
it = 1 << (params->depth - HISTOGRAM_SHIFT);
|
|
*thresh = threshold * it + it / 2;
|
|
}
|
|
else
|
|
*thresh = threshold;
|
|
DBG (10, "sanei_ir_threshold_maxentropy: threshold %d\n", *thresh);
|
|
}
|
|
|
|
cleanup:
|
|
if (P1)
|
|
free (P1);
|
|
if (P2)
|
|
free (P2);
|
|
return ret;
|
|
}
|
|
|
|
/* Generate gray scale luminance image from separate R, G, B images
|
|
*/
|
|
SANE_Status
|
|
sanei_ir_RGB_luminance (SANE_Parameters * params, const SANE_Uint **in_img,
|
|
SANE_Uint **out_img)
|
|
{
|
|
SANE_Uint *outi;
|
|
int itop, i;
|
|
|
|
if ((params->depth < 8) || (params->depth > 16) ||
|
|
(params->format != SANE_FRAME_GRAY))
|
|
{
|
|
DBG (5, "sanei_ir_RGB_luminance: invalid format\n");
|
|
return SANE_STATUS_UNSUPPORTED;
|
|
}
|
|
|
|
itop = params->pixels_per_line * params->lines;
|
|
outi = malloc (itop * sizeof(SANE_Uint));
|
|
if (!outi)
|
|
{
|
|
DBG (5, "sanei_ir_RGB_luminance: can not allocate out_img\n");
|
|
return SANE_STATUS_NO_MEM;
|
|
}
|
|
|
|
for (i = itop; i > 0; i--)
|
|
*(outi++) = (218 * (int) *(in_img[0]++) +
|
|
732 * (int) *(in_img[1]++) +
|
|
74 * (int) *(in_img[2]++)) >> 10;
|
|
*out_img = outi;
|
|
return SANE_STATUS_GOOD;
|
|
}
|
|
|
|
/* Convert image from >8 bit depth to an 8 bit image
|
|
*/
|
|
SANE_Status
|
|
sanei_ir_to_8bit (SANE_Parameters * params, const SANE_Uint *in_img,
|
|
SANE_Parameters * out_params, SANE_Uint **out_img)
|
|
{
|
|
SANE_Uint *outi;
|
|
size_t ssize;
|
|
int i, is;
|
|
|
|
if ((params->depth < 8) || (params->depth > 16))
|
|
{
|
|
DBG (5, "sanei_ir_to_8bit: invalid format\n");
|
|
return SANE_STATUS_UNSUPPORTED;
|
|
}
|
|
ssize = params->pixels_per_line * params->lines;
|
|
if (params->format == SANE_FRAME_RGB)
|
|
ssize *= 3;
|
|
outi = malloc (ssize * sizeof(SANE_Uint));
|
|
if (!outi)
|
|
{
|
|
DBG (5, "sanei_ir_to_8bit: can not allocate out_img\n");
|
|
return SANE_STATUS_NO_MEM;
|
|
}
|
|
|
|
if (out_params)
|
|
{
|
|
memmove (out_params, params, sizeof(SANE_Parameters));
|
|
out_params->bytes_per_line = out_params->pixels_per_line;
|
|
if (params->format == SANE_FRAME_RGB)
|
|
out_params->bytes_per_line *= 3;
|
|
out_params->depth = 8;
|
|
}
|
|
|
|
memmove (outi, in_img, ssize * sizeof(SANE_Uint));
|
|
is = params->depth - 8;
|
|
for (i = ssize; i > 0; i--) {
|
|
*outi = *outi >> is, outi += 2;
|
|
}
|
|
|
|
*out_img = outi;
|
|
return SANE_STATUS_GOOD;
|
|
}
|
|
|
|
/* allocate and initialize logarithmic lookup table
|
|
*/
|
|
SANE_Status
|
|
sanei_ir_ln_table (int len, double **lut_ln)
|
|
{
|
|
double *llut;
|
|
int i;
|
|
|
|
DBG (10, "sanei_ir_ln_table\n");
|
|
|
|
llut = malloc (len * sizeof (double));
|
|
if (!llut)
|
|
{
|
|
DBG (5, "sanei_ir_ln_table: no table\n");
|
|
return SANE_STATUS_NO_MEM;
|
|
}
|
|
llut[0] = 0;
|
|
llut[1] = 0;
|
|
for (i = 2; i < len; i++)
|
|
llut[i] = log ((double) i);
|
|
|
|
*lut_ln = llut;
|
|
return SANE_STATUS_GOOD;
|
|
}
|
|
|
|
|
|
/* Reduce red spectral overlap from an infrared image plane
|
|
*/
|
|
SANE_Status
|
|
sanei_ir_spectral_clean (const SANE_Parameters * params, double *lut_ln,
|
|
const SANE_Uint *red_data,
|
|
SANE_Uint *ir_data)
|
|
{
|
|
const SANE_Uint *rptr;
|
|
SANE_Uint *iptr;
|
|
SANE_Int depth;
|
|
double *llut;
|
|
double rval, rsum, rrsum;
|
|
double risum, rfac, radd;
|
|
double *norm_histo;
|
|
int64_t isum;
|
|
int *calc_buf, *calc_ptr;
|
|
int ival, imin, imax;
|
|
int itop, len, ssize;
|
|
int thresh_low, thresh;
|
|
int irand, i;
|
|
SANE_Status status;
|
|
|
|
DBG (10, "sanei_ir_spectral_clean\n");
|
|
|
|
itop = params->pixels_per_line * params->lines;
|
|
calc_buf = malloc (itop * sizeof (int)); /* could save this */
|
|
if (!calc_buf)
|
|
{
|
|
DBG (5, "sanei_ir_spectral_clean: no buffer\n");
|
|
return SANE_STATUS_NO_MEM;
|
|
}
|
|
|
|
depth = params->depth;
|
|
len = 1 << depth;
|
|
if (lut_ln)
|
|
llut = lut_ln;
|
|
else
|
|
{
|
|
status = sanei_ir_ln_table (len, &llut);
|
|
if (status != SANE_STATUS_GOOD) {
|
|
free (calc_buf);
|
|
return status;
|
|
}
|
|
}
|
|
|
|
/* determine not transparent areas to exclude them later
|
|
* TODO: this has not been tested for negatives
|
|
*/
|
|
thresh_low = INT_MAX;
|
|
status =
|
|
sanei_ir_create_norm_histogram (params, ir_data, &norm_histo);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (5, "sanei_ir_spectral_clean: no buffer\n");
|
|
free (calc_buf);
|
|
return SANE_STATUS_NO_MEM;
|
|
}
|
|
|
|
/* TODO: remember only needed if cropping is not ok */
|
|
status = sanei_ir_threshold_maxentropy (params, norm_histo, &thresh);
|
|
if (status == SANE_STATUS_GOOD)
|
|
thresh_low = thresh;
|
|
status = sanei_ir_threshold_otsu (params, norm_histo, &thresh);
|
|
if ((status == SANE_STATUS_GOOD) && (thresh < thresh_low))
|
|
thresh_low = thresh;
|
|
status = sanei_ir_threshold_yen (params, norm_histo, &thresh);
|
|
if ((status == SANE_STATUS_GOOD) && (thresh < thresh_low))
|
|
thresh_low = thresh;
|
|
if (thresh_low == INT_MAX)
|
|
thresh_low = 0;
|
|
else
|
|
thresh_low /= 2;
|
|
DBG (10, "sanei_ir_spectral_clean: low threshold %d\n", thresh_low);
|
|
|
|
/* calculate linear regression ired (red) from randomly chosen points */
|
|
ssize = itop / 2;
|
|
if (SAMPLE_SIZE < ssize)
|
|
ssize = SAMPLE_SIZE;
|
|
isum = 0;
|
|
rsum = rrsum = risum = 0.0;
|
|
i = ssize;
|
|
while (i > 0)
|
|
{
|
|
irand = rand () % itop;
|
|
rval = llut[red_data[irand]];
|
|
ival = ir_data[irand];
|
|
if (ival > thresh_low)
|
|
{
|
|
isum += ival;
|
|
rsum += rval;
|
|
rrsum += rval * rval;
|
|
risum += rval * (double) ival;
|
|
i--;
|
|
}
|
|
}
|
|
|
|
/* "a" in ired = b + a * ln (red) */
|
|
rfac =
|
|
((double) ssize * risum -
|
|
rsum * (double) isum) / ((double) ssize * rrsum - rsum * rsum);
|
|
radd = ((double) isum - rfac * rsum) / (double) ssize; /* "b" unused */
|
|
|
|
DBG (10, "sanei_ir_spectral_clean: n = %d, ired(red) = %f * ln(red) + %f\n",
|
|
ssize, rfac, radd);
|
|
|
|
/* now calculate ired' = ired - a * ln (red) */
|
|
imin = INT_MAX;
|
|
imax = INT_MIN;
|
|
rptr = red_data;
|
|
iptr = ir_data;
|
|
calc_ptr = calc_buf;
|
|
for (i = itop; i > 0; i--)
|
|
{
|
|
ival = *iptr++ - (int) (rfac * llut[*rptr++] + 0.5);
|
|
if (ival > imax)
|
|
imax = ival;
|
|
if (ival < imin)
|
|
imin = ival;
|
|
*calc_ptr++ = ival;
|
|
}
|
|
|
|
/* scale the result back into the ired image */
|
|
calc_ptr = calc_buf;
|
|
iptr = ir_data;
|
|
rfac = (double) (len - 1) / (double) (imax - imin);
|
|
for (i = itop; i > 0; i--)
|
|
*iptr++ = (double) (*calc_ptr++ - imin) * rfac;
|
|
|
|
if (!lut_ln)
|
|
free (llut);
|
|
free (calc_buf);
|
|
free (norm_histo);
|
|
return SANE_STATUS_GOOD;
|
|
}
|
|
|
|
|
|
/* Hopefully fast mean filter
|
|
* JV: what does this do? Remove local mean?
|
|
*/
|
|
SANE_Status
|
|
sanei_ir_filter_mean (const SANE_Parameters * params,
|
|
const SANE_Uint *in_img, SANE_Uint *out_img,
|
|
int win_rows, int win_cols)
|
|
{
|
|
const SANE_Uint *src;
|
|
SANE_Uint *dest;
|
|
int num_cols, num_rows;
|
|
int itop, iadd, isub;
|
|
int ndiv, the_sum;
|
|
int nrow, ncol;
|
|
int hwr, hwc;
|
|
int *sum;
|
|
int i, j;
|
|
|
|
DBG (10, "sanei_ir_filter_mean, window: %d x%d\n", win_rows, win_cols);
|
|
|
|
if (((win_rows & 1) == 0) || ((win_cols & 1) == 0))
|
|
{
|
|
DBG (5, "sanei_ir_filter_mean: window even sized\n");
|
|
return SANE_STATUS_INVAL;
|
|
}
|
|
|
|
num_cols = params->pixels_per_line;
|
|
num_rows = params->lines;
|
|
|
|
sum = malloc (num_cols * sizeof (int));
|
|
if (!sum)
|
|
{
|
|
DBG (5, "sanei_ir_filter_mean: no buffer for sums\n");
|
|
return SANE_STATUS_NO_MEM;
|
|
}
|
|
dest = out_img;
|
|
|
|
hwr = win_rows / 2; /* half window sizes */
|
|
hwc = win_cols / 2;
|
|
|
|
/* pre-pre calculation */
|
|
for (j = 0; j < num_cols; j++)
|
|
{
|
|
sum[j] = 0;
|
|
src = in_img + j;
|
|
for (i = 0; i < hwr; i++)
|
|
{
|
|
sum[j] += *src;
|
|
src += num_cols;
|
|
}
|
|
}
|
|
|
|
itop = num_rows * num_cols;
|
|
iadd = hwr * num_cols;
|
|
isub = (hwr - win_rows) * num_cols;
|
|
nrow = hwr;
|
|
|
|
for (i = 0; i < num_rows; i++)
|
|
{
|
|
/* update row sums if possible */
|
|
if (isub >= 0) /* subtract old row */
|
|
{
|
|
nrow--;
|
|
src = in_img + isub;
|
|
for (j = 0; j < num_cols; j++)
|
|
sum[j] -= *src++;
|
|
}
|
|
isub += num_cols;
|
|
|
|
if (iadd < itop) /* add new row */
|
|
{
|
|
nrow++;
|
|
src = in_img + iadd;
|
|
for (j = 0; j < num_cols; j++)
|
|
sum[j] += *src++;
|
|
}
|
|
iadd += num_cols;
|
|
|
|
/* now we do the image columns using only the precalculated sums */
|
|
|
|
the_sum = 0; /* precalculation */
|
|
for (j = 0; j < hwc; j++)
|
|
the_sum += sum[j];
|
|
ncol = hwc;
|
|
|
|
/* at the left margin, real index hwc lower */
|
|
for (j = hwc; j < win_cols; j++)
|
|
{
|
|
ncol++;
|
|
the_sum += sum[j];
|
|
*dest++ = the_sum / (ncol * nrow);
|
|
}
|
|
|
|
ndiv = ncol * nrow;
|
|
/* in the middle, real index hwc + 1 higher */
|
|
for (j = 0; j < num_cols - win_cols; j++)
|
|
{
|
|
the_sum -= sum[j];
|
|
the_sum += sum[j + win_cols];
|
|
*dest++ = the_sum / ndiv;
|
|
}
|
|
|
|
/* at the right margin, real index hwc + 1 higher */
|
|
for (j = num_cols - win_cols; j < num_cols - hwc - 1; j++)
|
|
{
|
|
ncol--;
|
|
the_sum -= sum[j]; /* j - hwc - 1 */
|
|
*dest++ = the_sum / (ncol * nrow);
|
|
}
|
|
}
|
|
free (sum);
|
|
return SANE_STATUS_GOOD;
|
|
}
|
|
|
|
|
|
/* Find noise by adaptive thresholding
|
|
*/
|
|
SANE_Status
|
|
sanei_ir_filter_madmean (const SANE_Parameters * params,
|
|
const SANE_Uint *in_img,
|
|
SANE_Uint ** out_img, int win_size,
|
|
int a_val, int b_val)
|
|
{
|
|
SANE_Uint *delta_ij, *delta_ptr;
|
|
SANE_Uint *mad_ij;
|
|
const SANE_Uint *mad_ptr;
|
|
SANE_Uint *out_ij, *dest8;
|
|
double ab_term;
|
|
int num_rows, num_cols;
|
|
int threshold, itop;
|
|
size_t size;
|
|
int ival, i;
|
|
int depth;
|
|
SANE_Status ret = SANE_STATUS_NO_MEM;
|
|
|
|
DBG (10, "sanei_ir_filter_madmean\n");
|
|
|
|
depth = params->depth;
|
|
if (depth != 8)
|
|
{
|
|
a_val = a_val << (depth - 8);
|
|
b_val = b_val << (depth - 8);
|
|
}
|
|
num_cols = params->pixels_per_line;
|
|
num_rows = params->lines;
|
|
itop = num_rows * num_cols;
|
|
size = itop * sizeof (SANE_Uint);
|
|
out_ij = malloc (size);
|
|
delta_ij = malloc (size);
|
|
mad_ij = malloc (size);
|
|
|
|
if (out_ij && delta_ij && mad_ij)
|
|
{
|
|
/* get the differences to the local mean */
|
|
mad_ptr = in_img;
|
|
if (sanei_ir_filter_mean (params, mad_ptr, delta_ij, win_size, win_size)
|
|
== SANE_STATUS_GOOD)
|
|
{
|
|
delta_ptr = delta_ij;
|
|
for (i = 0; i < itop; i++)
|
|
{
|
|
ival = *mad_ptr++ - *delta_ptr;
|
|
*delta_ptr++ = abs (ival);
|
|
}
|
|
/* make the second filtering window a bit larger */
|
|
win_size = MAD_WIN2_SIZE(win_size);
|
|
/* and get the local mean differences */
|
|
if (sanei_ir_filter_mean
|
|
(params, delta_ij, mad_ij, win_size,
|
|
win_size) == SANE_STATUS_GOOD)
|
|
{
|
|
mad_ptr = mad_ij;
|
|
delta_ptr = delta_ij;
|
|
dest8 = out_ij;
|
|
/* construct the noise map */
|
|
ab_term = (b_val - a_val) / (double) b_val;
|
|
for (i = 0; i < itop; i++)
|
|
{
|
|
/* by calculating the threshold */
|
|
ival = *mad_ptr++;
|
|
if (ival >= b_val) /* outlier */
|
|
threshold = a_val;
|
|
else
|
|
threshold = a_val + (double) ival *ab_term;
|
|
/* above threshold is noise, indicated by 0 */
|
|
if (*delta_ptr++ >= threshold)
|
|
*dest8++ = 0;
|
|
else
|
|
*dest8++ = 255;
|
|
}
|
|
*out_img = out_ij;
|
|
ret = SANE_STATUS_GOOD;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
DBG (5, "sanei_ir_filter_madmean: Cannot allocate buffers\n");
|
|
|
|
free (mad_ij);
|
|
free (delta_ij);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* Add dark pixels to mask from static threshold
|
|
*/
|
|
void
|
|
sanei_ir_add_threshold (const SANE_Parameters * params,
|
|
const SANE_Uint *in_img,
|
|
SANE_Uint * mask_img, int threshold)
|
|
{
|
|
const SANE_Uint *in_ptr;
|
|
SANE_Uint *mask_ptr;
|
|
int itop, i;
|
|
|
|
DBG (10, "sanei_ir_add_threshold\n");
|
|
|
|
itop = params->pixels_per_line * params->lines;
|
|
in_ptr = in_img;
|
|
mask_ptr = mask_img;
|
|
|
|
for (i = itop; i > 0; i--)
|
|
{
|
|
if (*in_ptr++ <= threshold)
|
|
*mask_ptr = 0;
|
|
mask_ptr++;
|
|
}
|
|
}
|
|
|
|
|
|
/* Calculate minimal Manhattan distances for an image mask
|
|
*/
|
|
void
|
|
sanei_ir_manhattan_dist (const SANE_Parameters * params,
|
|
const SANE_Uint * mask_img, unsigned int *dist_map,
|
|
unsigned int *idx_map, unsigned int erode)
|
|
{
|
|
const SANE_Uint *mask;
|
|
unsigned int *index, *manhattan;
|
|
int rows, cols, itop;
|
|
int i, j;
|
|
|
|
DBG (10, "sanei_ir_manhattan_dist\n");
|
|
|
|
if (erode != 0)
|
|
erode = 255;
|
|
|
|
/* initialize maps */
|
|
cols = params->pixels_per_line;
|
|
rows = params->lines;
|
|
itop = rows * cols;
|
|
mask = mask_img;
|
|
manhattan = dist_map;
|
|
index = idx_map;
|
|
for (i = 0; i < itop; i++)
|
|
{
|
|
*manhattan++ = *mask++;
|
|
*index++ = i;
|
|
}
|
|
|
|
/* traverse from top left to bottom right */
|
|
manhattan = dist_map;
|
|
index = idx_map;
|
|
for (i = 0; i < rows; i++)
|
|
for (j = 0; j < cols; j++)
|
|
{
|
|
if (*manhattan == erode)
|
|
{
|
|
/* take original, distance = 0, index stays the same */
|
|
*manhattan = 0;
|
|
}
|
|
else
|
|
{
|
|
/* assume maximal distance to clean pixel */
|
|
*manhattan = cols + rows;
|
|
/* or one further away than pixel to the top */
|
|
if (i > 0)
|
|
if (manhattan[-cols] + 1 < *manhattan)
|
|
{
|
|
*manhattan = manhattan[-cols] + 1;
|
|
*index = index[-cols]; /* index follows */
|
|
}
|
|
/* or one further away than pixel to the left */
|
|
if (j > 0)
|
|
{
|
|
if (manhattan[-1] + 1 < *manhattan)
|
|
{
|
|
*manhattan = manhattan[-1] + 1;
|
|
*index = index[-1]; /* index follows */
|
|
}
|
|
if (manhattan[-1] + 1 == *manhattan)
|
|
if (rand () % 2 == 0) /* chose index */
|
|
*index = index[-1];
|
|
}
|
|
}
|
|
manhattan++;
|
|
index++;
|
|
}
|
|
|
|
/* traverse from bottom right to top left */
|
|
manhattan = dist_map + itop - 1;
|
|
index = idx_map + itop - 1;
|
|
for (i = rows - 1; i >= 0; i--)
|
|
for (j = cols - 1; j >= 0; j--)
|
|
{
|
|
if (i < rows - 1)
|
|
{
|
|
/* either what we had on the first pass
|
|
or one more than the pixel to the bottm */
|
|
if (manhattan[+cols] + 1 < *manhattan)
|
|
{
|
|
*manhattan = manhattan[+cols] + 1;
|
|
*index = index[+cols]; /* index follows */
|
|
}
|
|
if (manhattan[+cols] + 1 == *manhattan)
|
|
if (rand () % 2 == 0) /* chose index */
|
|
*index = index[+cols];
|
|
}
|
|
if (j < cols - 1)
|
|
{
|
|
/* or one more than pixel to the right */
|
|
if (manhattan[1] + 1 < *manhattan)
|
|
{
|
|
*manhattan = manhattan[1] + 1;
|
|
*index = index[1]; /* index follows */
|
|
}
|
|
if (manhattan[1] + 1 == *manhattan)
|
|
if (rand () % 2 == 0) /* chose index */
|
|
*index = index[1];
|
|
}
|
|
manhattan--;
|
|
index--;
|
|
}
|
|
}
|
|
|
|
|
|
/* dilate or erode a mask image */
|
|
|
|
void
|
|
sanei_ir_dilate (const SANE_Parameters *params, SANE_Uint *mask_img,
|
|
unsigned int *dist_map, unsigned int *idx_map, int by)
|
|
{
|
|
SANE_Uint *mask;
|
|
unsigned int *manhattan;
|
|
unsigned int erode;
|
|
unsigned int thresh;
|
|
int i, itop;
|
|
|
|
DBG (10, "sanei_ir_dilate\n");
|
|
|
|
if (by == 0)
|
|
return;
|
|
if (by > 0)
|
|
{
|
|
erode = 0;
|
|
thresh = by;
|
|
}
|
|
else
|
|
{
|
|
erode = 1;
|
|
thresh = -by;
|
|
}
|
|
|
|
itop = params->pixels_per_line * params->lines;
|
|
mask = mask_img;
|
|
sanei_ir_manhattan_dist (params, mask_img, dist_map, idx_map, erode);
|
|
|
|
manhattan = dist_map;
|
|
for (i = 0; i < itop; i++)
|
|
{
|
|
if (*manhattan++ <= thresh)
|
|
*mask++ = 0;
|
|
else
|
|
*mask++ = 255;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/* Suggest cropping for dark margins of positive film
|
|
*/
|
|
void
|
|
sanei_ir_find_crop (const SANE_Parameters * params,
|
|
unsigned int * dist_map, int inner, int * edges)
|
|
{
|
|
int width = params->pixels_per_line;
|
|
int height = params->lines;
|
|
uint64_t sum_x, sum_y, n;
|
|
int64_t sum_xx, sum_xy;
|
|
double a, b, mami;
|
|
unsigned int *src;
|
|
int off1, off2, inc, wh, i, j;
|
|
|
|
DBG (10, "sanei_ir_find_crop\n");
|
|
|
|
/* loop through top, bottom, left, right */
|
|
for (j = 0; j < 4; j++)
|
|
{
|
|
if (j < 2) /* top, bottom */
|
|
{
|
|
off1 = width / 8; /* only middle 3/4 */
|
|
off2 = width - off1;
|
|
n = width - 2 * off1;
|
|
src = dist_map + off1; /* first row */
|
|
inc = 1;
|
|
wh = width;
|
|
if (j == 1) /* last row */
|
|
src += (height - 1) * width;
|
|
}
|
|
else /* left, right */
|
|
{
|
|
off1 = height / 8; /* only middle 3/4 */
|
|
off2 = height - off1;
|
|
n = height - 2 * off1;
|
|
src = dist_map + (off1 * width); /* first column */
|
|
inc = width;
|
|
wh = height;
|
|
if (j == 3)
|
|
src += width - 1; /* last column */
|
|
}
|
|
|
|
/* calculate linear regression */
|
|
sum_x = 0; sum_y = 0;
|
|
sum_xx = 0; sum_xy = 0;
|
|
for (i = off1; i < off2; i++)
|
|
{
|
|
sum_x += i;
|
|
sum_y += *src;
|
|
sum_xx += i * i;
|
|
sum_xy += i * (*src);
|
|
src += inc;
|
|
}
|
|
b = ((double) n * (double) sum_xy - (double) sum_x * (double) sum_y)
|
|
/ ((double) n * (double) sum_xx - (double) sum_x * (double) sum_x);
|
|
a = ((double) sum_y - b * (double) sum_x) / (double) n;
|
|
|
|
DBG (10, "sanei_ir_find_crop: y = %f + %f * x\n", a, b);
|
|
|
|
/* take maximal/minimal value from either side */
|
|
mami = a + b * (wh - 1);
|
|
if (inner)
|
|
{
|
|
if (a > mami)
|
|
mami = a;
|
|
}
|
|
else
|
|
{
|
|
if (a < mami)
|
|
mami = a;
|
|
}
|
|
edges[j] = mami + 0.5;
|
|
}
|
|
edges[1] = height - edges[1];
|
|
edges[3] = width - edges[3];
|
|
|
|
DBG (10, "sanei_ir_find_crop: would crop at top: %d, bot: %d, left %d, right %d\n",
|
|
edges[0], edges[1], edges[2], edges[3]);
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/* Dilate clean image parts into dirty ones and smooth
|
|
*/
|
|
SANE_Status
|
|
sanei_ir_dilate_mean (const SANE_Parameters * params,
|
|
SANE_Uint **in_img,
|
|
SANE_Uint * mask_img,
|
|
int dist_max, int expand, int win_size,
|
|
SANE_Bool smooth, int inner,
|
|
int *crop)
|
|
{
|
|
SANE_Uint *color;
|
|
SANE_Uint *plane;
|
|
unsigned int *dist_map, *manhattan;
|
|
unsigned int *idx_map, *index;
|
|
int dist;
|
|
int rows, cols;
|
|
int k, i, itop;
|
|
SANE_Status ret = SANE_STATUS_NO_MEM;
|
|
|
|
DBG (10, "sanei_ir_dilate_mean(): dist max = %d, expand = %d, win size = %d, smooth = %d, inner = %d\n",
|
|
dist_max, expand, win_size, smooth, inner);
|
|
|
|
cols = params->pixels_per_line;
|
|
rows = params->lines;
|
|
itop = rows * cols;
|
|
idx_map = malloc (itop * sizeof (unsigned int));
|
|
dist_map = malloc (itop * sizeof (unsigned int));
|
|
plane = malloc (itop * sizeof (SANE_Uint));
|
|
|
|
if (!idx_map || !dist_map || !plane)
|
|
DBG (5, "sanei_ir_dilate_mean: Cannot allocate buffers\n");
|
|
else
|
|
{
|
|
/* expand dirty regions into their half dirty surround*/
|
|
if (expand > 0)
|
|
sanei_ir_dilate (params, mask_img, dist_map, idx_map, expand);
|
|
/* for dirty pixels determine the closest clean ones */
|
|
sanei_ir_manhattan_dist (params, mask_img, dist_map, idx_map, 1);
|
|
|
|
/* use the distance map to find how to crop dark edges */
|
|
if (crop)
|
|
sanei_ir_find_crop (params, dist_map, inner, crop);
|
|
|
|
/* replace dirty pixels */
|
|
for (k = 0; k < 3; k++)
|
|
{
|
|
manhattan = dist_map;
|
|
index = idx_map;
|
|
color = in_img[k];
|
|
/* first replacement */
|
|
for (i = 0; i < itop; i++)
|
|
{
|
|
dist = *manhattan++;
|
|
if ((dist != 0) && (dist <= dist_max))
|
|
color[i] = color[index[i]];
|
|
}
|
|
/* adapt pixels to their new surround and
|
|
* smooth the whole image or the replaced pixels only */
|
|
ret =
|
|
sanei_ir_filter_mean (params, color, plane, win_size, win_size);
|
|
if (ret != SANE_STATUS_GOOD)
|
|
break;
|
|
else
|
|
if (smooth)
|
|
{
|
|
/* a second mean results in triangular blur */
|
|
DBG (10, "sanei_ir_dilate_mean(): smoothing whole image\n");
|
|
ret =
|
|
sanei_ir_filter_mean (params, plane, color, win_size,
|
|
win_size);
|
|
if (ret != SANE_STATUS_GOOD)
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
/* replace with smoothened pixels only */
|
|
DBG (10, "sanei_ir_dilate_mean(): smoothing replaced pixels only\n");
|
|
manhattan = dist_map;
|
|
for (i = 0; i < itop; i++)
|
|
{
|
|
dist = *manhattan++;
|
|
if ((dist != 0) && (dist <= dist_max))
|
|
color[i] = plane[i];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
free (plane);
|
|
free (dist_map);
|
|
free (idx_map);
|
|
|
|
return ret;
|
|
}
|