31

This is the original image.

Orignal Image

Cam Scanner Magic color effect.Cam Scanner effect

My filter on the image.

My filter

I am changing the contrast of the image.

dst.convertTo(dst, -1, 2, 0);

Then using Gaussian blur for smoothing.

cv::GaussianBlur(dst,result,cv::Size(0,0),3);
cv::addWeighted(dst, 1.5, result, -0.5, 0, result);

What should I do achieve that kind of effect on my image ?

UPDATE

After Histogram Equilization -

vector<Mat> channels;
Mat img_hist_equalized;
cvtColor(dst, img_hist_equalized, CV_BGR2YCrCb);
split(img_hist_equalized,channels);
equalizeHist(channels[0], channels[0]);
merge(channels,img_hist_equalized);
cvtColor(img_hist_equalized, img_hist_equalized, CV_YCrCb2BGR);

Histogram Equilization

mihirjoshi
  • 12,161
  • 7
  • 47
  • 78

5 Answers5

15

The camscanner application may be using some complex algorithm to handle various lightning cases, etc. But I will try to cover a basic approach to such problem, The basic idea here is Binarization of the given input image, Or more precisely we can say Theresholding a given image, If you look at the OpenCV documentation, there are a lot of references to thresholding a given image, So let's start with the documentation.

  • Global Thresholding: In this approach we assume that the intensity value of the foreground is always below a certain value, In context of printed sheets, we assume that the ink color is always black and paper color is uniform and intensity is greater than the intensity of ink color, so we safely assume some threshold (say 40), (max. is 255) and threshold the input image as :

     ret, thresh1 = cv2.threshold(img, 100, 255, cv2.THRESH_BINARY)
    

enter image description here

    ret, thresh1 = cv2.threshold(img, 130, 255, cv2.THRESH_BINARY)

enter image description here

There are many disadvantages to this method, First of all it is **NOT** independent of intensity variance, So there is a very less chance that you can accurately estimate  a threshold value which segments text from the given image, It has very limited applications, can be only applied in case where the background paper is exactly white with minimum variation in intensity, so this process cannot be used for **Real world** images.
  • Adaptive Thresholding: This method covers the intensity variation problem in the given image, here the thresholding is done on the values of neighbouring pixels, So transitions from lower intensity to higher and vice versa are successfully captured with this method as:

     thresh = cv2.adaptiveThreshold(original_img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2)
    

enter image description here

*Further Work*: You may work on various techniques of denoising the binary image, to remove the dots, Or have a look at removing the salt and pepper noise from the image.
  • Otu's Binarization: This is yet another nice approach which intelligently calculates the threshold value, between the maximas, It may work very nice in some of the cases, but it seems to fail in your case.

     ret2,thresh = cv2.threshold(img,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
    

enter image description here

It basically does the same global thresholding But now the threshold is calculated automatically such that the threshold lies between 2 peaks and hence segmenting the ink from the paper.

Recommended Method: I guess the best approach to start with is Adaptive Thresholding, You may try some other preprocessing techniques such as sharpening image, Histogram Equalisation, etc. and analyse How it creates a more realistic output, You may also try to do some post processing such as denoising the image, Morphological operations

I tried the denoising of image and found it more effective to other approaches,

denoised = cv2.fastNlMeansDenoising(thresh, 11, 31, 9) # you may experiment with the constants here

enter image description here

But I welcome you to try various combinations of the above approaches to see which one works for all cases.

Note: The above technique may work for less colorful images. However, here is another excellent answer, which may solve the case of colored images.

ZdaR
  • 22,343
  • 7
  • 66
  • 87
  • 1
    It is a nice solution, but I do not they are using binarization. Binarization will generate bi-level image. However, in the example, the processed image is clean, enhanced but with more than two colors. Therefore, I do not think adaptive binarization can do the trick. – feelfree Oct 09 '15 at 15:15
  • Its a nice solution, though I haven't tried it yet, will it work for colored images as well. – mihirjoshi Oct 10 '15 at 04:20
  • No it won't work for coloured images, I tried to get the expected output for the given Input, irrespective of what magic color actually does, I would try to work this out for colored images if I get enough time. – ZdaR Oct 10 '15 at 04:49
  • @ZdaR Thanks for trying at least – mihirjoshi Oct 13 '15 at 20:23
  • sincerely, this answer doesn't deserves this much appreciation, I was unaware of the fact that Magic Color effect works for colored images as well, However if I get enough time I will update this answer accordingly to address the question properly, The current answer would work in case of characters printed on plain sheets of paper. – ZdaR Oct 13 '15 at 21:54
  • @mjosh, meanwhile I was trying the magic color effect on different images and found that it may be doing some sort of **downsampling** and then increasing the brightness of the downsampled image, I am not sure though, I will surely update this thread as soon as I get enough time. – ZdaR Oct 13 '15 at 21:56
9

One very simple, yet effective, method to deal with this type of image is a flat-field correction.

First, you generate a "fake" flat-field image F by applying a very strong blur filter to the original image I. Then you multiply I by the mean of F, and divide the resulting image by F (pixel by pixel) to get the corrected image C. The multiplication is just there to preserve the overall brightness, the division is where the magic happens.

Basically this: C = (I * mean(F)) / F

The resulting corrected image C will have most, if not all, of the unwanted large-scale lighting and color removed. Then all that's left to do is some contrast-stretching, and you get a result very similar to the provided reference image. (gray-scale, high contrast, but not thresholded)

If you're wondering what the result looks like for the provided image...

First, the flat field:

flat field

Then the corrected image:

corrected image

And finally, after increasing the contrast:

increased contrast

The hardest part about this is getting the flat field just right, as you want to blur it enough to get rid of the text, while at the same time preserving the background as much as possible. Non-linear filters (e.g. median), can help in this case.

Felix G
  • 674
  • 1
  • 7
  • 17
  • Thanks for the suggestion! It'd be great if you could provide a small proof of concept example, my friend. – stateMachine Jul 03 '20 at 16:26
  • @eldesgraciado Of course, i've added an example of what the result (and intermediate steps) would look like for the original image. – Felix G Jul 03 '20 at 19:56
  • Thanks for the answer, but let's not forget that CamScanner also maintains the quality of the image (seems like it increases the resolution of the image) – Pe Dro Jul 05 '20 at 06:14
  • I don't have that app, so all i can do is try to recreate the provided reference image (which, unfortunately, has an even lower resolution than the already low-res input image). The flat-field correction might not be the complete answer, but it is a good first step to get rid of a whole lot of problems in the input image. – Felix G Jul 05 '20 at 07:45
  • 1
    @FelixG A median filter, as you suggest, does the trick very nicely. If only linear blurring is used, you get dark blobs where there is more text-per-area. For example a paragraph of text might have 20% black ink, and then follow several rows of white space. In that case, the paragraph of text will create a big gray blob when blurring is applied, even if it's a perfectly taken photo. But with median filtering, the text will just disappear, as long as it takes up less than 50% of the local area). I tried with the image above and the text disappears with a median filter radius of only 11 pixels. – foolo Jul 05 '20 at 16:52
  • Very good appraoch, this will work for the colored cases as well, as opposed to the other answer from my side. – ZdaR Jul 06 '20 at 08:00
  • Can you describe how exactly are you increasing the contrast? I am not able to reproduce it. – satvik choudhary Jul 06 '20 at 23:46
  • @satvikchoudhary in this case i did it manually, after looking at the histogram of the corrected image, by mapping the range of approximately 100-240 to 0-255. – Felix G Jul 07 '20 at 08:04
3

I have used Photoshop to figure out editing techniques required to achieve the scan effect.

In Photoshop the scanning effect can be acheived using the operations, "set white point" & "set black point" provided by "Levels" feature. Combination of these two operations result in the scan effect often regarded as "magic color" in various mobile apps.

Apart from this, High Pass Filter can be used along with above two operations to achieve some exciting results like shadow removal.

Scanning of documents in the "Black & White" mode is achieved by processing the image in LAB color space using OpenCV.

Above mentioned operations can be implemented in OpenCV using various threshold techniques and few basic mathematical operations.

You can once go through this repository to get complete insight of what I am trying to say.

I have added a complete wiki documentation for the project in above repo.

This answer might not seem to be very informative but since the repo gives an elaborate discussion, I am keeping this post short.

Example of results we can achieve using these techniques: Image in top right corner and the image below it are the inputs whereas other images are output for various scan modes

Markings in this image helps us to understand the type of output from each mode discussed in the GitHub repo: Markings in this image helps us to understand the type of output from each mode discussed in the GitHub repo

MSD
  • 154
  • 2
  • 12
2

I've written code that does this sort of thing, although not with OpenCV.

Typically, I would analyze a histogram, estimate what "white" and "black" is based on the histogram, and then scale the image values so that black is scaled below 0 and white is scaled above 1 (or 255 depending on your representation), finally clamping the color values.

However, with OpenCV there may be a simpler way. Try using histogram equalization on your cropped page before applying a contrast filter -- that should spread out the pixel values in a more consistent manner so that adjusting the contrast works more reliably in more situations. You might try using localized histogram equalization to help mitigate gradients on the cropped image due to lighting, but this may cause issues with blank areas of the page.

Kaganar
  • 6,540
  • 2
  • 26
  • 59
1

I realize I'm a little late to the game, but I found this awesome, simple solution:

src.convertTo(dst, -1, 1.9, -80);

src and dst can be the same image if you are working in a processing pipeline.

bstar55
  • 3,542
  • 3
  • 20
  • 24
  • It will only change the contrast. Try it on the source image image I posted and you will see. – mihirjoshi Jul 16 '16 at 02:26
  • what is src and dst? Ar they same Bitmaps? – Narendra Sep 13 '16 at 16:21
  • Technically speaking, they are Mat objects that contain image data. In the case where src and dst are the same, the conversion will effectively happen "in-place". If src and dst are different Mat objects, the dst Mat will contain the converted src data. – bstar55 Sep 14 '16 at 06:35
  • If captured image is a document then text will get faded on the document upon applying above operation. It is not exactly what camscanner app is doing. – Mohd Khalil Ur Rehman Jan 13 '17 at 09:30
  • This is not the complete solution. Its only a part of the entire method to do so OR a trick to obtain a similar effect. – Pe Dro Jun 28 '20 at 05:14
  • what is convertTo ? – Shadab K Aug 03 '20 at 06:56