All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
degradeimage.cpp
Go to the documentation of this file.
1 /**********************************************************************
2  * File: degradeimage.cpp
3  * Description: Function to degrade an image (usually of text) as if it
4  * has been printed and then scanned.
5  * Authors: Ray Smith
6  * Created: Tue Nov 19 2013
7  *
8  * (C) Copyright 2013, Google Inc.
9  * Licensed under the Apache License, Version 2.0 (the "License");
10  * you may not use this file except in compliance with the License.
11  * You may obtain a copy of the License at
12  * http://www.apache.org/licenses/LICENSE-2.0
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  *
19  **********************************************************************/
20 
21 #include "degradeimage.h"
22 
23 #include <stdlib.h>
24 #include "allheaders.h" // from leptonica
25 #include "helpers.h" // For TRand.
26 
27 namespace tesseract {
28 
29 // Rotation is +/- kRotationRange radians.
30 const float kRotationRange = 0.02f;
31 // Number of grey levels to shift by for each exposure step.
32 const int kExposureFactor = 16;
33 // Salt and pepper noise is +/- kSaltnPepper.
34 const int kSaltnPepper = 5;
35 // Min sum of width + height on which to operate the ramp.
36 const int kMinRampSize = 1000;
37 
38 // Degrade the pix as if by a print/copy/scan cycle with exposure > 0
39 // corresponding to darkening on the copier and <0 lighter and 0 not copied.
40 // Exposures in [-2,2] are most useful, with -3 and 3 being extreme.
41 // If rotation is NULL, rotation is skipped. If *rotation is non-zero, the pix
42 // is rotated by *rotation else it is randomly rotated and *rotation is
43 // modified.
44 //
45 // HOW IT WORKS:
46 // Most of the process is really dictated by the fact that the minimum
47 // available convolution is 3X3, which is too big really to simulate a
48 // good quality print/scan process. (2X2 would be better.)
49 // 1 pixel wide inputs are heavily smeared by the 3X3 convolution, making the
50 // images generally biased to being too light, so most of the work is to make
51 // them darker. 3 levels of thickening/darkening are achieved with 2 dilations,
52 // (using a greyscale erosion) one heavy (by being before convolution) and one
53 // light (after convolution).
54 // With no dilation, after covolution, the images are so light that a heavy
55 // constant offset is required to make the 0 image look reasonable. A simple
56 // constant offset multiple of exposure to undo this value is enough to achieve
57 // all the required lightening. This gives the advantage that exposure level 1
58 // with a single dilation gives a good impression of the broken-yet-too-dark
59 // problem that is often seen in scans.
60 // A small random rotation gives some varying greyscale values on the edges,
61 // and some random salt and pepper noise on top helps to realistically jaggy-up
62 // the edges.
63 // Finally a greyscale ramp provides a continuum of effects between exposure
64 // levels.
65 Pix* DegradeImage(Pix* input, int exposure, TRand* randomizer,
66  float* rotation) {
67  Pix* pix = pixConvertTo8(input, false);
68  pixDestroy(&input);
69  input = pix;
70  int width = pixGetWidth(input);
71  int height = pixGetHeight(input);
72  if (exposure >= 2) {
73  // An erosion simulates the spreading darkening of a dark copy.
74  // This is backwards to binary morphology,
75  // see http://www.leptonica.com/grayscale-morphology.html
76  pix = input;
77  input = pixErodeGray(pix, 3, 3);
78  pixDestroy(&pix);
79  }
80  // A convolution is essential to any mode as no scanner produces an
81  // image as sharp as the electronic image.
82  pix = pixBlockconv(input, 1, 1);
83  pixDestroy(&input);
84  // A small random rotation helps to make the edges jaggy in a realistic way.
85  if (rotation != NULL) {
86  float radians_clockwise = 0.0f;
87  if (*rotation) {
88  radians_clockwise = *rotation;
89  } else if (randomizer != NULL) {
90  radians_clockwise = randomizer->SignedRand(kRotationRange);
91  }
92 
93  input = pixRotate(pix, radians_clockwise,
94  L_ROTATE_AREA_MAP, L_BRING_IN_WHITE,
95  0, 0);
96  // Rotate the boxes to match.
97  *rotation = radians_clockwise;
98  pixDestroy(&pix);
99  } else {
100  input = pix;
101  }
102 
103  if (exposure >= 3 || exposure == 1) {
104  // Erosion after the convolution is not as heavy as before, so it is
105  // good for level 1 and in addition as a level 3.
106  // This is backwards to binary morphology,
107  // see http://www.leptonica.com/grayscale-morphology.html
108  pix = input;
109  input = pixErodeGray(pix, 3, 3);
110  pixDestroy(&pix);
111  }
112  // The convolution really needed to be 2x2 to be realistic enough, but
113  // we only have 3x3, so we have to bias the image darker or lose thin
114  // strokes.
115  int erosion_offset = 0;
116  // For light and 0 exposure, there is no dilation, so compensate for the
117  // convolution with a big darkening bias which is undone for lighter
118  // exposures.
119  if (exposure <= 0)
120  erosion_offset = -3 * kExposureFactor;
121  // Add in a general offset of the greyscales for the exposure level so
122  // a threshold of 128 gives a reasonable binary result.
123  erosion_offset -= exposure * kExposureFactor;
124  // Add a gradual fade over the page and a small amount of salt and pepper
125  // noise to simulate noise in the sensor/paper fibres and varying
126  // illumination.
127  l_uint32* data = pixGetData(input);
128  for (int y = 0; y < height; ++y) {
129  for (int x = 0; x < width; ++x) {
130  int pixel = GET_DATA_BYTE(data, x);
131  if (randomizer != NULL)
132  pixel += randomizer->IntRand() % (kSaltnPepper*2 + 1) - kSaltnPepper;
133  if (height + width > kMinRampSize)
134  pixel -= (2*x + y) * 32 / (height + width);
135  pixel += erosion_offset;
136  if (pixel < 0)
137  pixel = 0;
138  if (pixel > 255)
139  pixel = 255;
140  SET_DATA_BYTE(data, x, pixel);
141  }
142  data += input->wpl;
143  }
144  return input;
145 }
146 
147 } // namespace tesseract
const int kExposureFactor
const float kRotationRange
const int kSaltnPepper
double SignedRand(double range)
Definition: helpers.h:53
const int kMinRampSize
inT32 IntRand()
Definition: helpers.h:48
Pix * DegradeImage(Pix *input, int exposure, TRand *randomizer, float *rotation)
#define NULL
Definition: host.h:144