Created
June 7, 2025 06:18
-
-
Save khursani8/6aa16033c4ef114956e5f7941fba532c to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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