Skip to content

Instantly share code, notes, and snippets.

@khursani8
Created June 7, 2025 06:18
Show Gist options
  • Select an option

  • Save khursani8/6aa16033c4ef114956e5f7941fba532c to your computer and use it in GitHub Desktop.

Select an option

Save khursani8/6aa16033c4ef114956e5f7941fba532c to your computer and use it in GitHub Desktop.
import cv2
import numpy as np
# Load the image
img = cv2.imread('miku.png')
h, w = img.shape[:2]
# Initiate SIFT detector
sift = cv2.SIFT_create()
# Find the keypoints and descriptors with SIFT
kp1, des1 = sift.detectAndCompute(img, None)
# Initialize the webcam
cap = cv2.VideoCapture(1)
# Create BFMatcher object
bf = cv2.BFMatcher()
# QR code detector
qr_code_detector = cv2.QRCodeDetector()
while(True):
# Capture frame-by-frame
ret, frame = cap.read()
# if frame is read correctly ret is True
if not ret:
print("Can't receive frame (stream end?). Exiting ...")
break
# Find the keypoints and descriptors with SIFT
kp2, des2 = sift = cv2.SIFT_create().detectAndCompute(frame, None)
# Detect QR code
data, bbox, _ = qr_code_detector.detectAndDecode(frame)
if des2 is not None:
matches = bf.knnMatch(des1, des2, k=2)
# Apply ratio test
good_matches = []
for match in matches:
if len(match) == 2: # Check if there are two matches
m, n = match
if m.distance < 0.7*n.distance:
good_matches.append(m)
else:
pass # Skip if not enough matches
# Extract location of good matches
src_pts = np.float32([ kp1[m.queryIdx].pt for m in good_matches ]).reshape(-1,1,2)
dst_pts = np.float32([ kp2[m.trainIdx].pt for m in good_matches ]).reshape(-1,1,2)
# Find homography
if len(good_matches) > 10:
M, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC,5.0)
matchesMask = mask.ravel().tolist()
# Perspective transform
if M is not None:
pts = np.float32([ [0,0],[0,h-1],[w-1,h-1],[w-1,0] ]).reshape(-1,1,2)
dst = cv2.perspectiveTransform(pts, M)
# Draw lines around the marker
cv2.line(frame, (int(dst[0][0][0]), int(dst[0][0][1])), (int(dst[1][0][0]), int(dst[1][0][1])), (0, 255, 0), 2)
cv2.line(frame, (int(dst[1][0][0]), int(dst[1][0][1])), (int(dst[2][0][0]), int(dst[2][0][1])), (0, 255, 0), 2)
cv2.line(frame, (int(dst[2][0][0]), int(dst[2][0][1])), (int(dst[3][0][0]), int(dst[3][0][1])), (0, 255, 0), 2)
cv2.line(frame, (int(dst[3][0][0]), int(dst[3][0][1])), (int(dst[0][0][0]), int(dst[0][0][1])), (0, 255, 0), 2)
# Use data from QR code if available, otherwise use "miku"
if data:
text = data
else:
text = 'miku'
# Create text image with alpha channel
font = cv2.FONT_HERSHEY_SIMPLEX
font_scale = 1
font_thickness = 2
text_size = cv2.getTextSize(text, font, font_scale, font_thickness)[0]
text_width, text_height = text_size
text_img = np.zeros((text_height * 2, text_width * 2, 4), np.uint8) # 4 channels for RGBA
text_x = int((text_width * 2 - text_width) / 2)
text_y = int((text_height * 2 + text_height) / 2)
cv2.putText(text_img, text, (text_x, text_y), font, font_scale, (0, 0, 255, 255), font_thickness, cv2.LINE_AA)
# Make background transparent
mask = text_img[:, :, 3] == 0
text_img[mask] = [0, 0, 0, 0]
# Warp text image
h_text, w_text = text_img.shape[:2]
pts_text = np.float32([[0, 0], [0, h_text], [w_text, h_text], [w_text, 0]]).reshape(-1, 1, 2)
dst_text = cv2.perspectiveTransform(pts_text, M)
# Get bounding rectangle
rect = cv2.boundingRect(dst_text)
x,y,w_rect,h_rect = rect
# Check if the bounding rectangle is within the frame bounds
if x >= 0 and y >= 0 and x + w_rect <= frame.shape[1] and y + h_rect <= frame.shape[0]:
# Warp perspective
warped_text = cv2.warpPerspective(text_img, M, (frame.shape[1], frame.shape[0]))
# Overlay warped text onto frame (alpha blending)
roi = frame[y:y+h_rect, x:x+w_rect]
warped_text_roi = warped_text[y:y+h_rect, x:x+w_rect]
# Split out the BGRA channels from the warped text
b,g,r,a = cv2.split(warped_text_roi)
# Create a mask from the alpha channel
alpha = a/255
# Blend the two images
for c in range(0,3):
roi[:,:,c] = alpha * warped_text_roi[:,:,c] + (1 - alpha) * roi[:,:,c]
else:
print ("Not enough matches are found - %d/%d" % (len(good_matches), 10))
matchesMask = None
# Draw first 10 matches
draw_params = dict(matchColor = (0,255,0), # draw matches in green color
singlePointColor = None,
matchesMask = matchesMask, # draw only inliers
flags = 2)
img3 = cv2.drawMatches(img,kp1,frame,kp2,good_matches, None, **draw_params)
# Display the resulting frame
cv2.imshow('Homography', img3)
else:
cv2.imshow('Homography', frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# When everything done, release the capture
cap.release()
cv2.destroyAllWindows()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment