title: OpenCV Python best thresholding for document photos with varying light
body: <p><strong>Thank you for your time dear reader,</strong></p>
  
  <p>i´m trying to implement a document scanner in Python/OpenCV but im struggling with varying lights in the image. <br>
  Im hoping that maybe some kind soul can at least point me in the right direction because I have no concrete clue how I could improve it - if that is possible or known at all.</p>
  
  <p>Im using otsus binarization for thresholding:
  <a href="https://docs.opencv.org/3.4.0/d7/d4d/tutorial_py_thresholding.html" rel="nofollow noreferrer">https://docs.opencv.org/3.4.0/d7/d4d/tutorial_py_thresholding.html</a></p>
  
  <p>My results so far are pretty good:
  <a href="https://i.stack.imgur.com/QM7sE.jpg" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/QM7sE.jpg" alt="results so far"></a>
  <br></p>
  
  <p>But for difficult lighting with either two bright/dark areas or one bright - one dark area for instance (this is a crass example) it fails:
  <a href="https://i.stack.imgur.com/3Y8dH.jpg" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/3Y8dH.jpg" alt="enter image description here"></a>
  <br></p>
  
  <p>Playing around with Gimp curves sometimes gets me clear edges - maybe there is a best practice how to tackle this problem that I dont know of?<br>
  I played around with the code a lot but got no real progress by combining hierarchy / chain approx / approxpolydp epsilon methods etc.</p>
  
  <p>My current code:</p>
  
  <pre><code>#!/usr/bin/python
  # -*- coding: UTF-8 -*-
  
  import cv2 as cv
  import numpy as np
  import sys
  
  # zero at the end reads black white
  img = cv.imread(sys.argv[1],0)
  blur = cv.GaussianBlur(img,(5,5),0)
  # find normalized_histogram, and its cumulative distribution function
  hist = cv.calcHist([blur],[0],None,[256],[0,256])
  hist_norm = hist.ravel()/hist.max()
  Q = hist_norm.cumsum()
  bins = np.arange(256)
  fn_min = np.inf
  thresh = -1
  for i in range(1,256):
      p1,p2 = np.hsplit(hist_norm,[i]) # probabilities
      q1,q2 = Q[i],Q[255]-Q[i] # cum sum of classes
      b1,b2 = np.hsplit(bins,[i]) # weights
      # finding means and variances
      m1,m2 = np.sum(p1*b1)/q1, np.sum(p2*b2)/q2
      v1,v2 = np.sum(((b1-m1)**2)*p1)/q1,np.sum(((b2-m2)**2)*p2)/q2
      # calculates the minimization function
      fn = v1*q1 + v2*q2
      if fn &lt; fn_min:
          fn_min = fn
          thresh = i
  
  # find otsu's threshold value with OpenCV function
  ret, otsu = cv.threshold(blur,0,255,cv.THRESH_BINARY+cv.THRESH_OTSU)
  
  cv.imwrite('otsu.jpg',otsu)
  
  _, contours, hierarchy = cv.findContours(otsu, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
  
  def biggestRectangle(contours):
      biggest = None
      max_area = 0
      indexReturn = -1
      for index in range(len(contours)):
              i = contours[index]
              area = cv.contourArea(i)
              if area &gt; 100:
                  peri = cv.arcLength(i,True)
                  approx = cv.approxPolyDP(i,0.1*peri,True)
                  if area &gt; max_area: #and len(approx)==4:
                          biggest = approx
                          max_area = area
                          indexReturn = index
      return indexReturn
  
  indexReturn = biggestRectangle(contours)
  hull = cv.convexHull(contours[indexReturn])
  orig = cv.imread(sys.argv[1])
  cv.imwrite('hola.jpg',cv.drawContours(orig, [hull], 0, (0,255,0),3))
  </code></pre>
  
  <p>The biggest rectangle code I copied from here:<br>
  <a href="https://stackoverflow.com/questions/42056592/how-to-detect-document-from-a-picture-in-opencv">How to detect document from a picture in opencv?</a>
  <br>All credits to monic! (accepted answer)</p>