tesseract 3.04.01

training/degradeimage.cpp

Go to the documentation of this file.
00001 /**********************************************************************
00002  * File:        degradeimage.cpp
00003  * Description: Function to degrade an image (usually of text) as if it
00004  *              has been printed and then scanned.
00005  * Authors:     Ray Smith
00006  * Created:     Tue Nov 19 2013
00007  *
00008  * (C) Copyright 2013, Google Inc.
00009  * Licensed under the Apache License, Version 2.0 (the "License");
00010  * you may not use this file except in compliance with the License.
00011  * You may obtain a copy of the License at
00012  * http://www.apache.org/licenses/LICENSE-2.0
00013  * Unless required by applicable law or agreed to in writing, software
00014  * distributed under the License is distributed on an "AS IS" BASIS,
00015  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
00016  * See the License for the specific language governing permissions and
00017  * limitations under the License.
00018  *
00019  **********************************************************************/
00020 
00021 #include "degradeimage.h"
00022 
00023 #include <stdlib.h>
00024 #include "allheaders.h"   // from leptonica
00025 #include "helpers.h"  // For TRand.
00026 
00027 namespace tesseract {
00028 
00029 // Rotation is +/- kRotationRange radians.
00030 const float kRotationRange = 0.02f;
00031 // Number of grey levels to shift by for each exposure step.
00032 const int kExposureFactor = 16;
00033 // Salt and pepper noise is +/- kSaltnPepper.
00034 const int kSaltnPepper = 5;
00035 // Min sum of width + height on which to operate the ramp.
00036 const int kMinRampSize = 1000;
00037 
00038 // Degrade the pix as if by a print/copy/scan cycle with exposure > 0
00039 // corresponding to darkening on the copier and <0 lighter and 0 not copied.
00040 // Exposures in [-2,2] are most useful, with -3 and 3 being extreme.
00041 // If rotation is NULL, rotation is skipped. If *rotation is non-zero, the pix
00042 // is rotated by *rotation else it is randomly rotated and *rotation is
00043 // modified.
00044 //
00045 // HOW IT WORKS:
00046 // Most of the process is really dictated by the fact that the minimum
00047 // available convolution is 3X3, which is too big really to simulate a
00048 // good quality print/scan process. (2X2 would be better.)
00049 // 1 pixel wide inputs are heavily smeared by the 3X3 convolution, making the
00050 // images generally biased to being too light, so most of the work is to make
00051 // them darker. 3 levels of thickening/darkening are achieved with 2 dilations,
00052 // (using a greyscale erosion) one heavy (by being before convolution) and one
00053 // light (after convolution).
00054 // With no dilation, after covolution, the images are so light that a heavy
00055 // constant offset is required to make the 0 image look reasonable. A simple
00056 // constant offset multiple of exposure to undo this value is enough to achieve
00057 // all the required lightening. This gives the advantage that exposure level 1
00058 // with a single dilation gives a good impression of the broken-yet-too-dark
00059 // problem that is often seen in scans.
00060 // A small random rotation gives some varying greyscale values on the edges,
00061 // and some random salt and pepper noise on top helps to realistically jaggy-up
00062 // the edges.
00063 // Finally a greyscale ramp provides a continuum of effects between exposure
00064 // levels.
00065 Pix* DegradeImage(Pix* input, int exposure, TRand* randomizer,
00066                   float* rotation) {
00067   Pix* pix = pixConvertTo8(input, false);
00068   pixDestroy(&input);
00069   input = pix;
00070   int width = pixGetWidth(input);
00071   int height = pixGetHeight(input);
00072   if (exposure >= 2) {
00073     // An erosion simulates the spreading darkening of a dark copy.
00074     // This is backwards to binary morphology,
00075     // see http://www.leptonica.com/grayscale-morphology.html
00076     pix = input;
00077     input = pixErodeGray(pix, 3, 3);
00078     pixDestroy(&pix);
00079   }
00080   // A convolution is essential to any mode as no scanner produces an
00081   // image as sharp as the electronic image.
00082   pix = pixBlockconv(input, 1, 1);
00083   pixDestroy(&input);
00084   // A small random rotation helps to make the edges jaggy in a realistic way.
00085   if (rotation != NULL) {
00086     float radians_clockwise = 0.0f;
00087     if (*rotation) {
00088       radians_clockwise = *rotation;
00089     } else if (randomizer != NULL) {
00090       radians_clockwise = randomizer->SignedRand(kRotationRange);
00091     }
00092 
00093     input = pixRotate(pix, radians_clockwise,
00094                       L_ROTATE_AREA_MAP, L_BRING_IN_WHITE,
00095                       0, 0);
00096     // Rotate the boxes to match.
00097     *rotation = radians_clockwise;
00098     pixDestroy(&pix);
00099   } else {
00100     input = pix;
00101   }
00102 
00103   if (exposure >= 3 || exposure == 1) {
00104     // Erosion after the convolution is not as heavy as before, so it is
00105     // good for level 1 and in addition as a level 3.
00106     // This is backwards to binary morphology,
00107     // see http://www.leptonica.com/grayscale-morphology.html
00108     pix = input;
00109     input = pixErodeGray(pix, 3, 3);
00110     pixDestroy(&pix);
00111   }
00112   // The convolution really needed to be 2x2 to be realistic enough, but
00113   // we only have 3x3, so we have to bias the image darker or lose thin
00114   // strokes.
00115   int erosion_offset = 0;
00116   // For light and 0 exposure, there is no dilation, so compensate for the
00117   // convolution with a big darkening bias which is undone for lighter
00118   // exposures.
00119   if (exposure <= 0)
00120     erosion_offset = -3 * kExposureFactor;
00121   // Add in a general offset of the greyscales for the exposure level so
00122   // a threshold of 128 gives a reasonable binary result.
00123   erosion_offset -= exposure * kExposureFactor;
00124   // Add a gradual fade over the page and a small amount of salt and pepper
00125   // noise to simulate noise in the sensor/paper fibres and varying
00126   // illumination.
00127   l_uint32* data = pixGetData(input);
00128   for (int y = 0; y < height; ++y) {
00129     for (int x = 0; x < width; ++x) {
00130       int pixel = GET_DATA_BYTE(data, x);
00131       if (randomizer != NULL)
00132         pixel += randomizer->IntRand() % (kSaltnPepper*2 + 1) - kSaltnPepper;
00133       if (height + width > kMinRampSize)
00134         pixel -= (2*x + y) * 32 / (height + width);
00135       pixel += erosion_offset;
00136       if (pixel < 0)
00137         pixel = 0;
00138       if (pixel > 255)
00139         pixel = 255;
00140       SET_DATA_BYTE(data, x, pixel);
00141     }
00142     data += input->wpl;
00143   }
00144   return input;
00145 }
00146 
00147 }  // namespace tesseract
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines