OpenCV transform image shape transformation into a given contour - python
Does anyone know whether it's possible to transform image A into image B contour if their shapes are random, using OpenCV or any other python libraries that work with images?
Here is what I have so far with 2 images:
I've been able to find draw contours of the bulb and insert a fox in it using bitwise_and method, but what it does is it crops the second image, whereas I need it to transform its shape into the bulb contour.
import cv2
src1 = cv2.imread('fox.png')
src2 = cv2.imread('bulb-contour-filled.png')
src2 = cv2.resize(src2, src1.shape[1::-1])
dst = cv2.bitwise_and(src1, src2)
cv2.imwrite('img_fin.jpg', dst)
Bulb original image:
Fox original image:
Bulb contour:
The Concept
For this we are going to need to slice the into triangles, and warp each triangle individually. The starting points of the image should be along the outline of the original image, and the ending points should be along the outline of the destination shape.
Although below I have hard-coded the 2 sets of points, you'll just need to figure out the optimal processing to retrieve the contours of the 2 images (each needs have the same number of points and in the same order). Also, I have programmed an interactive OpenCV program that will allow us to easily retrieve the coordinates.
The Code
import cv2
import numpy as np
def triangles(points):
points = np.where(points, points, 1)
subdiv = cv2.Subdiv2D((*points.min(0), *points.max(0)))
for pt in points:
subdiv.insert(tuple(map(int, pt)))
for pts in subdiv.getTriangleList().reshape(-1, 3, 2):
yield [np.where(np.all(points == pt, 1))[0][0] for pt in pts]
def crop(img, pts):
x, y, w, h = cv2.boundingRect(pts)
img_cropped = img[y: y + h, x: x + w]
pts[:, 0] -= x
pts[:, 1] -= y
return img_cropped, pts
def warp(img1, img2, pts1, pts2):
img2 = img2.copy()
for indices in triangles(pts1):
img1_cropped, triangle1 = crop(img1, pts1[indices])
img2_cropped, triangle2 = crop(img2, pts2[indices])
transform = cv2.getAffineTransform(np.float32(triangle1), np.float32(triangle2))
img2_warped = cv2.warpAffine(img1_cropped, transform, img2_cropped.shape[:2][::-1], None, cv2.INTER_LINEAR, cv2.BORDER_REFLECT_101)
mask = np.zeros_like(img2_cropped)
cv2.fillConvexPoly(mask, np.int32(triangle2), (1, 1, 1), 16, 0)
img2_cropped *= 1 - mask
img2_cropped += img2_warped * mask
return img2
def resize(img, size):
h, w = img.shape[:2]
return cv2.resize(img, (int(w * size), int(h * size)))
img1 = resize(cv2.imread("dog.png"), 0.8)
img2 = resize(cv2.imread("bulb.png"), 0.8)
pts1 = np.array([[322, 508], [390, 475], [413, 425], [440, 367], [453, 305], [458, 289], [446, 202], [434, 139], [392, 104], [324, 94], [246, 97], [194, 101], [111, 127], [98, 185], [88, 240], [95, 306], [90, 363], [123, 431], [160, 487], [223, 508]])
pts2 = np.array([[459, 793], [513, 715], [541, 580], [552, 470], [583, 398], [633, 323], [643, 233], [616, 144], [557, 71], [470, 28], [354, 27], [264, 72], [206, 138], [179, 225], [178, 302], [236, 401], [266, 480], [278, 564], [297, 707], [357, 792]])
cv2.imshow("result", warp(img1, img2, pts1, pts2))
cv2.waitKey(0)
cv2.destroyAllWindows()
The Output
The Explanation
Import the necessary libraries:
import cv2
import numpy as np
Define a function, triangles, that will take in an array of coordinates, points, and yield lists of 3 indices of the array for triangles that will cover the area of the original array of coordinates:
def triangles(points):
points = np.where(points, points, 1)
subdiv = cv2.Subdiv2D((*points.min(0), *points.max(0)))
for pt in points:
subdiv.insert(tuple(map(int, pt)))
for pts in subdiv.getTriangleList().reshape(-1, 3, 2):
yield [np.where(np.all(points == pt, 1))[0][0] for pt in pts]
Define a function, crop, that will take in an image array, img, and an array of three coordinates, pts. It will return a rectangular segment of the image just large enough to fit the triangle formed by the three point, and return the array of three coordinates transferred to the top-left corner of image:
def crop(img, pts):
x, y, w, h = cv2.boundingRect(pts)
img_cropped = img[y: y + h, x: x + w]
pts[:, 0] -= x
pts[:, 1] -= y
return img_cropped, pts
Define a function, warp, that will take in 2 image arrays, img1 and img2, and 2 arrays of coordinates, pts1 and pts2. It will utilize the triangles function defined before iterate through the triangles from the first array of coordinates, the crop function defined before to crop both images at coordinates corresponding to the triangle indices and use the cv2.warpAffine() method to warp the image at the current triangle of the iterations:
def warp(img1, img2, pts1, pts2):
img2 = img2.copy()
for indices in triangles(pts1):
img1_cropped, triangle1 = crop(img1, pts1[indices])
img2_cropped, triangle2 = crop(img2, pts2[indices])
transform = cv2.getAffineTransform(np.float32(triangle1), np.float32(triangle2))
img2_warped = cv2.warpAffine(img1_cropped, transform, img2_cropped.shape[:2][::-1], None, cv2.INTER_LINEAR, cv2.BORDER_REFLECT_101)
mask = np.zeros_like(img2_cropped)
cv2.fillConvexPoly(mask, np.int32(triangle2), (1, 1, 1), 16, 0)
img2_cropped *= 1 - mask
img2_cropped += img2_warped * mask
return img2
Read in your images. Note that I have resized the images to better fit my screen. If you remove the resizing part, you'll need to use the program below to re-adjust the points and get the corrected sets of points:
def resize(img, size):
h, w = img.shape[:2]
return cv2.resize(img, (int(w * size), int(h * size)))
img1 = resize(cv2.imread("dog.png"), 0.8)
img2 = resize(cv2.imread("bulb.png"), 0.8)
Finally, define the 2 sets of points; the first set outlining the first image, and the second one outlining the second. Use the warp function defined before to warp img1 to have its keypoints overlap with the kewpoints of img2 and show the resulting image:
pts1 = np.array([[0, 0], [286, 0], [286, 198], [174, 198], [158, 116], [0, 97]])
pts2 = np.array([[80, 37], [409, 42], [416, 390], [331, 384], [291, 119], [111, 311]])
cv2.imshow("result", warp(img1, img2, pts1, pts2))
cv2.waitKey(0)
cv2.destroyAllWindows()
Tools
Use the program below to manually drag the point onto each image, and see the warp effect in real-time. Of course, rather than manually doing this, you can detect the contours of the two images (make sure that they have the same number of points and are in the same order):
import cv2
import numpy as np
def triangles(points):
points = np.where(points, points, 1)
subdiv = cv2.Subdiv2D((*points.min(0), *points.max(0)))
for pt in points:
subdiv.insert(tuple(map(int, pt)))
for pts in subdiv.getTriangleList().reshape(-1, 3, 2):
yield [np.where(np.all(points == pt, 1))[0][0] for pt in pts]
def crop(img, pts):
x, y, w, h = cv2.boundingRect(pts)
img_cropped = img[y: y + h, x: x + w]
pts[:, 0] -= x
pts[:, 1] -= y
return img_cropped, pts
def warp(img1, img2, pts1, pts2):
img2 = img2.copy()
for indices in triangles(pts1):
img1_cropped, triangle1 = crop(img1, pts1[indices])
img2_cropped, triangle2 = crop(img2, pts2[indices])
transform = cv2.getAffineTransform(np.float32(triangle1), np.float32(triangle2))
img2_warped = cv2.warpAffine(img1_cropped, transform, img2_cropped.shape[:2][::-1], None, cv2.INTER_LINEAR, cv2.BORDER_REFLECT_101)
mask = np.zeros_like(img2_cropped)
cv2.fillConvexPoly(mask, np.int32(triangle2), (1, 1, 1), 16, 0)
img2_cropped *= 1 - mask
img2_cropped += img2_warped * mask
return img2
def draw_circle(event, x, y, flags, param):
pts = param
if event == cv2.EVENT_LBUTTONDOWN:
for pt in pts:
dist = (pt[0] - x) ** 2 + (pt[1] - y) ** 2
if dist < 225:
active_pt[:] = pt
elif event == cv2.EVENT_LBUTTONUP:
active_pt[:] = 0
elif event == cv2.EVENT_MOUSEMOVE:
if np.any(active_pt):
for pt in pts:
if np.all(active_pt == pt):
pt[:] = active_pt[:] = x, y
def draw_circles(img, pts):
img = img.copy()
for i, (x, y) in enumerate(pts):
cv2.circle(img, (x, y), 15, (0, 0, 255), -1)
cv2.putText(img, str(i), (x - 10, y + 10), cv2.FONT_HERSHEY_COMPLEX, 0.8, (0, 0, 0), 2)
return img
def resize(img, size):
h, w = img.shape[:2]
return cv2.resize(img, (int(w * size), int(h * size)))
img1 = resize(cv2.imread("dog.png"), 0.8)
img2 = resize(cv2.imread("bulb.png"), 0.8)
pts_count = 20
pts1 = np.arange(pts_count * 2).reshape((pts_count, 2))
pts2 = np.arange(pts_count * 2).reshape((pts_count, 2))
active_pt = np.array([0, 0])
cv2.namedWindow("image 1")
cv2.setMouseCallback('image 1', draw_circle, pts1)
cv2.namedWindow("image 2")
cv2.setMouseCallback('image 2', draw_circle, pts2)
pause = False
while True:
cv2.imshow('image 1', draw_circles(img1, pts1))
cv2.imshow('image 2', draw_circles(img2, pts2))
if not pause:
try:
cv2.imshow("result", warp(img1, img2, pts1, pts2))
except:
pass
key = cv2.waitKey(20)
if key & 0xFF == ord("q"):
break
if key & 0xFF == ord("p"):
pause = not pause
cv2.waitKey(0)
cv2.destroyAllWindows()
Here is a rough demonstration of how it works (sped up x4):
If the real-time warping is too slow on your computer, simply press the p key to pause the warp updating, and press it again to resume it.
Related
How to get shape of room using Opencv?
I'd like to know is it possible with Opencv to as an input image for instance use this one and based on the input image generate another 3D image with the same shape as input but just an 3D empty room. I tried following steps Load image Convert to gray scale Apply Gaussian Blur to reduce noise Added Canny edge detector Drawing white(255,255,255) contours on the detected edges Rest of the space filled with gray(43, 43, 43) look color Added white (255, 255, 255) borders on the corners. I thought that if will be able to detect all the edges in correct order, then i can just connect them with lines or contours and it will produce 3D image (But i didnt achieved that cuz i couldnt sort edges in the correct order) I know that in order to achieve 3D image, i need x,y,z coordinates, but i dont know what is the correct way to do that. As a result image i need to generate something like this one (ofcourse shape will depend on the input image). helper.py import cv2 import numpy as np def apply_canny_edge_detector(sobel_gray_image_with_gaussian_blur, threshold1=0.33, threshold2=0.5, weak_th=None, strong_th=None): return cv2.Canny(sobel_gray_image_with_gaussian_blur, threshold1, threshold2) def calc_gaussian_kernel(img_size, sigma=1): size = int(img_size) // 2 x, y = np.mgrid[-size:size + 1, -size:size + 1] normal = 1 / (2.0 * np.pi * sigma ** 2) g = np.exp(-(x ** 2 + y ** 2) / (2.0 * sigma ** 2)) * normal return g def apply_contours(original_img, gray_img, color=(0, 0, 255)): _, threshold = cv2.threshold(gray_img, 127, 255, cv2.THRESH_BINARY) contours, _ = cv2.findContours(threshold, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) # list for storing names of shapes for contour in contours: approx = cv2.approxPolyDP( contour, 0.01 * cv2.arcLength(contour, True), True ) cv2.drawContours(original_img, [approx], -2, color, 1, cv2.LINE_8) return original_img def fill_space_with_color(pixels: [], color=(197, 194, 199)): pixels[np.all(pixels != (10, 255, 255), axis=-1)] = color return pixels def apply_borders(image, color=(53, 11, 248)): bordered = cv2.copyMakeBorder(image, 1, 1, 1, 1, cv2.BORDER_CONSTANT, value=color) return bordered main.py from cv2_scripts.helper import * if __name__ == '__main__': for i in range(10, 11): # Read the image img_file = "p/0000{0}.jpg".format(str(i)) original_img = cv2.imread(img_file) sobel_gray_image = cv2.cvtColor(original_img, cv2.COLOR_BGR2GRAY) gaussian_kernel = calc_gaussian_kernel(11, 2) sobel_gray_image_with_gaussian_blur = cv2.filter2D(sobel_gray_image, -1, gaussian_kernel) canny_image = apply_canny_edge_detector(sobel_gray_image_with_gaussian_blur, 100) contoured_image = apply_contours(original_img, canny_image, (255,255,255)) filled_image = fill_space_with_color(contoured_image, [43, 43, 43]) room_shape = apply_borders(filled_image, [255, 255, 255]) cv2.imshow("Room Shape", room_shape) cv2.waitKey(0) cv2.destroyAllWindows()
How to mask outside or inside an arbitrary shape in Python?
from scipy import interpolate import matplotlib.pyplot as plt import numpy as np import cv2 a=np.arange(1,9) filename = 'image_file_0.tiff' img = cv2.imread(filename) x = np.array([389, 392, 325, 211, 92,103,194,310]) y = np.array([184,281,365,401,333,188,127,126]) x = np.r_[x, x[0]] y = np.r_[y, y[0]] tck, u = interpolate.splprep([x, y], s=0, per=True) xi, yi = interpolate.splev(np.linspace(0, 1, 1000), tck) fig, ax = plt.subplots(1, 1) ax.plot(xi, yi, '-b') plt.imshow(img) plt.show() I want to transform the pixel values of the outer or inner region after forming an arbitrary closed curve in the image. How do I do it?
You can do it using the cv2.fillpoly function. Using this picture for example: We can get the shape we want to mask using: from scipy import interpolate import matplotlib.pyplot as plt import numpy as np import cv2 a=np.arange(1,9) filename = 'image_file_0.tiff' img = cv2.imread('image_left.png', cv2.IMREAD_COLOR) x = np.array([289, 292, 125, 111, 40, 80, 94,210]) y = np.array([84 , 181, 265, 241,133, 88, 27, 40]) x = np.r_[x, x[0]] y = np.r_[y, y[0]] tck, u = interpolate.splprep([x, y], s=0, per=True) xi, yi = interpolate.splev(np.linspace(0, 1, 1000), tck) plt.imshow(img[:,:,[2,1,0]]) plt.scatter(xi, yi) plt.show() This results in: Now the masking can be done using: contour = np.array([[xii, yii] for xii, yii in zip(xi.astype(int), yi.astype(int))]) mask = np.zeros_like(img) cv2.fillPoly(mask, pts=[contour], color=(255, 255, 255)) masked_img = cv2.bitwise_and(img, mask) And the result: Using the inverted mask you manipulate the outer pixels as you wish: mask = np.ones_like(img)*255 cv2.fillPoly(mask, pts=[contour], color=(0,0,0)) masked_img = cv2.bitwise_and(img, mask) This results in:
Here's another way to do it using cv2.drawContours(). The idea is to take your rough contour points, smooth them out, draw this contour onto a mask, then cv2.bitwise_and() or inverse bitwise-and to extract the desired regions. Input image -> Generated mask Result -> Inverted result import numpy as np import cv2 from scipy import interpolate # Load image, make blank mask, define rough contour points image = cv2.imread('1.jpg') mask = np.zeros(image.shape, dtype=np.uint8) x = np.array([192, 225, 531, 900, 500]) y = np.array([154, 281, 665, 821, 37]) x = np.r_[x, x[0]] y = np.r_[y, y[0]] # Smooth contours tck, u = interpolate.splprep([x, y], s=0, per=True) x_new, y_new = interpolate.splev(np.linspace(0, 1, 1000), tck) smooth_contour = np.array([[[int(i[0]), int(i[1])]] for i in zip(x_new, y_new)]) # Draw contour onto blank mask in white cv2.drawContours(mask, [smooth_contour], 0, (255,255,255), -1) result1 = cv2.bitwise_and(image, mask) result2 = cv2.bitwise_and(image, 255 - mask) cv2.imshow('image', image) cv2.imshow('mask', mask) cv2.imshow('result1', result1) cv2.imshow('result2', result2) cv2.waitKey()
Perspective transform using Opencv
I am trying to achieve perspective transformation using Python and Open CV. While the transformation is done by selecting 4 points on the image, the output image is highly blurred. Even when I don't use the mouse event for selecting the 4 points(rather hard coding it), the image quality is still blurred.Here is my programmatic attempt to this: `def draw_circle(event, x, y, flags, param): if event == cv2.EVENT_LBUTTONDBLCLK: cv2.circle(img, (x, y), 5, (255, 0, 0), -1) p = (x, y) l.append(p) print(l) cv2.namedWindow('image', cv2.WINDOW_NORMAL) img = cv2.imread('Path to my input image') img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) cv2.resizeWindow('image', 600, 600) cv2.setMouseCallback('image', draw_circle) while 1: cv2.imshow('image', img) if cv2.waitKey(20) & 0xFF == 27: break cv2.destroyAllWindows() rows, cols, channels = img.shape pts1 = np.float32(l) pts2 = np.float32([[0, 0], [200, 0], [200, 100], [0, 100]]) M = cv2.getPerspectiveTransform(pts1, pts2) dst = cv2.warpPerspective(img, M, (200, 100), cv2.INTER_LINEAR) h1 = math.sqrt((abs(pts1[1][0] - pts1[0][0])) ** 2 + (abs(pts1[1][1] - pts1[0][1])) ** 2) h2 = math.sqrt((abs(pts1[3][0] - pts1[2][0])) ** 2 + (abs(pts1[3][1] - pts1[2][1])) ** 2) v1 = math.sqrt((abs(pts1[3][0] - pts1[0][0])) ** 2 + (abs(pts1[3][1] - pts1[0][1])) ** 2) v2 = math.sqrt((abs(pts1[2][0] - pts1[1][0])) ** 2 + (abs(pts1[2][1] - pts1[1][1])) ** 2) max_h = int(max(h1, h2)) max_v = int(max(v1, v2)) dst = cv2.resize(dst, (max_h, max_v)) plt.subplot(121), plt.imshow(img), plt.title('Input') plt.subplot(122), plt.imshow(dst), plt.title('Output') plt.show()` Here is my input image: This is a fridge image with selective beverages Here is my output image: This is the output image after perspective transform
replace in your code this line pts2 = np.float32([[0, 0], [200, 0], [200, 100], [0, 100]]) to this one (maybe you have to switch v/h order, I don't know python syntax): pts2 = np.float32([[0, 0], [max_h,0], [max_h,max_v], [0,max_v]]) by moving the max_h/max_v computation to before transformation computation. Then remove the resizing code. At the moment you first (implicitly) resize to a 100x200 temporary image, which will be very blurry if you resize it to a bigger image afterwards.
Find dominant color on contour opencv
I am trying to find the dominant color inside a contour (black or white). I am using OpenCV to read an image and extract white on black images. This is what I got so far: The green outline is the contour, the blue lines the bounding box. So I this instance I am trying to extract the numbers 87575220 but as you can see it also recognizes some random artifacts and for instance the letter G. I think the solution would be to find the dominant colour inside of the contours and that colour should be close to white. I don't have any idea how to do this though. This the code I have at the moment: import argparse import cv2 import imutils import numpy as np parser = argparse.ArgumentParser() parser.add_argument("--image", "-i", required=True, help="Image to detect blobs from") args = vars(parser.parse_args()) image = cv2.imread(args["image"]) image = imutils.resize(image, width=1200) grey = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) (minVal, maxVal, minLoc, maxLoc) = cv2.minMaxLoc(grey) maxval_10 = maxVal * 0.5 ret, threshold = cv2.threshold(grey, maxval_10, 255, cv2.THRESH_BINARY) canny = cv2.Canny(grey, 200, 250) lines = cv2.HoughLines(canny, 1, np.pi / 180, 140) print(maxVal) theta_min = 60 * np.pi / 180. theta_max = 120 * np.pi / 180.0 theta_avr = 0 theta_deg = 0 filteredLines = [] for rho, theta in lines[0]: a = np.cos(theta) b = np.sin(theta) x0 = a * rho y0 = b * rho x1 = int(x0 + 1000 * (-b)) y1 = int(y0 + 1000 * (a)) x2 = int(x0 - 1000 * (-b)) y2 = int(y0 - 1000 * (a)) cv2.line(image, (x1, y1), (x2, y2), (0, 0, 255), 2) if theta_min <= theta <= theta_max: filteredLines.append(theta) theta_avr += theta if len(filteredLines) > 0: theta_avr /= len(filteredLines) theta_deg = (theta_avr / np.pi * 180) - 90 else: print("Failed to detect skew") image = imutils.rotate(image, theta_deg) canny = imutils.rotate(canny, theta_deg) im2, contours, hierarchy = cv2.findContours(canny, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE) # cv2.drawContours(image, contours, -1, (0, 255, 0), 1) cv2.imshow('Contours', im2) boundingBoxes = [] filteredContours = [] for cnt in contours: (x, y, w, h) = cv2.boundingRect(cnt) if (h > 20 and h < 90 and w > 5 and w < h): if cv2.contourArea(cnt, True) <= 0: boundingBoxes.append((x, y, w, h)) filteredContours.append(cnt) for x, y, w, h in boundingBoxes: cv2.rectangle(image, (x, y), (x + w, y + h), (255, 0, 0), 2) cv2.drawContours(image, filteredContours, -1, (0, 255, 0), 1) cv2.imshow('Image', image) cv2.imshow('Edges', canny) cv2.imshow('Threshold', threshold) cv2.waitKey(0) cv2.destroyAllWindows() This is the original picture:
I would try to make a ROI before I start searching for numbers. You have not give the original image so this example is made with the image you posted (with boxes and contours allready drawn). Should aslo work with the original though. Steps are written in the example code. Hope it helps. Cheers! Example code: import cv2 import numpy as np # Read the image and make a copy then create a blank mask img = cv2.imread('dominant.jpg') img2 = img.copy() h,w = img.shape[:2] mask = np.zeros((h,w), np.uint8) # Transform to gray colorspace and perform histogram equalization gray = cv2.cvtColor(img2,cv2.COLOR_BGR2GRAY) equ = cv2.equalizeHist(gray) # Transform all pixels above thershold to white black = np.where(equ>10) img2[black[0], black[1], :] = [255, 255, 255] # Transform to gray colorspace and make a thershold then dilate the thershold gray = cv2.cvtColor(img2,cv2.COLOR_BGR2GRAY) _, thresh = cv2.threshold(gray,0,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU) kernel = np.ones((15,15),np.uint8) dilation = cv2.dilate(thresh,kernel,iterations = 1) # Search for contours and select the biggest one and draw it on mask _, contours, hierarchy = cv2.findContours(dilation,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE) cnt = max(contours, key=cv2.contourArea) cv2.drawContours(mask, [cnt], 0, 255, -1) # Perform a bitwise operation res = cv2.bitwise_and(img, img, mask=mask) # Display the ROI cv2.imshow('img', res) Result:
You could create a mask out of each contour: mask = np.zeros(image.shape, dtype="uint8") cv2.drawContours(mask, [cnt], -1, 255, -1) and then calculate the mean value of all pixels inside of the mask: mean = cv2.mean(image, mask=mask) and then check whether mean is close enough to white
Colors and mean do not match well due to color space properties. I would create an histogram and select the most frequent one (some color down sampling could be applied too)
4 point persective transform failure
I've been trying to do a 4 point perspective transform in order to start doing some OCR. Starting with the following image I can detect the number plate and crop it out with the green box being the bounding box and the red dots being the corners of the rectangle I want to square up. This is the output of the transform. At a first look it seams to have done the transform inside out (taking the parts either side rather than between the points). I'm using the imutils package to do the transform and working from this and this as a guide. I'm sure it's something relatively simple I'm missing. #!/usr/bin/python import numpy as np import cv2 import imutils from imutils import contours from imutils.perspective import four_point_transform img = cv2.imread("sample7-smaller.jpg") gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) blurred = cv2.bilateralFilter(gray,15,75,75) v = np.median(blurred) lower = int(max(0, (1.0 - 0.33) * v)) upper = int(min(255, (1.0 + 0.33) * v)) edged = cv2.Canny(blurred, lower, upper, 255) conts = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) conts = conts[0] if imutils.is_cv2() else conts[1] conts = sorted(conts, key=cv2.contourArea, reverse=True) for cnt in conts: approx = cv2.approxPolyDP(cnt,0.01*cv2.arcLength(cnt,True),True) if len(approx) == 4: x,y,w,h = cv2.boundingRect(cnt) cv2.rectangle(img,(x,y),(x+w,y+h),(0,255,0),2) for i in approx: cv2.circle(img,(i[0][0], i[0][1]),2,(0,0,255), thickness=4) warped = four_point_transform(img, approx.reshape(4,2)) cv2.imshow("crop",img[y:y+h,x:x+w]) cv2.imshow("warped", warped) cv2.waitKey(0)
I would recommend you to use the OpenCV Perspective Transform method, to get the desired results, as per the given image: First mark the position of src points: src_pts = np.array([[8, 136], [415, 52], [420, 152], [14, 244]], dtype=np.float32) And suppose you want to fit this number plate in a matrix of shape 50x200, so destination points would be: dst_pts = np.array([[0, 0], [200, 0], [200, 50], [0, 50]], dtype=np.float32) Find the perspective Transform Matrix as : M = cv2.getPerspectiveTransform(src_pts, dst_pts) warp = cv2.warpPerspective(img, M, (200, 50)) EDIT: As you didn't wanted to hard code the final width, height of plate, So in order to make the calculations more flexible you can calculate the width and height of the plate from the 4 marker points as: def get_euler_distance(pt1, pt2): return ((pt1[0] - pt2[0])**2 + (pt1[1] - pt2[1])**2)**0.5 src_pts = np.array([[8, 136], [415, 52], [420, 152], [14, 244]], dtype=np.float32) width = get_euler_distance(src_pts[0][0], src_pts[0][1]) height = get_euler_distance(src_pts[0][0], src_pts[0][3]) dst_pts = np.array([[0, 0], [width, 0], [width, height], [0, height]], dtype=np.float32) M = cv2.getPerspectiveTransform(src_pts, dst_pts) warp = cv2.warpPerspective(img, M, (width, height))