How to extract diagram from an image? - python

I've used Contour based approach but its detecting so many contours. How can I extract my ROI contour?
image = cv2.imread('ULTI.png')
original = image.copy()
cv2.imwrite("bg.png",bg)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (3, 3), 0)
canny = cv2.Canny(blurred, 120, 255, 1)
kernel = np.ones((5,5),np.uint8)
dilate = cv2.dilate(canny, kernel, iterations=1)
# Find contours
cnts = cv2.findContours(dilate, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
# Iterate thorugh contours and filter for ROI
image_number = 0
cnts = max(cnts, key = cv2.contourArea)
print("no ",len(cnts))
for c in cnts:
x,y,w,h = cv2.boundingRect(c)
cv2.rectangle(image, (x, y), (x + w, y + h), (36,255,12), 2)
ROI = original[y:y+h, x:x+w]
#cv2.imwrite("ROI_{}.png".format(image_number), ROI)
image_number += 1

You can get your ROI by specifying that you only want to use the contour that has the greatest area, that is, if your diagram will produce a contour with an area greater then the rest of the components in your image.
Here is an example:
import cv2
def preprocess(img):
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
img_blur = cv2.GaussianBlur(img_gray, (5, 5), 1)
img_canny = cv2.Canny(img_blur, 50, 50)
return img_canny
def get_roi(img, pad=3):
contours, hierarchy = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
max_area = 0
for cnt in contours:
peri = cv2.arcLength(cnt, True)
approx = cv2.approxPolyDP(cnt, 0.02 * peri, True)
x, y, w, h = cv2.boundingRect(approx)
rect_area = w * h
if rect_area > max_area:
max_area = rect_area
dim = x, y, w, h
if max_area:
x, y, w, h = dim
return x - pad, y - pad, w + pad * 2, h + pad * 2
img = cv2.imread("ULTI.png")
img_processed = preprocess(img)
x, y, w, h = get_roi(img_processed)
cv2.imshow("Image", img[y:y + h, x:x + w])
cv2.waitKey(0)
Output:
Explanation:
Import the necessary module(s):
import cv2
Define a function that will take in an image, and return a processed version of the image that will allow python to properly detect the necessary contours (you can tweak the values to further meet your needs):
def preprocess(img):
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
img_blur = cv2.GaussianBlur(img_gray, (5, 5), 1)
img_canny = cv2.Canny(img_blur, 50, 50)
return img_canny
Now, let's see how we can define a function that will retrieve the ROI of the image. First, define it so that a processed image array and a pad amount (optional) can be passed in as parameters:
def get_roi(img, pad=3):
Find the contours of the processed image, and define a variable to store the greatest area of the contours:
contours, hierarchy = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
max_area = 0
Use a for loop to loop through the contours, and find the area of the contour of each iteration of the loop:
for cnt in contours:
peri = cv2.arcLength(cnt, True)
approx = cv2.approxPolyDP(cnt, 0.02 * peri, True)
area = cv2.contourArea(approx)
Use an if statement to check if the area is greater than the defined variable that should store that greatest area. If the are of the contour of that iteration is greater than the variable, update the value of the variable to be equal to the new area. Also, save the contour of that iteration to a variable:
if area > max_area:
max_area = area
max_cnt = approx
After the for loop, if the max_area variable doesn't equal to 0, then a max_cnt has also been defined. Use the cv2.boundingRect to get the x, y, w and h properties:
if max_area:
x, y, w, h = cv2.boundingRect(max_cnt)
return x - pad, y - pad, w + pad * 2, h + pad * 2
Finally, after reading the image into a variable, you can utilize the 2 functions we defined, and display the resulting image:
img = cv2.imread("ULTI.png")
img_processed = preprocess(img)
x, y, w, h = get_roi(img_processed)
cv2.imshow("Image", img[y:y + h, x:x + w])
cv2.waitKey(0)
Note: The code likely will not work for all diagrams. But again, you can tweak the values in the preprocess function to meet your needs.

Related

How to extract the fixed size ROI for multiple images?

I have a folder of images. I would like to extract the object as fixed size ROI such as (100*100) and extract the location of that object. I use the following code. But it can only crop the contours into various rectangle shape. My target is to extract the object into equal size frame. I need like the following samples where the output patch are equal shape.
import cv2
import glob
def crop_brain_contour(image):
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # grayscale
cnts = cv2.findContours(gray, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
#ROI_number = 0
for c in cnts:
x,y,w,h = cv2.boundingRect(c)
ROI = image[y:y+h, x:x+w]
#cv2.imwrite('ROI_{}.png'.format(ROI_number), ROI)
#ROI_number += 1
return ROI
i=0
for img in glob.glob('./image_data/*.bmp'):
cv_img = cv2.imread(img)
img_crop = crop_brain_contour(cv_img)
#img_resize = cv2.resize(img_crop,(224,224))
cv2.imwrite("./extracted_data_1/image%04i.bmp" %i,img_crop)
i += 1
Make the following changes where you are calling the crop_brain_contour function:
desired_width, desired_height = (100, 100)
final_img = np.zeros((desired_height, desired_width, 3), dtype="uint8")
img_crop = crop_brain_contour(cv_img)
h,w = img_crop.shape[:2]
#Make sure h < desired_height and w < desired_width
x1 = int(desired_width/2 - w/2)
y1 = int(desired_height/2 - h/2)
x2 = x1 + w
y2 = y1 + h
final_img[y1:y2, x1:x2, :] = img_crop
# Write the final image
cv2.imwrite("./extracted_data_1/image%04i.bmp" %i,final_img)

How to detect overlapping or embedded rectangle in python OpenCv

So Im having trouble detecting rectangles that are embedded and overlapping as separate rectangles with python OpenCv
If given this image:
These are rectangles embedded
or this image:
enter image description here
how do I detect these rectangles as 2 separate rectangles and not just one big polygon?
and could you print the output onto the image?
Here is the code to identify the rectangles separately. The explanations are inline with the code:
import numpy as np
import cv2
# The standard stuff: image reading, grayscale conversion, blurring & edge detection
image = cv2.imread('rect_image.png')
orig = image.copy()
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray, (3, 3), 0)
edges = cv2.Canny(gray, 50, 200)
# Finding and sorting contours based on contour area
cnts = cv2.findContours(edges, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
cnts = sorted(cnts, key = cv2.contourArea, reverse = True)[:6]
vertices = []
for i, c in enumerate(cnts):
if i == 0:
# This is the largest contour
# For overlapping case the largest one will be the only one contour
peri = cv2.arcLength(cnts[i], True)
approx = cv2.approxPolyDP(cnts[i], 0.02 * peri, True)
vertices.append(approx)
elif i < len(cnts) - 1:
# Searches for any other inner contour
# Also filters out close contours generated due to thick line
if not np.isclose(cv2.contourArea(cnts[i]), cv2.contourArea(cnts[i+1]), atol=20000):
peri = cv2.arcLength(cnts[i+1], True)
approx = cv2.approxPolyDP(cnts[i+1], 0.02 * peri, True)
vertices.append(approx)
if len(vertices) == 1:
# This case is where there is only one contour (the overlapping case)
# There are eight extreme points for two overlapping rectangles
# The distinct rectangles are colored in 'green' and 'red'
extLeft1 = tuple(vertices[0][vertices[0][:, :, 0].argmin()][0])
extRight1 = tuple(vertices[0][vertices[0][:, :, 0].argmax()][0])
extTop1 = tuple(vertices[0][vertices[0][:, :, 1].argmin()][0])
extBot1 = tuple(vertices[0][vertices[0][:, :, 1].argmax()][0])
mask = np.isin(vertices[0][:, :, 1], (extRight1, extLeft1, extTop1, extBot1))
indices = np.where(mask)
vertices = np.delete(vertices[0], indices, 0)
extLeft2 = tuple(vertices[vertices[:, :, 0].argmin()][0])
extRight2 = tuple(vertices[vertices[:, :, 0].argmax()][0])
extTop2 = tuple(vertices[vertices[:, :, 1].argmin()][0])
extBot2 = tuple(vertices[vertices[:, :, 1].argmax()][0])
x, y, w, h = cv2.boundingRect(np.array([extLeft1, extLeft2, extRight1, extRight2]))
cv2.rectangle(image, (x, y), (x + w, y + h), (0, 255, 0), 2)
x, y, w, h = cv2.boundingRect(np.array([extTop1, extTop2, extBot1, extBot2]))
cv2.rectangle(image, (x, y), (x + w, y + h), (0, 0, 255), 2)
else:
# This case is where there are inner rectangle (the embedded case)
# The distinct rectangles are colored in 'green' and 'red'
x, y, w, h = cv2.boundingRect(vertices[0])
cv2.rectangle(image, (x, y), (x + w, y + h), (0, 255, 0), 2)
x, y, w, h = cv2.boundingRect(vertices[1])
cv2.rectangle(image, (x, y), (x + w, y + h), (0, 0, 255), 2)
# Displaying the images with identified colored rectangles
cv2.imshow("Input", orig)
cv2.imshow("Contour", image)
cv2.waitKey(0)
cv2.destroyAllWindows()
Below are the output of the detected rectangles drawn on the image in green and red colors:

Remove Contours OpenCV

My Image
I want to get
https://ibb.co/t8hNkM2
I could only get
I was able to find the maximum contour
def img_counter_max(image_file: str):
img = cv2.imread(image_file)
# grayscale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # меняем цветовую модель с BGR на HSV
cv2.waitKey(0)
# binarize
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY_INV)
cv2.waitKey(0)
# find contours
ctrs, hier = cv2.findContours(thresh.copy(), cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
# sort contours
sorted_ctrs = sorted(ctrs, key=lambda ctr: cv2.boundingRect(ctr)[0])
# sorted_ctrs sorted(ctrs, key=cv2.contourArea, reverse=True)[0]
contour_sizes = [(cv2.contourArea(contour), contour) for contour in sorted_ctrs]
biggest_contour = max(contour_sizes, key=lambda x: x[0])[1]
x, y, w, h = cv2.boundingRect(biggest_contour)
roi = img[y:y + h, x:x + w]
cv2.imwrite("C:\\Users\\dennn\\PycharmProjects\\untitled2\\imag\\roi1.jpg",
roi)
cv2.rectangle(img, (x, y), (x + w, y + h), (90, 255, 0), 2)
from tensorflow.python import Size
resize_img = cv2.resize(img, (512,512))
# cv2.resize(img, Size(512,512), interpolation=cv2.INTER_AREA)
cv2.namedWindow("Display frame", cv2.WINDOW_AUTOSIZE);
cv2.imshow('Display frame', resize_img)
cv2.waitKey(0)
How do I get the image I need?
I found that sorting by contourArea() gives wrong results. Probably it calculates all points inside contour but not rectangle area which it uses - and this rectangle can be bigger.
I use boundingRect() to get rectangle used by contour and later calculate size using w*h and then it sorts contours in correct way.
I use for-loop to display image with different rectangles and see which contour gives expected region. And this way I see that third contour gives expected region so I can use [2] to get it and save it.
Eventually I would use size to select region which has w*h is in some range
expecte_region_size - range < w*h < expecte_region_size + range
Eventually I would use for-loop which display image with different rectangles to select manually which rectangle to use to save in file.
import cv2
img = cv2.imread('image.jpg')
# grayscale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # меняем цветовую модель с BGR на HSV
# binarize
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY_INV)
# find contours
ctrs, hier = cv2.findContours(thresh.copy(), cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
# find rect and area - and create items [contour, rect, area] - but sorting by area gives wrong results
#items = [[ctr, cv2.boundingRect(ctr), cv2.contourArea(ctr)] for ctr in ctrs]
# find rect - and create items [contour, rect]
items = [[ctr, cv2.boundingRect(ctr)] for ctr in ctrs]
# find rect's size and create items [contour, rect, size]
items = [[ctr, rect, rect[2]*rect[3]] for ctr, rect in items]
# sort by size
items = sorted(items, key=lambda x: x[2], reverse=True)
for index, item in enumerate(items[:5]):
contour = item[0]
x, y, w, h = item[1]
size = item[2]
print(index, '->', size, '(', x, y, w, h, ')')
img_copy = img.copy()
cv2.rectangle(img_copy, (x, y), (x + w, y + h), (0, 0, 255), 15)
resize_img = cv2.resize(img_copy, (512,512))
cv2.imshow('frame', resize_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
# --- save image ---
item = items[2]
contour = item[0]
x, y, w, h = item[1]
size = item[2]
img = img[y:y+h, x:x+w]
cv2.imwrite('output.jpg', img)
Preview:
Output:
The code finds characters well,but outputs them out of order
I found a piece of code that should solve this problem, but I can't -
after finding the contours using contours=cv2.findContours(),use -
boundary=[]
for c,cnt in enumerate(contours):
x,y,w,h = cv2.boundingRect(cnt)
boundary.append((x,y,w,h))
count=np.asarray(boundary)
max_width = np.sum(count[::, (0, 2)], axis=1).max()
max_height = np.max(count[::, 3])
nearest = max_height * 1.4
ind_list=np.lexsort((count[:,0],count[:,1]))
c=count[ind_list]
Find symbols
img = "C:\\Users\\dennn\\PycharmProjects\\untitled2\\output.jpg" dir = os.curdir
path = os.path.join(dir,img)
raw_image = cv2.imread(path,0)
cv2.imshow("original",raw_image)
plt.subplot(2,3,1)
plt.title("Original")
plt.imshow(raw_image,'gray')
plt.xticks([]),plt.yticks([]);
sm_image = cv2.blur(raw_image,(8,8))
cv2.imshow("smoothed",sm_image)
plt.subplot(2,3,2)
plt.title("Smoothed")
plt.imshow(sm_image,'gray')
plt.xticks([]),plt.yticks([]);
#cv2.imshow("smoothed",sm_image)
ret,bw_image = cv2.threshold(sm_image,160,255,cv2.THRESH_BINARY_INV)
cv2.imshow("thresholded",bw_image)
plt.subplot(2,3,3)
plt.title("Thresholded")
plt.imshow(bw_image,'gray')
plt.xticks([]),plt.yticks([]);
kernel = np.ones((4,4),np.uint8)
er_image = cv2.erode(bw_image,kernel)
cv2.imshow("eroded",er_image)
plt.subplot(2,3,4)
plt.title("Eroded")
plt.imshow(er_image,'gray')
plt.xticks([]),plt.yticks([]);
kernel = np.ones((2,2),np.uint8)
di_image = cv2.dilate(er_image,kernel)
cv2.imshow("dilated",di_image)
plt.title("Dilated")
plt.subplot(2,3,5)
plt.imshow(di_image,'gray')
plt.xticks([]),plt.yticks([]);
mo_image = di_image.copy()
contour0 =
cv2.findContours(mo_image.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
contours = [cv2.approxPolyDP(cnt,3,True) for cnt in contour0[0]]
maxArea = 0
rect = []
for ctr in contours:
maxArea = max(maxArea, cv2.contourArea(ctr))
if img == "C:\\Users\\dennn\\PycharmProjects\\untitled2\\output.jpg":
areaRatio = 0.05
for ctr in contours:
if cv2.contourArea(ctr) > maxArea * areaRatio:
rect.append(cv2.boundingRect(cv2.approxPolyDP(ctr, 1, True)))
symbols = []
for i in rect:
x = i[0]
y = i[1]
w = i[2]
h = i[3]
p1 = (x, y)
p2 = (x + w, y + h)
cv2.rectangle(mo_image, p1, p2, 255, 2)
image = cv2.resize(mo_image[y:y + h, x:x + w], (32, 32))
symbols.append(image.reshape(1024, ).astype("uint8"))
testset_data = np.array(symbols)
cv2.imshow("segmented", mo_image)
plt.subplot(2, 3, 6)
plt.title("Segmented")
plt.imshow(mo_image, 'gray')
plt.xticks([]), plt.yticks([]);
# plt.show()
# garbage collection
cv2.destroyAllWindows()
plt.close()
# show glyphs
for i in range(len(symbols)):
image = np.zeros(shape=(64,64))
image[15:47,15:47] = symbols[i].reshape((32,32))
cv2.imshow("sym",image)
cv2.waitKey(0)
cv2.destroyAllWindows()
plt.close()

Trying to determine the co-ordinates of the bounding box in the image and crop it further on

I'm trying to get the co-ordinates of the bbox in the image and crop that area from the image.
I'm a newbie with opencv and python
I tried getting the list of the co-ordinates in a list and trying to pass it in. It gives an error of "SystemError: tile cannot extend outside image".
I looked for answers in this regard but couldn't understand it.
import numpy as np
import imutils, cv2
import o
from PIL import Image
original_image = cv2.imread("04239713_02718309.tiff")
image = original_image.copy()
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
edged = cv2.Canny(blurred, 120, 255, 1)
#cv2.imshow("edged", edged)
cnts = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
checkbox_contours = []
threshold_max_area = 3000
threshold_min_area = 375
contour_image = edged.copy()
cood=[]
allcoord=[]
for c in cnts:
peri = cv2.arcLength(c, True)
approx = cv2.approxPolyDP(c, 0.035 * peri, True)
x=0
y=0
w=0
h=0
(x, y, w, h) = cv2.boundingRect(approx)
aspect_ratio = w / float(h)
area = cv2.contourArea(c)
if area < threshold_max_area and area > threshold_min_area and (aspect_ratio >= 0.9 and aspect_ratio <= 1):
cv2.drawContours(original_image,[c], 0, (0,255,0), 3)
#print(x,y,w,h)
temp=(x,y,w,h)
cood.append(temp)
checkbox_contours.append(c)
allcoord.append(cood)
print("cood",len(cood))
#print("allcoords",allcoord)
#print(allcoord)
print('checkbox_contours', len(checkbox_contours))
cv2.imwrite("peternass1.png", original_image)
print(cood)
org_image ='04239713_02718309.tiff'
for i, n in enumerate(cood):
image_obj = Image.open(org_image)
cropped_image = image_obj.crop(n)
os.system("{}.png".format(i))
cropped_image.save('Cro_{}.png'.format(i), 'png')
I'm using opencv 4.0 here
You find the contours with
contours, _ = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
You can draw a bounding box around the first contour
cnt = contours[0]
x,y,w,h = cv2.boundingRect(cnt)
x,y are the left top coordinates of the bounding
w is the width(x coordinate value),h is the height(y coordinate value)
let say your original image was
img = cv2.imread('myimage.jpg')
you can crop it using
#x1,y1 = x,y(left top corner) and x2,y2 = x+w,y+h(right bottom corner)
selected_roi = img[y1:y2,x1:x2]
Image ROI

Find the center line between object's edges

Today I am trying to identify the edge of an object.
I got a great result by doing this.
import cv2
img = cv2.imread("0.png")
img2 = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
img2 = cv2.equalizeHist(img2)
img2 = cv2.GaussianBlur(img2, (7, 7), 0)
edges = cv2.Canny(img2, 180, 300)
im2, contours, hierarchy = cv2.findContours(edges, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(img, contours, -1, (0, 255, 0), 1)
cv2.imshow('img', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
and the image looks like: (it's a welding's x-ray)
My ultimate goal is to find the center line between the edges,
(the collection of (MaxY+MinY)/2 on each X)
the ideal result should look like this: (sorry for the bad hand drawing)
Could anyone let me know how should I do this?
Thank you very much.
First of all you should prepare your image so that you can found your one contour (threshold, histogram equalization etc.). The contour returns you a set of (x,y) coordinates that represent the contour so for the first step you should seperate the upper edge from the bottom (split it on half). In my example I made it complementary to momements of the contour but note that this will not work for curved lines! You will have to make an algorithm to divide upper side and down side. Once you have done this you can make two list, containing one element per x coordinate. Then simply calculate the midle and make a point on the image.
Example code:
import cv2
import numpy as np
img = cv2.imread('centerline.png')
mask = np.zeros((img.shape[:2]), np.uint8)
h2, w2 = img.shape[:2]
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
equ = cv2.equalizeHist(gray)
_, thresh = cv2.threshold(equ,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
kernel = np.ones((5,5),np.uint8)
opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel)
_, contours, hierarchy = cv2.findContours(opening,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)
for cnt in contours:
x,y,w,h = cv2.boundingRect(cnt)
print(h, w)
if h < 30 and w > 270:
cv2.drawContours(mask, [cnt], 0, (255,255,255), -1)
res = cv2.bitwise_and(img, img, mask=mask)
gray = cv2.cvtColor(res,cv2.COLOR_BGR2GRAY)
_, thresh = cv2.threshold(gray,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
blur = cv2.GaussianBlur(thresh,(5,5),0)
_, contours, hierarchy = cv2.findContours(blur,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)
cnt = max(contours, key=cv2.contourArea)
M = cv2.moments(cnt)
cy = int(M['m01']/M['m00'])
mask = np.zeros((img.shape[:2]), np.uint8)
cv2.drawContours(mask, [cnt], 0, (255,255,255), -1)
up = []
down = []
for i in cnt:
x = i[0][0]
y = i[0][1]
if x == 0:
pass
elif x == w2:
pass
else:
if y > cy:
down.append(tuple([x,y]))
elif y < cy:
up.append(tuple([x,y]))
else:
pass
up.sort(key = lambda x: x[0])
down.sort(key = lambda x: x[0])
up_1 = []
down_1 = []
for i in range(0, len(up)-1):
if up[i][0] != up[i+1][0]:
up_1.append(up[i])
else:
pass
for i in range(0, len(down)-1):
if down[i][0] != down[i+1][0]:
down_1.append(down[i])
else:
pass
lines = zip(up_1, down_1)
for i in lines:
x1 = i[0][0]
y1 = i[0][1]
x2 = i[1][0]
y2 = i[1][1]
middle = np.sqrt(((x2-x1)**2)+((y2-y1)**2))
cv2.circle(img, (x1, y1+int(middle/2)), 1, (0,0,255), -1)
cv2.imshow('img', img)
Result:

Categories