Skip to content

Instantly share code, notes, and snippets.

@kevinlinxc
Created March 23, 2024 19:00
Show Gist options
  • Save kevinlinxc/ed8fc8fc3aa4b4ded9ff128ef6df464b to your computer and use it in GitHub Desktop.
Save kevinlinxc/ed8fc8fc3aa4b4ded9ff128ef6df464b to your computer and use it in GitHub Desktop.
On key press, display a letter in the center of cv2 imshow, and get points on the contour of the letter
from PIL import Image, ImageDraw, ImageFont
import cv2
import numpy as np
def get_bounding_rect_area(contour):
# calculate area using the minimum bounding rectangle instead of whatever cv2.contourArea does
rect = cv2.minAreaRect(contour)
width = rect[1][0]
height = rect[1][1]
return width * height
def draw_biggest_external_contour(frame,
border_size=0,
border_color=(255, 255, 255),
min_distance=50,
canny_low=50,
canny_high=150,
dilate_kernel_size=10):
# Add a white border to the frame, so that a fully black frame will still have an outline
frame = cv2.copyMakeBorder(frame, border_size, border_size, border_size, border_size, cv2.BORDER_CONSTANT, value=border_color)
# Convert the frame to grayscale
gray_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (dilate_kernel_size, dilate_kernel_size))
morphed = cv2.morphologyEx(gray_frame, cv2.MORPH_CLOSE, kernel)
# dilate and erode to remove noise
# Run Canny edge detection
edges = cv2.Canny(morphed, canny_low, canny_high)
# Find contours
contours, _ = cv2.findContours(edges, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE) # cv2.RETR_EXTERNAL for just external,
# Find the largest n contours
if len(contours) == 0:
return [], frame, 0
main_contours = []
# add all contours with area greater than 100
for contour in contours:
if get_bounding_rect_area(contour) > 200:
main_contours.append(contour)
# num_contours = len(main_contours)
# Extract points along the contour that are farther than min_distance away from each other
contour_points = []
for main_contour in main_contours:
for i in range(len(main_contour)):
point = main_contour[i][0]
# if i is 0 or if the the point is far enough from the previous point
if i == 0 or np.linalg.norm(np.array(point) - np.array(contour_points[-1])) > min_distance:
contour_points.append(tuple(point))
# reduce duplicates
new_contour_points = []
for i in range(len(contour_points)):
if i == 0 or all(np.linalg.norm(np.array(contour_points[i]) - np.array(point)) > min_distance/3 for point in new_contour_points):
new_contour_points.append(contour_points[i])
contour_points = new_contour_points
# Draw circles on the frame at the extracted points
for point in contour_points:
cv2.circle(frame, point, 8, (0, 255, 0), -1)
return contour_points, frame
if __name__ == "__main__":
font_size = 500 # Increase the font size for a much bigger text
font_filepath = "Lato-Black.ttf" # download a font to use, put it in the same directory
text_color = (0, 0, 0, 255) # RGBA format, where the last value is alpha (transparency)
padding = 50 # Padding size in pixels
# Initialize the font and mask image
font = ImageFont.truetype(font_filepath, size=font_size)
text = "A" # Initial text
text_width, text_height = font.getsize(text)
padded_width = text_width + 2 * padding
padded_height = text_height + 2 * padding
mask_image = Image.new("RGBA", (padded_width, padded_height), (255, 255, 255, 255)) # Transparent background
mask_draw = ImageDraw.Draw(mask_image)
# Calculate the position to center the text horizontally
text_x = (padded_width - text_width) // 2
text_y = 0
mask_draw.text((text_x, text_y), text, font=font, fill=text_color) # Draw text with padding
img = np.array(mask_image)
contour_points, frame = draw_biggest_external_contour(img)
last_letter = ord('A')
cv2.imshow("test", frame)
key = cv2.waitKey(0) & 0xFF
while key != 27: # the ESC key
print(key)
if key != 255 and key != 1: # default if no key is pressed, also 1 sometimes shows up for the first button?
text = chr(key)
# Clear the previous text in the mask image
mask_draw.rectangle((0, 0, padded_width, padded_height), fill=(255, 255, 255, 255))
# Calculate the position to center the new text horizontally
text_width, text_height = font.getsize(text)
text_x = (padded_width - text_width) // 2
text_y = 0
# Draw the new text in the mask image with padding
mask_draw.text((text_x, text_y), text, font=font, fill=text_color)
# Convert the updated mask image to a numpy array for OpenCV processing
img = np.array(mask_image)
contour_points, frame = draw_biggest_external_contour(img)
# Display the updated image using OpenCV
cv2.imshow("test", frame)
last_letter = key
key = cv2.waitKeyEx(0)
cv2.destroyAllWindows()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment