1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
|
/*
* Copyright (C) 2005 - 2013 René Rebe, ExactCODE GmbH
* (C) 2005 - 2007 Archivista GmbH, CH-8042 Zuerich
*
* 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; version 2. A copy of the GNU General
* Public License can be found in the file LICENSE.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANT-
* ABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* Alternatively, commercial licensing options are available from the
* copyright holder ExactCODE GmbH Germany.
*/
/* Short Description:
* Any color-space to b/w (1-bit) optimization. Applications involving
* huge amount of document archiving often prefer storing the data
* as (compressed) b/w data even in the age of TB storage as higher
* (color) image often explores the data volume exponentially.
*
* For this case this algorithm automatically determines a threshold
* and performs some optimizations in order to loose as few details
* as possible (e.g. retrieve hand notes, stamps, etc. but leave
* the background color, coffee sparkels, dust and folding lines
* out.
*
* While the first version only performed a unsharp mask and auto-
* thresholding, the latest version does perform a segmentation-pass,
* to optimize the performance (by leaving out empty areas) and
* to keep dithering of image areas.
*/
#include <cstdlib>
#include <cmath>
#include <iostream>
#include <iomanip>
#ifdef _MSC_VER
#include <vector>
#endif
#include "Image.hh"
#include "Colorspace.hh"
#include "Matrix.hh"
#include "optimize2bw.hh"
//#include "Timer.cc"
void optimize2bw (Image& image, int low, int high, int threshold,
int sloppy_threshold,
int radius, double standard_deviation)
{
// do nothing if already at s/w, ...
if (image.spp == 1 && image.bps == 1)
return;
/* Convert to RGB to gray.
If the threshold is to be determined automatically, use color info. */
const bool debug = false;
// color normalize on background color
// search for background color
{
std::vector<std::vector<unsigned int> > hist = histogram(image);
colorspace_by_name (image, "rgb8");
int lowest = 255, highest = 0, bg_r = 0, bg_g = 0, bg_b = 0;
for (int i = 0; i <= 255; i++)
{
int r, g, b;
r = g = b = hist[0][i];
if (hist.size() > 1) {
g = hist[1][i];
b = hist[2][i];
}
if (debug)
std::cout << i << ": "<< r << " " << g << " " << b << std::endl;
const int magic = 2; // magic denoise constant
if (r >= magic || g >= magic || b >= magic)
{
if (i < lowest)
lowest = i;
if (i > highest)
highest = i;
}
if (hist[0][i] > hist[0][bg_r])
bg_r = i;
if (hist.size() > 1) {
if (hist[1][i] > hist[1][bg_r])
bg_g = i;
if (hist[2][i] > hist[2][bg_r])
bg_b = i;
} else {
bg_g = bg_b = i;
}
}
highest = (int) (.21267 * bg_r + .71516 * bg_g + .07217 * bg_b);
if (false)
std::cerr << "lowest: " << lowest << ", highest: " << highest
<< ", back rgb: " << bg_r << " " << bg_g << " " << bg_b
<< std::endl;
const int min_delta = 128;
lowest = std::max (std::min (lowest, highest - min_delta), 0);
highest = std::min (std::max (highest, lowest + min_delta), 255);
if (low)
lowest = low;
if (high)
highest = high;
if (false)
std::cerr << "after limit and overwrite, lowest: " << lowest
<< ", highest: " << highest << std::endl;
signed int a = (255 * 256) / (highest - lowest);
signed int b = (-a * lowest);
if (false)
std::cerr << "a: " << (float) a / 256
<< " b: " << (float) b / 256 << std::endl;
uint8_t* it = image.getRawData();
uint8_t* end = image.getRawDataEnd();
uint8_t* it2 = it;
while (it != end) {
int _r = *it++;
int _g = *it++;
int _b = *it++;
_r = (_r * a + b) / 256;
_g = (_g * a + b) / 256;
_b = (_b * a + b) / 256;
// clip
_r = std::max (std::min (_r, 255), 0);
_g = std::max (std::min (_g, 255), 0);
_b = std::max (std::min (_b, 255), 0);
// on-the-fly convert to gray with associated weighting
*it2++ = (_r*28 + _g*59 + _b*11) / 100;
}
image.spp = 1; // converted data RGB8->GRAY8
image.setRawData();
}
// Convolution Matrix (unsharp mask a-like)
if (radius > 0)
{
// compute kernel (convolution matrix to move over the iamge)
// Utility::AutoTimer<Utility::Timer> timer ("convolution");
matrix_type divisor = 0;
float sd = standard_deviation;
#ifdef _MSC_VER
std::vector<matrix_type> matrix(radius+1);
std::vector<matrix_type> matrix_2(radius+1);
#else
matrix_type matrix[radius+1];
matrix_type matrix_2[radius+1];
#endif
for (int d = 0; d <= radius; ++d) {
matrix_type v = (matrix_type) (exp (-((float)d*d) / (2. * sd * sd)) );
matrix[d] = v;
divisor+=v;
if (d>0)
divisor+=v;
}
// normalize (will not work with integer matrix type !)
divisor=1.0/divisor;
for (int i=0; i<=radius; i++) {
matrix[i]*=divisor;
matrix_2[i]=-matrix[i];
}
decomposable_sym_convolution_matrix (image, &matrix[0], &matrix_2[0], radius, radius, 2.0);
}
}
|