Duplicates in findContour OpenCV - python

I am new to OpenCV and Python. I have been able to findContours and draw a boundingRect on the contours and saving them as a new image. I encountered a problem, it seems that the program is saving 2 images because there is a duplicate in the found contours. I have been stuck in this part, how can I avoid having duplicates?
Here is the code:
img = cv2.imread('2.bmp')
img_2 = cv2.imread('2.bmp')
input_img = cv2.addWeighted(img, 0.55, img_2, 0.6, 0)
retval, threshold = cv2.threshold(input_img, 158, 255, cv2.THRESH_BINARY)
threshold = cv2.cvtColor(threshold, cv2.COLOR_BGR2GRAY)
retval2, threshold2 = cv2.threshold(threshold, 0, 255, cv2.THRESH_BINARY|cv2.THRESH_OTSU)
blur2 = cv2.medianBlur(threshold2,5)
canny = cv2.Canny(blur2, 100,200)
im2, contours, hierarchy = cv2.findContours(canny,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
maxsize = 0
best = 0
count = 0
limit_area = 1000
number_name = 0
x = 0
y = 0
w = 0
h = 0
nuclei = []
for cnt in contours:
if cv2.contourArea(cnt) >= limit_area:
print(cv2.contourArea(cnt))
nuclei.append(cnt)
print(count)
x, y, w, h = cv2.boundingRect(cnt)
roi = blur2[y:y+h, x:x+w]
outfile = '%d.jpg' % number_name
cv2.imwrite(outfile, roi)
number_name += 1
cnt+=1
count += 1
cv2.drawContours(blur2, nuclei, -1, (0,0,255), 2)
cv2.rectangle(blur2, (x, y), (x+w, y+h), (0,255,0), 7)
Here is the image I have been trying this program with:

I think this change should do it for you.
im2, contours, hierarchy = cv2.findContours(canny,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
Also, you should do thresholding on a gray image, rather than a color one. The following lines of code will help you get a better result:
img = cv2.imread('nuclei.png',cv2.IMREAD_GRAYSCALE)
retval, threshold = cv2.threshold(255-img, 100,255, cv2.THRESH_BINARY)

Related

How to find the length of the contour selected with area in python?

I am new to OpenCV. I have two questions to ask.
I am trying to print the no of contours available after applying the area. I am getting the correct output in imshow but not in the print statement. I understood that print(len(contours)) gives the total number of contours but I need the no of contours in the given area > 400. You can check the below python code for more details. Please help me with this.
Is it possible to change the threshold value above 255, whenever I change it to above 255, I am getting the black image even if I increase the max value?
Thank you!
import cv2 as cv
import numpy as np
im_color = cv.imread("D:\python_project\Focus_detection_1/_00005_cells.png", cv.IMREAD_COLOR)
im_gray = cv.cvtColor(im_color, cv.COLOR_BGR2GRAY)
_, thresh = cv.threshold(im_gray, thresh=254, maxval=255, type=cv.THRESH_BINARY)
mask = cv.cvtColor(thresh, cv.COLOR_GRAY2BGR)
im_thresh_color = cv.bitwise_and(im_color,mask)
contours, hierarchy = cv.findContours(thresh, cv.RETR_TREE,cv.CHAIN_APPROX_SIMPLE)
cv.drawContours(im_thresh_color,contours,-1,(0,0,255),2)
for c in contours:
area = cv.contourArea(c)
if area > 400:
x,y,w,h = cv.boundingRect(c)
im = cv.rectangle(im_thresh_color, (x,y), (x+w, y+h), (255,0,0), 2)
cv.drawContours(im_thresh_color,contours, -1,(0,0,255),2) #-1 is to draw all the contour, 0 is the 1st contour and so on
text = cv.putText(im, 'Focused', (x, y), cv.FONT_HERSHEY_SIMPLEX, 0.5, (36, 255, 12), 2)
no_of_images = len(contours)
print("images:", no_of_images)
while True:
cv.imshow("original image", im_color)
cv.imshow("Thresh color with contour", im_thresh_color)
#print("n:",len(im_thresh_color))
if cv.waitKey(1) == ord("n"):
break
cv.destroyAllWindows()
Here is the updated code for your first question: This code counts how many contours are in the area and then prints the number of contours in that area
import cv2 as cv
import numpy as np
im_color = cv.imread("D:\python_project\Focus_detection_1/_00005_cells.png", cv.IMREAD_COLOR)
im_gray = cv.cvtColor(im_color, cv.COLOR_BGR2GRAY)
_, thresh = cv.threshold(im_gray, thresh=254, maxval=255, type=cv.THRESH_BINARY)
mask = cv.cvtColor(thresh, cv.COLOR_GRAY2BGR)
im_thresh_color = cv.bitwise_and(im_color, mask)
contours, hierarchy = cv.findContours(thresh, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
cv.drawContours(im_thresh_color, contours, -1, (0, 0, 255), 2)
counter = 0
for c in contours:
area = cv.contourArea(c)
if area > 400:
x, y, w, h = cv.boundingRect(c)
im = cv.rectangle(im_thresh_color, (x, y), (x+w, y+h), (255, 0, 0), 2)
# -1 is to draw all the contour, 0 is the 1st contour and so on
cv.drawContours(im_thresh_color, contours, -1, (0, 0, 255), 2)
text = cv.putText(im, 'Focused', (x, y), cv.FONT_HERSHEY_SIMPLEX, 0.5, (36, 255, 12), 2)
counter += 1
print("images:", counter)
while True:
cv.imshow("original image", im_color)
cv.imshow("Thresh color with contour", im_thresh_color)
# print("n:",len(im_thresh_color))
if cv.waitKey(1) == ord("n"):
break
cv.destroyAllWindows()
Answer question 2: no it is not possible to change it over a value of 255 bc the range of the r,g,b values is from 0 to 255.

Unable to segment handwritten characters

I am trying to extract handwritten numbers and alphabet from an image, for that i followed this stackoverflow link. It is working fine for most of the images where letter is written using marker but when i am using image where data is written using Pen it is failing miserably. Need some help to fix this.
Below is my code:
import cv2
import imutils
from imutils import contours
# Load image, grayscale, Otsu's threshold
image = cv2.imread('xxx/pic_crop_7.png')
image = imutils.resize(image, width=350)
img=image.copy()
# Remove border
kernel_vertical = cv2.getStructuringElement(cv2.MORPH_RECT, (1,50))
temp1 = 255 - cv2.morphologyEx(image, cv2.MORPH_CLOSE, kernel_vertical)
horizontal_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (50,1))
temp2 = 255 - cv2.morphologyEx(image, cv2.MORPH_CLOSE, horizontal_kernel)
temp3 = cv2.add(temp1, temp2)
result = cv2.add(temp3, image)
# Convert to grayscale and Otsu's threshold
gray = cv2.cvtColor(result, cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray,(5,5),0)
_,thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_OTSU | cv2.THRESH_BINARY_INV)
# thresh=cv2.dilate(thresh,None,iterations=1)
# Find contours and filter using contour area
cnts = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[0]
MIN_AREA=45
digit_contours = []
for c in cnts:
if cv2.contourArea(c)>MIN_AREA:
x,y,w,h = cv2.boundingRect(c)
cv2.rectangle(img, (x, y), (x + w, y + h), (36,255,12), 2)
digit_contours.append(c)
# cv2.imwrite("C:/Samples/Dataset/ocr/segmented" + str(i) + ".png", image[y:y+h,x:x+w])
sorted_digit_contours = contours.sort_contours(digit_contours, method='left-to-right')[0]
contour_number = 0
for c in sorted_digit_contours:
x,y,w,h = cv2.boundingRect(c)
ROI = image[y:y+h, x:x+w]
cv2.imwrite('xxx/segment_{}.png'.format(contour_number), ROI)
contour_number += 1
cv2.imshow('thresh', thresh)
cv2.imshow('img', img)
cv2.waitKey()
It is correctly able to extract the numbers when written using marker.
Below is an example:
Original Image
Correctly extracting charachters
Image where it fails to read.
Original Image
Incorrectly Extracting
In this case, you only need to adjust your parameter.
Because there is no vertical line in your handwritten characters' background, so I decided to delete them.
# Remove border
horizontal_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (50,1))
temp2 = 255 - cv2.morphologyEx(image, cv2.MORPH_CLOSE, horizontal_kernel)
result = cv2.add(temp2, image)
And it works.
The solution that CodingPeter has given is perfectly fine, except that it may not be generic apropos the two test images you have posted. So, here's my take on it that might work on both of your test images, albeit with a little lesser accuracy.
import numpy as np
import cv2
import matplotlib.pyplot as plt
plt.rcParams['figure.figsize'] = (20, 20)
plt.rcParams["image.cmap"] = 'gray'
img_rgb = cv2.imread('path/to/your/image.jpg')
img = cv2.cvtColor(img_rgb, cv2.COLOR_BGR2GRAY)
th = cv2.adaptiveThreshold(img,255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,cv2.THRESH_BINARY_INV,11,2)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (15,1))
horiz = cv2.morphologyEx(th, cv2.MORPH_OPEN, kernel, iterations=3)
ctrs, _ = cv2.findContours(horiz,cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
for ctr in ctrs:
x,y,w,h = cv2.boundingRect(ctr)
if w < 20:
cv2.drawContours(horiz, [ctr], 0, 0, cv2.FILLED)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1,10))
vert = cv2.morphologyEx(th, cv2.MORPH_OPEN, kernel, iterations=3)
ctrs, _ = cv2.findContours(vert,cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
for ctr in ctrs:
x,y,w,h = cv2.boundingRect(ctr)
if h < 25:
cv2.drawContours(vert, [ctr], 0, 0, cv2.FILLED)
th = th - (horiz | vert)
ctrs, _ = cv2.findContours(th,cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
min_ctr_area = 400 # Min character bounding box area
for ctr in ctrs:
x, y, w, h = cv2.boundingRect(ctr)
# Filter contours based on size
if w * h > min_ctr_area and \
w < 100 and h < 100:
cv2.rectangle(img_rgb, (x, y), (x+w, y+h), (0, 255, 0), 1)
plt.imshow(img_rgb)
Of course some of the parameters here are hard-coded for filtering, which compare the contour height and width to ascertain whether it is a part of a line or maybe a character. With different images you may have to smartly change these values.

OpenCV contour detection

I have been trying to detect contours using OpenCV. I am trying to detect the nucleus of white blood cells. I tested it on other images of mine and it turned out to be okay except for images where the nuclei are too far away from each other. This is the result from the program I made:
At the bottom part, the nucleus was not detected as one but they are detected as two because they are not conjoined or sticking together. How can I make the program to detect it as only one cell?
Here is my code:
import cv2
import numpy as np
limit_area = 1000
x = 0
y = 0
w = 0
h = 0
nuclei = []
count = 0
number_name = 1
img1 = cv2.imread('7.bmp')
img = cv2.add(img1, 0.70)
img_3 = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
mask1 = cv2.inRange(img_3, (90,140,0), (255,255,255))
mask2 = cv2.inRange(img_3, (90,90,0), (255,255,255))
mask1 = cv2.equalizeHist(mask1)
mask2 = cv2.equalizeHist(mask2)
mask = mask1 + mask2
kernel = np.ones((1,4),np.uint8)
mask = cv2.dilate(mask,kernel,iterations = 1)
kernel_close = np.ones((3,3),np.uint8)
mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel_close)
blur2 = cv2.medianBlur(mask,7)
canny = cv2.Canny(blur2, 100,200)
im2, contours, hierarchy = cv2.findContours(canny,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
for cnt in contours:
if cv2.contourArea(cnt) >= limit_area:
nuclei.append(cnt)
print(cv2.contourArea(cnt))
x, y, w, h = cv2.boundingRect(cnt)
roi = blur2[y:y+h, x:x+w]
outfile = '%d.jpg' % number_name
image_roi = cv2.resize(roi, (128,128), interpolation=cv2.INTER_AREA)
image_roi = cv2.medianBlur(image_roi, 5)
(T, thresh) = cv2.threshold(image_roi, 10, 255, cv2.THRESH_BINARY)
_, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = [i for i in contours if cv2.contourArea(i) <= 5000]
cv2.fillPoly(thresh, contours, color=(0,0,0))
image_roi = thresh
cv2.imshow(outfile, image_roi)
cv2.rectangle(img, (x, y), (x+w, y+h), (0,255,0), 7)
number_name += 1
count += 1
cv2.imshow('img', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
Here is the Original Image:
One simple way can be to merge closely detected regions. There is a concept called Intersection over Union in Image Localization, in which two bounding boxes are merged if their IoU score is greater than a certain threshold. A psuedo cade will be like
xi1 = max(box1[0], box2[0])
yi1 = max(box1[1], box2[1])
xi2 = min(box1[2], box2[2])
yi2 = min(box1[3], box2[3])
inter_area = max((xi2 - xi1), 0) * max((yi2 - yi1), 0)
box1_area = (box1[2] - box1[0]) * (box1[3] - box1[1])
box2_area = (box2[2] - box2[0]) * (box2[3] - box2[1])
union_area = box1_area + box2_area - inter_area
iou = inter_area/union_area
Another approach you can try is the Flood Fill Algorithm, I think it should work fine as the nucleus is one entity after all (fully connected). Try this before dialation which probably is the reason your contours are breaking into two.

cv2.drawContours() - unfill circles inside characters (Python, OpenCV)

As suggested by #Silencer, I used the code he posted here to draw contours around the numbers in my image.
At some point, working with numbers like 0,6,8,9 I saw that their inside contours (the circles) are being filled as well.
How can I prevent this ? Is there a min/max area of action to set for cv2.drawContours() so I can exclude the inner area ?
I tried to pass cv2.RETR_EXTERNAL but with this parameter only the whole external area is considered.
The code is this (again, thanks Silencer. Was searching for this for months..):
import numpy as np
import cv2
im = cv2.imread('imgs\\2.png')
imgray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(imgray, 127, 255, 0)
image, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
#contours.sort(key=lambda x: int(x.split('.')[0]))
for i, cnts in enumerate(contours):
## this contour is a 3D numpy array
cnt = contours[i]
res = cv2.drawContours(im, [cnt], 0, (255, 0, 0), 1)
cv2.imwrite("contours.png", res)
'''
## Method 1: crop the region
x,y,w,h = cv2.boundingRect(cnt)
croped = res[y:y+h, x:x+w]
cv2.imwrite("cnts\\croped{}.png".format(i), croped)
'''
## Method 2: draw on blank
# get the 0-indexed coords
offset = cnt.min(axis=0)
cnt = cnt - cnt.min(axis=0)
max_xy = cnt.max(axis=0) + 1
w, h = max_xy[0][0], max_xy[0][1]
# draw on blank
canvas = np.ones((h, w, 3), np.uint8) * 255
cv2.drawContours(canvas, [cnt], -1, (0, 0, 0), -1)
#if h > 15 and w < 60:
cv2.imwrite("cnts\\canvas{}.png".format(i), canvas)
The main image on which I am working..
Thanks
UPDATE
I implemented Fiver answer below and this is the result:
import cv2
import numpy as np
img = cv2.imread('img.png')
img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
img_v = img_hsv[:, :, 2]
ret, thresh = cv2.threshold(~img_v, 127, 255, 0)
image, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
for i, c in enumerate(contours):
tmp_img = np.zeros(img_v.shape, dtype=np.uint8)
res = cv2.drawContours(tmp_img, [c], -1, 255, cv2.FILLED)
tmp_img = np.bitwise_and(tmp_img, ~img_v)
ret, inverted = cv2.threshold(tmp_img, 127, 255, cv2.THRESH_BINARY_INV)
cnt = contours[i]
x, y, w, h = cv2.boundingRect(cnt)
cropped = inverted[y:y + h, x:x + w]
cv2.imwrite("roi{}.png".format(i), cropped)
To draw the char without filled the closed inner regions:
find the contours on the threshed binary image with hierarchy.
find the outer contours that don't have inner objects (by flag hierarchyi).
for each outer contour:
3.1 fill it(maybe need check whether needed);
3.2 then iterate in it's inner children contours, fill then with other color(such as inversed color).
combine with the crop code, crop them.
maybe you need sort them, resplit them, normalize them.
maybe, now you can do ocr with the trained model.
FindContours, refill the inner closed regions.
Combine with this answer Copy shape to blank canvas (OpenCV, Python), do more steps, maybe you can get this or better:
The core code to refill the inner closed regions is as follow:
#!/usr/bin/python3
# 2018.01.14 09:48:15 CST
# 2018.01.15 17:56:32 CST
# 2018.01.15 20:52:42 CST
import numpy as np
import cv2
img = cv2.imread('img02.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
## Threshold
ret, threshed = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY_INV|cv2.THRESH_OTSU)
## FindContours
cnts, hiers = cv2.findContours(threshed, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)[-2:]
canvas = np.zeros_like(img)
n = len(cnts)
hiers = hiers[0]
for i in range(n):
if hiers[i][3] != -1:
## If is inside, the continue
continue
## draw
cv2.drawContours(canvas, cnts, i, (0,255,0), -1, cv2.LINE_AA)
## Find all inner contours and draw
ch = hiers[i][2]
while ch!=-1:
print(" {:02} {}".format(ch, hiers[ch]))
cv2.drawContours(canvas, cnts, ch, (255,0,255), -1, cv2.LINE_AA)
ch = hiers[ch][0]
cv2.imwrite("001_res.png", canvas)
Run this code with this image:
You will get:
Of course, this is for two hierarchies. I haven't test for more than two. You who need can do test by yourself.
Update:
Notice in different OpenCVs, the cv2.findContours return different values. To keep code executable, we can just get the last two returned values use: cnts, hiers = cv2.findContours(...)[-2:]
In OpenCV 3.4:
In OpenCV 4.0:
Since you already have a mask from your threshold step, you can also use it to bitwise_and against the drawn contour:
import cv2
import numpy as np
import matplotlib.pyplot as plt
img = cv2.imread('drawn_chars.png')
img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
img_v = img_hsv[:, :, 2]
ret, thresh = cv2.threshold(~img_v, 127, 255, 0)
image, contours, hierarchy = cv2.findContours(
thresh,
cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE
)
for c in contours:
tmp_img = np.zeros(img_v.shape, dtype=np.uint8)
cv2.drawContours(tmp_img, [c], -1, 255, cv2.FILLED)
tmp_img = np.bitwise_and(tmp_img, ~img_v)
plt.figure(figsize=(16, 2))
plt.imshow(tmp_img, cmap='gray')
I've inverted the image so the contours are white and I left out the cropping as you already solved that. Here is the result on one of the "O" characters:
Full code...
This will not sort the images.
import numpy as np
import cv2
im = cv2.imread('imgs\\1.png')
imgray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
## Threshold
ret, threshed = cv2.threshold(imgray, 127, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)
## FindContours
image, cnts, hiers = cv2.findContours(threshed, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
canvas = np.zeros_like(im)
n = len(cnts)
hiers = hiers[0]
for i, imgs in enumerate(cnts):
cnt = cnts[i]
res = cv2.drawContours(im, [cnt], 0, (0, 0, 0), -1)
x, y, w, h = cv2.boundingRect(cnt)
croped = res[y:y + h, x:x + w]
if h > 10:
cv2.imwrite("out\\croped{}.png".format(i), croped)
cv2.imshow('i', croped)
cv2.waitKey(0)
for i, value in enumerate(cnts):
## this contour is a 3D numpy array
cnt = cnts[i]
res = cv2.drawContours(im, [cnt], 0, (0, 0, 0), -1)
# cv2.imwrite("out\\contours{}.png".format(i), res)
## Find all inner contours and draw
ch = hiers[i][2]
while ch != -1:
print(" {:02} {}".format(ch, hiers[ch]))
res1 = cv2.drawContours(im, cnts, ch, (255, 255, 255), -1)
ch = hiers[ch][0]
x, y, w, h = cv2.boundingRect(cnt)
croped = res[y:y + h, x:x + w]
if h > 10:
cv2.imwrite("out\\croped{}.png".format(i), croped)
Any correction is accepted.
This will do definetively the job...
import cv2
import os
import numpy as np
img = cv2.imread("image.png")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
retval, thresholded = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)
medianFiltered = cv2.medianBlur(thresholded, 3)
_, contours, hierarchy = cv2.findContours(medianFiltered, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contour_list = []
for contour in contours:
area = cv2.contourArea(contour)
if area > 80:
contour_list.append(contour)
numbers = cv2.drawContours(img, contour_list, -1, (0, 0, 0), 2)
cv2.imshow('i', numbers)
cv2.waitKey(0)
sorted_ctrs = sorted(contours, key=lambda ctr: cv2.boundingRect(ctr)[0])
for i, cnts in enumerate(contours):
cnt = contours[i]
x, y, w, h = cv2.boundingRect(cnt)
croped = numbers[y:y + h, x:x + w]
h, w = croped.shape[:2]
print(h, w)
if h > 15:
cv2.imwrite("croped{}.png".format(i), croped)
This is conceptually similar to Fivers answer, just that bitwise_and occurs outside the for loop and perhaps is better in terms of performance. Source code is in C++ for those looking for C++ answer for this problem.
int thWin = 3;
int thOffset = 1;
cv::adaptiveThreshold(image, th, 255, cv::ADAPTIVE_THRESH_MEAN_C, cv::THRESH_BINARY_INV, thWin, thOffset);
int minMoveCharCtrArea = 140;
std::vector<std::vector<cv::Point> > contours;
std::vector<cv::Vec4i> hierarchy;
cv::findContours(th.clone(), contours, hierarchy, cv::RETR_LIST, cv::CHAIN_APPROX_SIMPLE);
cv::Mat filtImg = cv::Mat::zeros(img.rows, img.cols, CV_8UC1 );
for (int i = 0; i< contours.size(); ++i) {
int ctrArea = cv::contourArea(contours[i]);
if (ctrArea > minMoveCharCtrArea) {
cv::drawContours(filtImg, contours, i, 255, -1);
}
}
cv::bitwise_and(th, filtImg, filtImg);
Remember to clone the image (for python it should be copy) when passing source image argument to findContours, since findContours modifies the original image. I reckon later versions of opencv (perhaps opencv3 +) don't require cloning.

detect rectangle in image and crop

I have lots of scanned images of handwritten digit inside a rectangle(small one).
Please help me to crop each image containing digits and save them by giving the same name to each row.
import cv2
img = cv2.imread('Data\Scan_20170612_4.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
gray = cv2.bilateralFilter(gray, 11, 17, 17)
edged = cv2.Canny(gray, 30, 200)
_, contours, hierarchy = cv2.findContours(edged, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
i = 0
for c in contours:
peri = cv2.arcLength(c, True)
approx = cv2.approxPolyDP(c, 0.09 * peri, True)
if len(approx) == 4:
screenCnt = approx
cv2.drawContours(img, [screenCnt], -1, (0, 255, 0), 3)
cv2.imwrite('cropped\\' + str(i) + '_img.jpg', img)
i += 1
Here is My Version
import cv2
import numpy as np
fileName = ['9','8','7','6','5','4','3','2','1','0']
img = cv2.imread('Data\Scan_20170612_17.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
gray = cv2.bilateralFilter(gray, 11, 17, 17)
kernel = np.ones((5,5),np.uint8)
erosion = cv2.erode(gray,kernel,iterations = 2)
kernel = np.ones((4,4),np.uint8)
dilation = cv2.dilate(erosion,kernel,iterations = 2)
edged = cv2.Canny(dilation, 30, 200)
_, contours, hierarchy = cv2.findContours(edged, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
rects = [cv2.boundingRect(cnt) for cnt in contours]
rects = sorted(rects,key=lambda x:x[1],reverse=True)
i = -1
j = 1
y_old = 5000
x_old = 5000
for rect in rects:
x,y,w,h = rect
area = w * h
if area > 47000 and area < 70000:
if (y_old - y) > 200:
i += 1
y_old = y
if abs(x_old - x) > 300:
x_old = x
x,y,w,h = rect
out = img[y+10:y+h-10,x+10:x+w-10]
cv2.imwrite('cropped\\' + fileName[i] + '_' + str(j) + '.jpg', out)
j+=1
That's an easy thing if u try. Here's my output- (The image and its one small bit)
What i did?
Resized the image first because it was too big in my screen
Erode, Dilate to remove small dots and thicken the lines
Threshold the image
Flood fill, beginning at the right point
Invert the flood fill
Find contours and draw one at a time which are in range of approximately the
area on the rectangle. For my resized (500x500) image i put Area of
contour in range 500 to 2500 (trial and error anyway).
Find bounding rectangle and crop that mask from main image.
Then save that piece with proper name- which i didn't do.
Maybe, there's a simpler way, but i liked this. Not putting the code because
i made it all clumsy. Will put if u still need it.
Here's how the mask looks when you find contours each at a time
code:
import cv2;
import numpy as np;
# Run the code with the image name, keep pressing space bar
# Change the kernel, iterations, Contour Area, position accordingly
# These values work for your present image
img = cv2.imread("your_image.jpg", 0);
h, w = img.shape[:2]
kernel = np.ones((15,15),np.uint8)
e = cv2.erode(img,kernel,iterations = 2)
d = cv2.dilate(e,kernel,iterations = 1)
ret, th = cv2.threshold(d, 150, 255, cv2.THRESH_BINARY_INV)
mask = np.zeros((h+2, w+2), np.uint8)
cv2.floodFill(th, mask, (200,200), 255); # position = (200,200)
out = cv2.bitwise_not(th)
out= cv2.dilate(out,kernel,iterations = 3)
cnt, h = cv2.findContours(out,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
for i in range(len(cnt)):
area = cv2.contourArea(cnt[i])
if(area>10000 and area<100000):
mask = np.zeros_like(img)
cv2.drawContours(mask, cnt, i, 255, -1)
x,y,w,h = cv2.boundingRect(cnt[i])
crop= img[ y:h+y,x:w+x]
cv2.imshow("snip",crop )
if(cv2.waitKey(0))==27:break
cv2.destroyAllWindows()
_, contours, hierarchy = cv2.findContours(edged, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
you are using cv2.RETR_LIST to find contours in the image. For your image to get better output use cv2.RETR_EXTERNAL. Before using that first remove black border line from the image.
cv2.RETR_LIST gives you list of all contours for image
cv2.RETR_EXTERNAL gives you only external or outer contours, not internal contours
change line to
_, contours, hierarchy = cv2.findContours(edged, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
Contours Hierarchy

Categories

Resources