OpenCv: Connect edges to make rectangle - python

I am using opencv to detect receipt:
import numpy as np
import cv2
import matplotlib.pyplot as plt
from skimage.filters import threshold_local
from PIL import Image
import sys
file_name = "test.jpg"
image = cv2.imread(file_name)
# Downscale image as finding receipt contour is more efficient on a small image
resize_ratio = 500 / image.shape[0]
original = image.copy()
image = opencv_resize(image, resize_ratio)
# Convert to grayscale for further processing
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# Get rid of noise with Gaussian Blur filter
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
# Detect white regions
rectKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 9))
dilated = cv2.dilate(blurred, rectKernel)
# Detect edge
edged = cv2.Canny(dilated, 40, 200, apertureSize=3)
# Detect all contours in Canny-edged image
contours, hierarchy = cv2.findContours(edged, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
image_with_contours = cv2.drawContours(image.copy(), contours, -1, (0,255,0), 3)
# Create hull to have good shape rectangle
# Find the convex hull object for each contour
hull_list = []
for i in range(len(contours)):
hull = cv2.convexHull(contours[i])
hull_list.append(hull)
image_with_hull = cv2.drawContours(image.copy(), hull_list, -1, (0,255,0), 3)
# Get 10 largest contours
#largest_contours = sorted(contours, key = cv2.contourArea, reverse = True)[:10]
largest_contours = sorted(hull_list, key = cv2.contourArea, reverse = True)[:10]
image_with_largest_contours = cv2.drawContours(image.copy(), largest_contours, -1, (0,255,0), 3)
I am detecting image contours and once I have the contours I would like to make it a rectangle to make sure I capture a complete receipt. I am using convex hull for making the rectangle, but the problem is convex hull not follow straight lines (I mean along with border of the receipt, like in 90-degree angle) instead it just connects the points where it stopped which is not correct. Is it possible to make a rectangle with 4 points to capture a complete receipt? something like this:
Any help will be really appreciated.

Related

Is there a way to draw inscribed ellipse in a almost rectangular contour?

I am wondering if there is a way to inscribe an ellipse in almost rectangular contour? I am using openCV findcontours() method in my image and my aim is to fit biggest possible ellipses inside every object in the following image:
Code I am using:
import cv2
import numpy as np
def find_contours(preprocessed, min_perimeter):
contours, hierarchy = cv2.findContours(preprocessed,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE) #find contours
contours_new = [] #new empty list of contours
for cnts in contours: #looping trough contours
perimeter = cv2.arcLength(cnts,True) #arc length
if perimeter > min_perimeter: #leave only significant contours
contours_new.append(cnts)
return contours_new
min_perimeter = 7000
img_num = 6
results_path = 'results/'
image_path = 'images/'
mask = image_path+str(img_num)+'_predicted_mask.bmp'
empty = np.empty([9452, 6144], dtype=np.uint16)
empty = cv2.cvtColor(empty, cv2.COLOR_GRAY2BGR)
gray = cv2.imread(mask)
ret,thresholded_mask = cv2.threshold(gray,150,255,0)
gray_mask = cv2.cvtColor(thresholded_mask, cv2.COLOR_BGR2GRAY)
contours_new = find_contours(gray_mask, min_perimeter)
cv2.drawContours(empty, contours_new, -1, (255,255,255), 3)
for cnt in contours_new:
ellipse = cv2.fitEllipse(cnt)
cv2.ellipse(empty, ellipse, (0, 0, 255), 3)
cv2.imwrite('contours.bmp',empty)
Result (as you can see elipses are not inscribed):

opencv: how to merge near contours to get the one big outest contour?

I am trying to digitize the kid's drawing into SVG or transparent png file format so that they can be used in Scratch. The white paper should be replaced by transparent background and all the drawing part should be preserved.
My plan is to get the outest contour of the drawing and generate a mask, then use the mask to get the drawing part without paper background.
The problem is the drawing may not consecutive which means there may have some small holes leading to break the entire drawing contour to many many small contours.
Now I want to concatenate the near outest contours to form a big outest contour for masking.
The original drawing and the processed result is attached.
Code:
from __future__ import print_function
import cv2 as cv
import numpy as np
import argparse
import random as rng
rng.seed(12345)
def thresh_callback(val):
threshold = val
# Detect edges using Canny
canny_output = cv.Canny(src_gray, threshold, threshold * 2)
# Find contours
contours, hierarchy = cv.findContours(canny_output, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
# Draw contours
drawing = np.zeros((canny_output.shape[0], canny_output.shape[1], 3), dtype=np.uint8)
for i in range(len(contours)):
color = (rng.randint(0,256), rng.randint(0,256), rng.randint(0,256))
cv.drawContours(drawing, contours, i, color, 2, cv.LINE_8, hierarchy, 0)
# Show in a window
cv.imshow('Contours', drawing)
# Load source image
parser = argparse.ArgumentParser(description='Code for Finding contours in your image tutorial.')
parser.add_argument('--input', help='Path to input image.', default='IMG_4446.jpg')
args = parser.parse_args()
src = cv.imread(cv.samples.findFile(args.input))
if src is None:
print('Could not open or find the image:', args.input)
exit(0)
# Convert image to gray and blur it
src_gray = cv.cvtColor(src, cv.COLOR_BGR2GRAY)
src_gray = cv.blur(src_gray, (3,3))
# Create Window
source_window = 'Source'
cv.namedWindow(source_window)
cv.imshow(source_window, src)
max_thresh = 255
thresh = 100 # initial threshold
cv.createTrackbar('Canny Thresh:', source_window, thresh, max_thresh, thresh_callback)
thresh_callback(thresh)
cv.waitKey()
import cv2, numpy as np
# Read Image
img = cv2.imread('/home/stephen/Desktop/test_img.png')
img =cv2.resize(img, (750,1000))
# Find the gray image
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # Gray
gray = cv2.blur(gray, (2,2))
cv2.imwrite('/home/stephen/Desktop/gray.png',gray)
# Find the canny image
canny = cv2.Canny(gray, 30, 150) # Canny
# Find contours
contours, _ = cv2.findContours(canny,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
# Draw contours on canny (this connects the contours)
cv2.drawContours(canny, contours, -1, 255, 6)
cv2.imwrite('/home/stephen/Desktop/contours.png',canny)
# Get mask for floodfill
h, w = canny.shape[:2]
mask = np.zeros((h+2, w+2), np.uint8)
# Floodfill from point (0, 0)
cv2.floodFill(canny, mask, (0,0), 123)
cv2.imwrite('/home/stephen/Desktop/floodfill.png',canny)
# Exclude everying but the floodfill region
canny = cv2.inRange(canny, 122, 124)
cv2.imwrite('/home/stephen/Desktop/inrange.png',canny)

OpenCV, cv2.approxPolyDP() Draws double lines on closed contour

I want to create some polygons out of this mask:
image 1 - Mask
So i created these contours with openCV findcontours():
image 2 - Contours
When creating polygons I get these polygons:
image 3 - Polygons
As you can see some polygons are drawn using double lines. How do I prevent this?
See my code:
import glob
from PIL import Image
import cv2
import numpy as np
# Let's load
image = cv2.imread(path + "BigOneEnhanced.tif")
# Grayscale
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# Find Canny edges
edged = cv2.Canny(gray, 30, 200)
# Finding Contours
contours, hierarchy = cv2.findContours(edged,
cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_TC89_L1)
canvas = np.zeros(image.shape, np.uint8)
# creating polygons from contours
polygonelist = []
for cnt in contours:
# define contour approx
perimeter = cv2.arcLength(cnt,True)
epsilon = 0.005*cv2.arcLength(cnt,True)
approx = cv2.approxPolyDP(cnt,epsilon,True)
polygonelist.append(approx)
cv2.drawContours(canvas, polygonelist, -1, (255, 255, 255), 3)
imgB = Image.fromarray(canvas)
imgB.save(path + "TEST4.png")
The problem source is the Canny edges detection:
After applying edge detection you are getting two contours for every original contour - one outside the edge and one inside the edge (and other weird stuff).
You may solve it by applying findContours without using Canny.
Here is the code:
import glob
from PIL import Image
import cv2
import numpy as np
path = ''
# Let's load
#image = cv2.imread(path + "BigOneEnhanced.tif")
image = cv2.imread("BigOneEnhanced.png")
# Grayscale
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# Apply threshold (just in case gray is not binary image).
ret, thresh_gray = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)
# Find Canny edges
#edged = cv2.Canny(gray, 30, 200)
# Finding Contours cv2.CHAIN_APPROX_TC89_L1
#contours, hierarchy = cv2.findContours(edged, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
contours, hierarchy = cv2.findContours(thresh_gray, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
canvas = np.zeros(image.shape, np.uint8)
# creating polygons from contours
polygonelist = []
for cnt in contours:
# define contour approx
perimeter = cv2.arcLength(cnt, True)
epsilon = 0.005*perimeter #0.005*cv2.arcLength(cnt, True)
approx = cv2.approxPolyDP(cnt, epsilon, True)
polygonelist.append(approx)
cv2.drawContours(canvas, polygonelist, -1, (255, 255, 255), 3)
imgB = Image.fromarray(canvas)
imgB.save(path + "TEST4.png")
Result:

How to crop an image into pieces after detecting the edges using python

I am working on a torn document reconstruction project. First I tried to detect the edges of the image which contain torn document pieces and then I tried to crop the image into the pieces through the detected edges using the sample code,
import cv2
import numpy as np
img = cv2.imread("test.png")
img = cv2.imread("d:/test.jpeg")
cv2.imshow('Original Image',img)
new_img = cv2.Canny(img, 0, 505)
cv2.imshow('new image', new_img)
blurred = cv2.blur(new_img, (3,3))
canny = cv2.Canny(blurred, 50, 200)
## find the non-zero min-max coords of canny
pts = np.argwhere(canny>0)
y1,x1 = pts.min(axis=0)
y2,x2 = pts.max(axis=0)
## crop the region
cropped = new_img[y1:y2, x1:x2]
cv2.imwrite("cropped.png", cropped)
tagged = cv2.rectangle(new_img.copy(), (x1,y1), (x2,y2), (0,255,0), 3, cv2.LINE_AA)
cv2.imshow("tagged", tagged)
cv2.waitKey()
my input image was
after running the above code i gets a output like
can someone help me to crop the torn document pieces and assign them into variables
The beginning of my workflow is similar to yours. First step: blur the image..
blurred = cv2.GaussianBlur(gray, (5, 5), 0) # Blur
Second step: get the canny image...
canny = cv2.Canny(blurred, 30, 150) # Canny
Third step: draw the contours on the canny image. This closes the torn pieces.
# Find contours
_, contours, _ = cv2.findContours(canny,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
# Draw contours on canny (this connects the contours
cv2.drawContours(canny, contours, -1, 255, 2)
canny = 255 - canny
Fourth step: floodfill (the floodfilled areas are gray)
# Get mask for floodfill
h, w = thresh.shape[:2]
mask = np.zeros((h+2, w+2), np.uint8)
# Floodfill from point (0, 0)
cv2.floodFill(thresh, mask, (0,0), 123);
Fifth step: get rid of the really small and really large contours
# Create a blank image to draw on
res = np.zeros_like(src_img)
# Create a list for unconnected contours
unconnectedContours = []
for contour in contours:
area = cv2.contourArea(contour)
# If the contour is not really small, or really big
if area > 123 and area < 760000:
cv2.drawContours(res, [contour], 0, (255,255,255), cv2.FILLED)
unconnectedContours.append(contour)
Finally, once you have segmented the pieces, they can be nested.

Find exact contours (opencv, python)

I am using OpenCV+Python to detect and extract eyeglasses from a (face) image. I followed the line of reasoning of this post(https://stackoverflow.com/questi...) which is the following:
1) Detect the face
2) Find the largest contour in the face area which must be the outer frame of the glasses
3) Find the second and third largest contours in the face area which must be the frame of the two lenses
4) Extract the area between these contours which essentially represents the eyeglasses
However, findContours() does find the outer frame of the glasses as contour but it does not accurately find the frame of the two lenses as contours.
My results are the following:
Original image, Outer frame,
Left lens
My source code is the following:
import cv2
import numpy as np
import matplotlib.pyplot as plt
img = cv2.imread('Luc_Marion.jpg')
RGB_img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Detect the face in the image
haar_face_cascade = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')
faces = haar_face_cascade.detectMultiScale(gray_img, scaleFactor=1.1, minNeighbors=8);
# Loop in all detected faces - in our case it is only one
for (x,y,w,h) in faces:
cv2.rectangle(RGB_img,(x,y),(x+w,y+h),(255,0,0), 1)
# Focus on the face as a region of interest
roi = RGB_img[int(y+h/4):int(y+2.3*h/4), x:x+w]
roi_gray = gray_img[int(y+h/4):int(y+2.3*h/4), x:x+w]
# Apply smoothing to roi
roi_blur = cv2.GaussianBlur(roi_gray, (5, 5), 0)
# Use Canny to detect edges
edges = cv2.Canny(roi_gray, 250, 300, 3)
# Dilate and erode to thicken the edges
kernel = np.ones((3, 3), np.uint8)
edg_dil = cv2.dilate(edges, kernel, iterations = 3)
edg_er = cv2.erode(edg_dil, kernel, iterations = 3)
# Thresholding instead of Canny does not really make things better
# ret, thresh = cv2.threshold(roi_blur, 127, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)
# thresh = cv2.adaptiveThreshold(blur_edg, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 115, 1)
# Find and sort contours by contour area
cont_img, contours, hierarchy = cv2.findContours(edg_er, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
print("Number of contours: ", len(contours))
cont_sort = sorted(contours, key=cv2.contourArea, reverse=True)
# Draw largest contour on original roi (which is the outer
# frame of eyeglasses)
cv2.drawContours(roi, cont_sort[0], -1, (0, 255, 0), 2)
# Draw second largest contour on original roi (which is the left
# lens of the eyeglasses)
# cv2.drawContours(roi, cont_sort[1], -1, (0, 255, 0), 2)
plt.imshow(RGB_img)
plt.show()
How can I detect exactly the contour of the left lens and of the right lens of the eyeglasses?
I hope that it is clear that my final output must be the eyeglasses themselves as my final goal is to extract the eyeglasses from a face image.

Categories

Resources