Pupil Detection in eye images using python - python-3.x

I need to mark a pupil in an image like this of the eye. I have written this code
img_name='6.jpg'
image = cv2.imread(img_name)
image_copy_new=cv2.imread(img_name)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
retval, thresholded = cv2.threshold(gray, 30, 255, cv2.THRESH_BINARY_INV)
plt.imshow(thresholded,cmap="gray")
This produces output like this -
Then I searched for the contours in the images and tried to find only the most circular one in the image through this code
contours, hierarchy = cv2.findContours(thresholded, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
image_copy = np.zeros_like(image) # create a new emtpy image
for cnt in contours:
peri = cv2.arcLength(cnt, True)
approx = cv2.approxPolyDP(cnt, 0.04 * peri, True)
(x, y, w, h) = cv2.boundingRect(cnt)
ar = w / float(h)
if w*h > 20 and 0.9 < ar < 1.1: # filtering condition
cv2.drawContours(image, [cnt], 0, 255, -1)
While this produces great results in some cases where the eyes are in front facing direction but in other cases(like this one) it completely fails. I have tried many other things like "hough transform, different morphs" but I'm not able to tackle this problem.
The images are of only eyes and not the whole face else dlibs face detection would've worked
The cases where this code works is
Thanks for taking time and helping me out.

Adding some blurring, erosion and dilation may help. Eroding will remove very small features, like the noise around the eyelashes, and dilation will bring any surviving points back up to size. By tweaking the erosion and dilation sizes, you should be able to get rid of most of the noise and make that center pupil look much better.
Here's an example of how I would do this:
gray = cv2.cvtColor(frame_in, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
thresh = cv2.threshold(blurred, 30, 255, cv2.THRESH_BINARY)[1]
erosion_size = 10
dilate_size = 8
thresh = cv2.erode(thresh, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (erosion_size, erosion_size)))
thresh = cv2.dilate(thresh, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (dilate_size, dilate_size)))

Related

Python pytesseract extract number from various images

I have various type of images like those:
As you see, they are all kinda similar, however I do not manage to properly extract the number on them.
So far my code consists in the following:
lower = np.array([250,200,90], dtype="uint8")
upper = np.array([255,204,99], dtype="uint8")
mask = cv2.inRange(img, lower, upper)
res = cv2.bitwise_and(img, img, mask=mask)
data = image_to_string(res, lang="eng", config='--psm 13 --oem 3 -c tessedit_char_whitelist=0123456789')
numbers = int(''.join(re.findall(r'\d+', data)))
I tried twearking the psm parameter 6,8 and 13 they all work for some of those examples, but none on all, and I have no idea how I could circumvent my problem.
Another solution proposed is:
gry = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
(h, w) = gry.shape[:2]
gry = cv2.resize(gry, (w*2, h*2))
erd = cv2.erode(gry, None, iterations=1)
thr = cv2.threshold(erd, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]
bnt = cv2.bitwise_not(thr)
However, on the first picture, bnt gives:
And then pytesseract sees 460..
Any idea please?
My approach:
Upsample
Erosion
Simple-thresholding
Bitwise-not
Upsampling is required for accurate recognition. Resizing two-times will make the image readable.
Erosion operation is a morphological operation helps to remove the boundary of the pixels. Erosion remove the strokes on the digit, make it easier to detect.
Thresholding (Binary and Inverse Binary) helps to reveal the features.
Bitwise-not is an arithmetic operation highly useful for extracting part of the image.
You can learn more methods simple reading from Improving the quality of the output
Erosion
Threshold
Bitwise-not
Update
The first image is easy to read, since it is not requiring any pre-processing technique. Please read How to Improve Quality of Tesseract
Result:
1460
720
3250
3146
2681
1470
Code:
import cv2
import pytesseract
img_lst = ["oqWjd.png", "YZDt1.png", "MUShJ.png", "kbK4m.png", "POIK2.png", "4W3R4.png"]
for i, img_nm in enumerate(img_lst):
img = cv2.imread(img_nm)
gry = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
(h, w) = gry.shape[:2]
if i == 0:
thr = gry
else:
gry = cv2.resize(gry, (w * 2, h * 2))
erd = cv2.erode(gry, None, iterations=1)
if i == len(img_lst)-1:
thr = cv2.threshold(erd, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
else:
thr = cv2.threshold(erd, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]
bnt = cv2.bitwise_not(thr)
txt = pytesseract.image_to_string(bnt, config="--psm 6 digits")
print("".join([t for t in txt if t.isalnum()]))
cv2.imshow("bnt", bnt)
cv2.waitKey(0)
If you want to display comma in the result, change print("".join([t for t in txt if t.isalnum()])) line to print(txt).
Not that on the fourth image the threshold method changed from binary to inverse-binary. Binary thresholding is not working accurately on all images. Therefore you need to change.

Crop the rectangular paper from the image

from the discussion : Crop exactly document paper from image
I'm trying to get the white paper from the image and I'm using the following code which not cropping exactly rectangular.
def crop_image(image):
image = cv2.imread(image)
# convert to grayscale image
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# threshold
thresh = cv2.threshold(gray, 190, 255, cv2.THRESH_BINARY)[1]
# apply morphology
kernel = np.ones((7, 7), np.uint8)
morph = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
kernel = np.ones((9, 9), np.uint8)
morph = cv2.morphologyEx(morph, cv2.MORPH_ERODE, kernel)
# Get Largest contour
contours = cv2.findContours(morph, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
contours = contours[0] if len(contours) == 2 else contours[1]
area_thresh = 0
for cnt in contours:
area = cv2.contourArea(cnt)
if area > area_thresh:
area_thresh = area
big_contour = cnt
# get bounding box
x, y, w, h = cv2.boundingRect(big_contour)
# draw filled contour on black background
mask = np.zeros_like(gray)
mask = cv2.merge([mask, mask, mask])
cv2.drawContours(mask, [big_contour], -1, (255, 255, 255), cv2.FILLED)
# apply mask to input
result = image.copy()
result = cv2.bitwise_and(result, mask)
# crop result
img_result = result[y:y+h, x:x+w]
filename = generate_filename()
cv2.imwrite(filename, img_result)
logger.info('Successfully saved cropped file : %s' % filename)
return img_result, filename
I'm able to get the desired result but not the rectangular image.
Here I'm attaching and here is what I'm getting after cropping image .
I want a rectangular image of the paper.
Please help me with this.
Thanks in advance
The first problem I can see is that the threshold value is not low enough so the bottom part of the paper is not correctly capture (it's too dark to be captured by the threshold)
The second problem as far I can understand is being able to fit the square to the image. What you need to do is wrapping perspective.
To do that you can find more information in this amazing post of PyImageSearch

How to apply threshold counters to only specified/masking region in the image using opencv

I am new in OpenCV and stuck at a point actually, I study lots of QA but all are unable or I can say not fully satisfied my requirement.
Suppose I have an image and I want to find counters for objectifying specified area only.
It is easy to counter a complete image but I want to counters only masking area.
For masking purpose I have x,y coordinates with me.
So my question is how to counter only masking area in the image using OpenCV
This is the one solution matching with my question->Stackoverflow suggestion -:
But it does not solving my problem completely.
The given solution in this solution is quite complicated and not solving my problem. as I want my counters ROI to be show in the same image using mask coordinates(xmin, ymin, xmax, ymax).
Means i first drawing reactangle mask into the image and i want to draw counters boundary only into that rectangle mask in the same image.
This is my Demo.py file:-
import cv2
img = cv2.imread("F:/mycar.jpg",1)
crop_img = cv2.rectangle(img, (631,181), (698,386), (0,255,0), 3)
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
retval, thresh = cv2.threshold(gray_img, 127, 255, 0)
img_contours, _ = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(img, img_contours, -1, (255, 0, 0))
cv2.imshow("dflj",img)
cv2.waitKey(0)
cv2.destroyAllWindows()
output
From this image, you can see that the complete image is countering.
But I want to just counter only that green-rectangle part of the image.
Means that blue counter boundary lines is visible inside whole image but I want to show only it inside of green-rectangle box.
I have rectangle coordinates with me (eg. minx-631, miny-181, maxx-698, maxy-386).
Please help.
You can apply functions to ROIs using Numpy indexing as explained in this tutoral:
top,left = 338,282
h,w = 216,106
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
crop_img = cv2.rectangle(img, (left,top), (left+w,top+h), (0,255,0), 3)
retval, thresh = cv2.threshold(gray_img, 127, 255, 0)
img_contours, _ = cv2.findContours(thresh[top:top+h,left:left+w], cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(img[top:top+h,left:left+w], img_contours, -1, (255, 0, 0))
Please note that with Numpy indexing y comes first then x, as opposed to point coordinates with (x,y).
With this image we get:

Remove lines from this image

Trying to remove those lines after stitching multiple images but these lines are just not going away
Tried morphological Transformations of opencv nothing worked. Any help would be great.
You can use Gaussian blurring for this. However, as you want to remove vertical lines use a m x 1 kernel as it would affect only the vertical lines and will not blur horizontally.
img = cv2.imread('vertical_noise.jpg')
img = cv2.GaussianBlur(img, (11, 1), 0)
cv2.imshow('image', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
You can increase the kernel size to completely remove the lines but the image will also get blurrier.
EDIT
Sorry for the extremely late edit. If you don't want to blur then I don't know how to remove the lines in the car. However, the lines outside the car can be removed. For this create a threshold using cv2.threshold with a high thresh value and then find contours on it. Then using cv2.drawContours the blank spaces in the threshold image can be filled which can be treated as a mask. Using cv2.bitwise_and obtain two images using that as a mask with a blank image and using its inverse as a mask along with the original image. Then combine then cv2.bitwise_or.
import cv2
import numpy as np
img = cv2.imread('vertical_noise.jpg')
height, width, ch = img.shape
blur = cv2.GaussianBlur(img, (11, 1), 0)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_, thresh = cv2.threshold(gray, 200, 255, cv2.THRESH_BINARY)
_, cnts, _ = cv2.findContours(thresh, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)
cv2.drawContours(thresh, cnts, -1, 0, 3)
res = cv2.bitwise_and(img, img, mask=cv2.bitwise_not(thresh))
blank = np.ones((height, width, ch), np.uint8)*255
inv_res = cv2.bitwise_and(blank, blank, mask=thresh)
res = cv2.bitwise_or(res, inv_res)
cv2.imshow('image', res)
cv2.waitKey(0)
cv2.destroyAllWindows()
Now if you want you can blur it a little for the lines inside the car.

How to find the document edges in various coloured backgrounds using opencv python? [Document Scanning in various backgrounds]

I am currently have a document that needs to be smart scanned.
For that, I need to find proper contours of the document in any background so that I can do a warped perspective projection and detection with that image.
The main issue faced while doing this is that the document edge detects any kind of background.
I have tried to use the function HoughLineP and tried to find contours on the grayscale blurred image passed through canny edge detection until now.
MORPH = 9
CANNY = 84
HOUGH = 25
IM_HEIGHT, IM_WIDTH, _ = rescaled_image.shape
# convert the image to grayscale and blur it slightly
gray = cv2.cvtColor(rescaled_image, cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray, (7,7), 0)
#dilate helps to remove potential holes between edge segments
kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(MORPH,MORPH))
dilated = cv2.dilate(gray, kernel)
# find edges and mark them in the output map using the Canny algorithm
edged = cv2.Canny(dilated, 0, CANNY)
test_corners = self.get_corners(edged)
approx_contours = []
(_, cnts, hierarchy) = cv2.findContours(edged.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
cnts = sorted(cnts, key=cv2.contourArea, reverse=True)[:5]
# loop over the contours
for c in cnts:
# approximate the contour
approx = cv2.approxPolyDP(c, 80, True)
if self.is_valid_contour(approx, IM_WIDTH, IM_HEIGHT):
approx_contours.append(approx)
break
How to find a proper bounding box around the document via OpenCV code.
Any help will be much appreciated.
(The document is taken from the camera in any angle and any coloured background.)
Following code might help you to detect/segment the page in the image...
import cv2
import matplotlib.pyplot as plt
import numpy as np
image = cv2.imread('test_p.jpg')
image = cv2.imread('test_p.jpg')
print(image.shape)
ori = image.copy()
image = cv2.resize(image, (image.shape[1]//10,image.shape[0]//10))
Resized the image to make the operations more faster so that we can work on realtime..
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray, (11,11), 0)
edged = cv2.Canny(gray, 75, 200)
print("STEP 1: Edge Detection")
plt.imshow(edged)
plt.show()
cnts = cv2.findContours(edged.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
cnts = sorted(cnts[1], key = cv2.contourArea, reverse = True)[:5]
Here we will consider only first 5 contours from the sorted list based on area
Here the size of the gaussian blur is bit sensitive, so chose it accordingly based on the image size.
After the above operations image may look like..
for c in cnts:
### Approximating the contour
#Calculates a contour perimeter or a curve length
peri = cv2.arcLength(c, True)
approx = cv2.approxPolyDP(c, 0.01 * peri, True)
# if our approximated contour has four points, then we
# can assume that we have found our screen
screenCnt = approx
if len(approx) == 4:
screenCnt = approx
break
# show the contour (outline)
print("STEP 2: Finding Boundary")
cv2.drawContours(image, [screenCnt], -1, (0, 255, 0), 2)
image_e = cv2.resize(image,(image.shape[1],image.shape[0]))
cv2.imwrite('image_edge.jpg',image_e)
plt.imshow(image_e)
plt.show()
Final Image may look like...
Rest of the things may be handled after getting the final image...
Code Reference :- Git Repository
I guess this answer would be helpful...
There is a similar problem which is called orthographic projection.
Orthographic approaches
Rather than doing, Gaussian blur+morphological operation to get the edge of the document, try to do orthographic projection first and then find contours via your method.
For fining proper bounding box, try some preset values or a reference letter after which an orthographic projection will allow you to compute the height and hence the dimensions of the bounding box.

Resources