I'm trying to calculate different areas within a picture using opencv's contourArea without much success. The picture I'm using as an example is the following one:
My objective is to calculate the table's free area (greyish) and the occupied area (orange objects), and so far managed to print the contours with the following code:
img = cv2.imread('table.jpg', 1)
b,g,r = cv2.split(img)
imgRGB = cv2.merge([r,g,b])
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
hsv_channels = cv2.split(hsv)
rows = img.shape[0]
cols = img.shape[1]
for i in range(0, rows):
for j in range(0, cols):
h = hsv_channels[1][i][j]
if h > 90 and h < 120:
hsv_channels[2][i][j] = 255
else:
hsv_channels[2][i][j] = 0
image, contours, hierarchy = cv2.findContours(hsv_channels[2],cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)
img1 = cv2.drawContours(imgRGB, contours, -1, (0,255,0), 3)
However, I'm facing two issues:
1- The code detects contours inside the circle.
2- Given the multiple contours I don't know if the area returned is the table's, the objects' or both.
Any suggestion?
Thanks a million.
Since you transforming to HSV colorspace have you thought about cv2.inRange()? After that you can find contours with cv2.findContours() and draw them out of the image, leaving only the gray area.
Example:
import cv2
import numpy as np
img = cv2.imread('tablest.jpg')
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
lower = np.array([0,0,50])
upper = np.array([160,255,255])
mask = cv2.inRange(hsv, lower, upper)
res = cv2.bitwise_and(hsv,hsv, mask= mask)
gray = cv2.cvtColor(res,cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
_, contours, hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)
for i in contours:
cnt = cv2.contourArea(i)
if cnt > 1000:
cv2.drawContours(img, [i], 0, (0,0,0), -1)
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
_, contours, hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)
cnt = max(contours, key=cv2.contourArea)
area = cv2.contourArea(cnt)
cv2.putText(img,'Gray area ='+str(area),(60,90), cv2.FONT_HERSHEY_COMPLEX, 0.5,(0,255,0),1,cv2.LINE_AA)
cv2.imshow('img', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
EDIT
For calculating percentage:
import cv2
import numpy as np
img = cv2.imread('tablest.jpg')
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
lower = np.array([0,0,50])
upper = np.array([160,255,255])
# Calculate whole area
h,w = img.shape[:2]
whole_area_mask = np.ones((h, w), np.uint8)
ret, thresh = cv2.threshold(whole_area_mask,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
_, contours, hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)
cnt = max(contours, key=cv2.contourArea)
whole_area = cv2.contourArea(cnt)
# Threshold the HSV image to get only blue colors
mask = cv2.inRange(hsv, lower, upper)
# Bitwise-AND mask and original image
res = cv2.bitwise_and(hsv,hsv, mask= mask)
gray = cv2.cvtColor(res,cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
_, contours, hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)
other_area = []
table_area = []
for i in contours:
cnt = cv2.contourArea(i)
M = cv2.moments(i)
cx = int(M['m10']/M['m00'])
if cnt > 1000:
cv2.drawContours(img, [i], 0, (0,0,0), -1)
if w-100 > cx > 100:
other_area.append(cnt)
else:
table_area.append(cnt)
# Percentage table/napkin/object 1/object 2
table_per = (100*(table_area[0]+table_area[1]))/whole_area
print('Table percentage: ', table_per)
napkin_per = (100*(whole_area-other_area[0]-other_area[1]-table_area[0]-table_area[1]))/whole_area
print('Napkin percentage: ', napkin_per)
first_object_per = (100*other_area[0])/whole_area
print('First object percentage: ', first_object_per)
second_object_per = (100*other_area[1])/whole_area
print('Second object percentage: ', second_object_per)
print('SUM: ', table_per+napkin_per+first_object_per+second_object_per)
cv2.imshow('img', img)
cv2.imwrite('tablest_res.png', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
Output:
Table percentage: 9.875440996472028
Napkin percentage: 58.93872849017208
First object percentage: 28.05565555475556
Second object percentage: 3.1301749586003313
SUM: 100.0
Related
How do I run a loop to get the contour and pixles for 8 objects in an image, rather than just finding max contour and pixels of one object in an image.
import cv2
import numpy as np
img = cv2.imread('C:\\Users\\marnes\\Downloads\\25%.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# get shape of largest contour and count pixels
edges = cv2.Canny(image=img, threshold1=100, threshold2=200)
contours, hierarchy = cv2.findContours(edges, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
max_contour = max(contours, key=lambda c: cv2.contourArea(c))
mask = np.zeros_like(gray)
cv2.drawContours(mask, [max_contour], 0, 255, -1)
pct_100 = cv2.countNonZero(mask)
cv2.imshow("mask", mask)
# get dark area and count pixels
ret, thresh = cv2.threshold(gray, 100, 255, cv2.THRESH_BINARY_INV)
thresh = cv2.bitwise_and(thresh, mask)
pct_dark = cv2.countNonZero(thresh)
cv2.imshow("dark", thresh)
print(f"mask = {pct_100}, dark = {pct_dark}, %dark = {pct_dark / pct_100 * 100}")
first_operator = 100
second_operator = pct_dark / pct_100 * 100
output1 = first_operator - second_operator
parameter = 'starch breakdown'
print([parameter] + [output1])
cv2.waitKey(0)
cv2.destroyAllWindows()
I have an image with a white background and some colored blocks. I have created multiple filters to find the colored blocks and get the contours for another purpose and I am able to draw the contours around the colored blocks.
These blocks are connected by some black lines which I would like to get the contours of. I was using the original contours to also get the lines but I was advised not to do that and instead remove the colored blocks from the image so that I would only remain with the black lines in the image making it easier to contour.
I have created a mask that would draw over the contoured block but when I display the final image the contoured blocks are black instead of white.
Is there any way to make the blocks white similar to the background so that I could remain with only the black lines?
From the images, you can see that the mask covers the image and the black line remains. However, I can't figure out how to make the blocks white instead of black so that only the line remains.
from ctypes import sizeof
from doctest import master
from cv2 import approxPolyDP, contourArea, cvtColor, inRange
import numpy as np
import cv2
kernel = (5, 5)
srcImg = cv2.imread('BluePurpleConnected.png', cv2.IMREAD_COLOR)
blur = cv2.GaussianBlur(srcImg, kernel, 0)
hsv = cv2.cvtColor(blur, cv2.COLOR_BGR2HSV)
grayImg = cv2.cvtColor(srcImg, cv2.COLOR_BGR2GRAY)
rows = int(grayImg.shape[0]/2)
cols = int(grayImg.shape[1]/2)
ker = np.ones((0, 0), 'uint8')
graySize = grayImg.shape
sig = 0.33
cPix = 200
bPix = 255
lPurp = np.array([130, 20, 10])
uPurp = np.array([145, 255, 255])
lBlue = np.array([94, 80, 2])
uBlue = np.array([126, 255, 255])
colArray = np.array([lPurp, uPurp, lBlue, uBlue])
masterStruc = []
maskArray = []
mask = np.ones(srcImg.shape[:2], dtype="uint8") * 255
x = 0
while x <= 1:
imgMask = cv2.inRange(hsv, colArray[2*x], colArray[2*x+1])
maskArray.append(imgMask)
v = np.median(imgMask)
lower = int(max(0, (1.0 - sig) * v))
upper = int(min(255, (1.0 + sig) * v))
bwImg = cv2.Canny(imgMask, lower, upper)
nbwImg = cv2.dilate(bwImg, kernel, iterations=1)
cImg = nbwImg
ret, threshold = cv2.threshold(cImg, cPix, bPix, cv2.THRESH_BINARY)
contours, hierarchy = cv2.findContours(threshold, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnt = 0
for cnt in contours:
curve = approxPolyDP(cnt, 0.02 * cv2.arcLength(cnt, True), True)
vert = len(curve)
area = cv2.contourArea(curve)
if(area > 1000):
if(vert == 4):
masterStruc.append([x, curve, area])
cv2.drawContours(mask, [curve], -1, 0, -1)
x = x + 1
srcImg = cv2.bitwise_and(srcImg, srcImg, mask=mask)
cv2.imshow('Mask', mask)
cv2.imshow('Image', srcImg)
if cv2.waitKey(0) & 0xFF == ord('q'):
cv2.destroyAllWindows()
I try to remove background and white text from a photo 1 like below but I can only remove like these images2 3. They still have white text inside the circle.
I've used the following code.
Any help from everyone is greatly appreciated by me.
img = cv2.imread('sample.jpg')
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
#Crop image
croped_img = img[51:403,102:454]
#plt.imshow(croped_img)
radius = 176
cx, cy = radius, radius # The center of circle
x,y = np.ogrid[-radius: radius, -radius: radius]
index = x**2 + y**2 > radius**2
croped_img[cy-radius:cy+radius, cx-radius:cx+radius][index] = 0
plt.imshow(croped_img)
croped_img=cv2.cvtColor(croped_img, cv2.COLOR_BGR2RGB)
cv2.imwrite('croped_circle_2.jpg', croped_img)
One approach is to create a mask of the text and use that to do inpainting. In Python/OpenCV, there are two forms of inpainting: Telea and Navier-Stokes. Both produce about the same results.
Input:
import cv2
import numpy as np
# read input
img = cv2.imread('circle_text.png')
# convert to gray
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# threshold and invert
thresh = cv2.threshold(gray, 155, 255, cv2.THRESH_BINARY)[1]
# apply morphology close
kernel = np.ones((3,3), np.uint8)
thresh = cv2.morphologyEx(thresh, cv2.MORPH_DILATE, kernel)
# get contours and filter to keep only small regions
mask = np.zeros_like(gray, dtype=np.uint8)
cntrs = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cntrs = cntrs[0] if len(cntrs) == 2 else cntrs[1]
for c in cntrs:
area = cv2.contourArea(c)
if area < 1000:
cv2.drawContours(mask,[c],0,255,-1)
# do inpainting
result1 = cv2.inpaint(img,mask,3,cv2.INPAINT_TELEA)
result2 = cv2.inpaint(img,mask,3,cv2.INPAINT_NS)
# save results
cv2.imwrite('circle_text_threshold.png', thresh)
cv2.imwrite('circle_text_mask.png', mask)
cv2.imwrite('circle_text_inpainted_telea.png', result1)
cv2.imwrite('circle_text_inpainted_ns.png', result2)
# show results
cv2.imshow('thresh',thresh)
cv2.imshow('mask',mask)
cv2.imshow('result1',result1)
cv2.imshow('result2',result2)
cv2.waitKey(0)
cv2.destroyAllWindows()
Threshold image:
Mask image:
Telea Inpainting:
Navier-Stokes Inpainting:
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.
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