I'm trying to write a program in Python that can mark exam papers using opencv and imutils, I have attached an example. I'm trying to get a top down view of the page but it's not detecting the right contours.Contours drawn imutils four_point_transform applied I would like it to apply the function to the whole page and not to the questions. Is it possible to use the corner borders for four_point_transform? It may not be very visible so here is the original image.
# Import libraries
import cv2
import numpy as np
import imutils
from imutils import contours
from imutils.perspective import four_point_transform
# Verify exam sheet location
image = cv2.imread("test4.jpg")
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (5, 5), 0)
edge = cv2.Canny(blur, 100, 200)
# Locate contours
contours = cv2.findContours(edge.copy(), cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
contours = imutils.grab_contours(contours)
docCnt = None
# Verify main object is exam
if len(contours) > 0:
contours = sorted(contours, key=cv2.contourArea, reverse=True)
for c in contours:
peri = cv2.arcLength(c, True)
approx = cv2.approxPolyDP(c, 0.02 * peri, True)
if len(approx) == 4:
docCnt = approx
break
# Create a top-down view of paper
paper = four_point_transform(image, docCnt.reshape(4,2))
warped = four_point_transform(edge, docCnt.reshape(4,2))
# Draw contours
cv2.drawContours(image, contours, -1, (0,255,0), 3)
cv2.imwrite("Contours.jpg", image)
# Display images
cv2.imshow("Original", image)
# cv2.imshow("Perspective", paper)
# cv2.imshow("Processed", edge)
# cv2.imshow("Processed + Perspective", warped)
cv2.waitKey(0)
cv2.destroyAllWindows
The contour just needed a bit of dilating and then it was able to capture the paper in itself. Here is the code:
image = cv2.imread('Example.jpg') # read image and convert to rgb for each plotting
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # turn to gray
blurred = cv2.GaussianBlur(gray, (5, 5), 0) # blurring
edges = cv2.Canny(blurred, 50, 150) # edges
kernel = np.ones((5,5), np.uint8) # kernel for dilating
edges = cv2.dilate(edges, kernel, iterations=3) # dilating
contours, _ = cv2.findContours(edges.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE) # get contours
largest_contour = max(contours, key = cv2.contourArea) # get largest
im_cont = image.copy() # copy image for contouring
In the above code, I went through a standard canny edge detection. Then, I got the approximation of the contour:
hull = cv2.convexHull(largest_contour) # get the hull
epsilon = 0.02 * cv2.arcLength(largest_contour, True) # epsilon
pts1 = np.float32(cv2.approxPolyDP(hull, epsilon, True).reshape(-1, 2)) # get the points
pts2 = np.float32([[0, 0], [image.shape[1], 0], [image.shape[1], image.shape[0]], [0, image.shape[0]]]) # get original edges
matrix = cv2.getPerspectiveTransform(pts1, pts2) # get perspective transform matrix
result = cv2.warpPerspective(image, matrix, (image.shape[1], image.shape[0])) # apply matrix to image
Plotting, I got the following results:
Maybe think also a bit about stretching to make the aspect ratio a bit better. This is a nice tutorial as well
V2.0:Using imutils
As you can see from the function, all you require for the function are the image itself and the coordinates of the detected corner. Hence, here is the adjusted code:
import cv2
import numpy as np
%matplotlib notebook
import matplotlib.pyplot as plt
from imutils.perspective import four_point_transform
#####################################################################################################
image = cv2.imread('Example.jpg') # read image and convert to rgb for each plotting
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # turn to gray
blurred = cv2.GaussianBlur(gray, (5, 5), 0) # blurring
edges = cv2.Canny(blurred, 50, 150) # edges
kernel = np.ones((5,5), np.uint8) # kernel for dilating
edges = cv2.dilate(edges, kernel, iterations=3) # dilating
contours, _ = cv2.findContours(edges.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE) # get contours
largest_contour = max(contours, key = cv2.contourArea) # get largest
im_cont = image.copy() # copy image for contouring
cv2.drawContours(im_cont, largest_contour, -1, (255,0,0), 2)
#####################################################################################################
hull = cv2.convexHull(largest_contour) # get the hull
epsilon = 0.02 * cv2.arcLength(largest_contour, True) # epsilon
pts1 = np.float32(cv2.approxPolyDP(hull, epsilon, True).reshape(-1, 2)) # get the points
result = four_point_transform(image, pts1) # using imutils
Here is the result, the nice addition is that the imutils function additionally scales the results.