Reputation: 59
Using the threshold functions in open CV on an image to get a binary image, with Otsu's thresholding I get a image that has white spots due to different lighting conditions in parts of the image
or with adaptive threshold to fix the lighting conditions, it fails to accurately represent the pencil-filled bubbles that Otsu actually can represent.
How can I get both the filled bubbles represented and a fixed lighting conditions without patches?
Here's the original image
Here is my code
#binary image conversion
thresh2 = cv2.adaptiveThreshold(papergray, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY_INV, 21, 13)
thresh = cv2.threshold(papergray, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]
cv2.imshow("Binary", thresh) #Otsu's
cv2.imshow("Adpative",thresh2)
Upvotes: 3
Views: 974
Reputation: 21233
The methods you have tried out:
OpenCV's ximgproc
module has specialized binary image generation methods. One such method is the popular Niblack threshold technique.
This is a local threshold technique that depends on statistical measures. It divides the image into blocks (sub-images) of size predefined by the user. A threshold is set based on the mean minus k times standard deviation of pixel values for each block. The k
is decided by the user.
Code:
img =cv2.imread('omr_sheet.jpg')
blur = cv2.GaussianBlur(img, (3, 3), 0)
gray = cv2.cvtColor(blur, cv2.COLOR_BGR2GRAY)
niblack = cv2.ximgproc.niBlackThreshold(gray, 255, cv2.THRESH_BINARY, 41, -0.1, binarizationMethod=cv2.ximgproc.BINARIZATION_NICK)
Result:
Links:
To know more about cv2.ximgproc.niBlackThreshold
There are other binarization techniques available that you may want to explore. It also contains links to research papers that explain each of these techniques on detail.
Edit: Adaptive threshold actually works if you know what you are working with. You can decide the kernel size beforehand.
See Prashant's answer.
Upvotes: 4
Reputation: 60770
An alternative approach would be to apply a morphological closing, which would remove all the drawing, yielding an estimate of the illumination level. Dividing the image by the illumination level gives you an image of the sheet corrected for illumination:
In this image we can easily apply a global threshold:
I used the following code:
import diplib as dip
img = dip.ImageRead('tlCw6.jpg')(1)
corrected = img / dip.Closing(img, dip.SE(40, 'rectangular'))
out = dip.Threshold(corrected, method='triangle')[0]
Upvotes: 6
Reputation: 678
This can be done with cv2.ADAPTIVE_THRESH_MEAN_C
:
import cv2
img = cv2.imread("omr.jpg")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
thresh = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY_INV, 31, 10)
cv2.imshow("Mean Adaptive Thresholding", thresh)
cv2.waitKey(0)
Upvotes: 5