Spaces:
Sleeping
Sleeping
| import streamlit as st | |
| from PIL import Image | |
| import io | |
| import cv2 | |
| import numpy as np | |
| import matplotlib.pyplot as plt | |
| import io as BytesIO | |
| from rembg import remove | |
| import mediapipe as mp | |
| from scipy.spatial import distance as dist | |
| ring_size_dict = { | |
| 14.0: 3, | |
| 14.4: 3.5, | |
| 14.8: 4, | |
| 15.2: 4.5, | |
| 15.6: 5, | |
| 16.0: 5.5, | |
| 16.45: 6, | |
| 16.9: 6.5, | |
| 17.3: 7, | |
| 17.7: 7.5, | |
| 18.2: 8, | |
| 18.6: 8.5, | |
| 19.0: 9, | |
| 19.4: 9.5, | |
| 19.8: 10, | |
| 20.2: 10.5, | |
| 20.6: 11, | |
| 21.0: 11.5, | |
| 21.4: 12, | |
| 21.8: 12.5, | |
| 22.2: 13, | |
| 22.6: 13.5 | |
| } | |
| def calculate_pixel_per_metric(image, known_diameter_of_coin=25): | |
| grayed = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) | |
| pixel_per_metric = None | |
| mm_per_pixel = None | |
| height, width = grayed.shape | |
| x_start, y_start = 0, 0 | |
| x_end, y_end = width // 2, height // 2 | |
| roi = grayed[y_start:y_end, x_start:x_end] | |
| roi_color = image[y_start:y_end, x_start:x_end] | |
| blurred = cv2.GaussianBlur(grayed, (9, 9), 2) | |
| circles = cv2.HoughCircles( | |
| blurred, | |
| cv2.HOUGH_GRADIENT, | |
| dp=1, | |
| minDist=50, | |
| param1=100, | |
| param2=30, | |
| minRadius=10, | |
| maxRadius=100 | |
| ) | |
| if circles is not None: | |
| circles = np.round(circles[0, :]).astype("int") | |
| largest_circle = max(circles, key=lambda c: c[2]) | |
| (x, y, r) = largest_circle | |
| cv2.circle(image, (x, y), r, (0, 255, 0), 4) | |
| cv2.rectangle(image, (x - 5, y - 5), (x + 5, y + 5), (0, 128, 255), -1) | |
| diameter = 2 * r | |
| if pixel_per_metric is None: | |
| pixel_per_metric = diameter / known_diameter_of_coin | |
| mm_per_pixel = known_diameter_of_coin / diameter | |
| diameter_in_mm = diameter / pixel_per_metric | |
| cv2.putText( | |
| image, | |
| f"Diameter: {diameter} px, Diameter in mm: {diameter_in_mm:.2f} mm", | |
| (x - 50, y - r - 10), | |
| cv2.FONT_HERSHEY_SIMPLEX, | |
| 1.5, | |
| (255, 255, 255), | |
| 3 | |
| ) | |
| return pixel_per_metric, mm_per_pixel, image | |
| def process_image(image): | |
| return remove(image) | |
| def calculate_pip_width(image, original_img, pixel_per_metric): | |
| def calSize(xA, yA, xB, yB, color_circle, color_line, img): | |
| d = dist.euclidean((xA, yA), (xB, yB)) | |
| cv2.circle(img, (int(xA), int(yA)), 5, color_circle, -1) | |
| cv2.circle(img, (int(xB), int(yB)), 5, color_circle, -1) | |
| cv2.line(img, (int(xA), int(yA)), (int(xB), int(yB)), color_line, 2) | |
| d_mm = d / pixel_per_metric | |
| d_mm = d_mm | |
| cv2.putText(img, "{:.1f}".format(d_mm), (int(xA - 15), int(yA - 10)), cv2.FONT_HERSHEY_SIMPLEX, 0.65, (255, 255, 255), 2) | |
| # print(d_mm) | |
| return d_mm, img | |
| og_img = original_img.copy() | |
| imgH, imgW, _ = image.shape | |
| imgcpy = image.copy() | |
| image_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) | |
| _, binary_image = cv2.threshold(image_gray, 1, 255, cv2.THRESH_BINARY) | |
| contours, _ = cv2.findContours(binary_image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) | |
| contour_image = np.zeros_like(image_gray) | |
| cv2.drawContours(contour_image, contours, -1, (255), thickness=cv2.FILLED) | |
| cv2.drawContours(imgcpy, contours, -1, (0, 255, 0), 2) | |
| # print("length : ",len(contours)) | |
| d_mm = 0 | |
| marked_img = image.copy() | |
| if len(contours) > 0: | |
| # print("hi") | |
| cnt = max(contours, key=cv2.contourArea) | |
| frame2 = cv2.cvtColor(og_img, cv2.COLOR_BGR2RGB) | |
| handsLM = mp.solutions.hands.Hands(max_num_hands=1, min_detection_confidence=0.8, min_tracking_confidence=0.8) | |
| pr = handsLM.process(frame2) | |
| # print(pr.multi_hand_landmarks) | |
| if pr.multi_hand_landmarks: | |
| # print("inside") | |
| for hand_landmarks in pr.multi_hand_landmarks: | |
| lmlist = [] | |
| for id, landMark in enumerate(hand_landmarks.landmark): | |
| xPos, yPos = int(landMark.x * imgW), int(landMark.y * imgH) | |
| lmlist.append([id, xPos, yPos]) | |
| if len(lmlist) != 0: | |
| pip_joint = [lmlist[14][1], lmlist[14][2]] | |
| mcp_joint = [lmlist[13][1], lmlist[13][2]] | |
| m2 = (pip_joint[1] - mcp_joint[1]) / (pip_joint[0] - mcp_joint[0]) | |
| m1 = -1 / m2 | |
| b = pip_joint[1] - m1 * pip_joint[0] | |
| x1, x2 = pip_joint[0], pip_joint[0] | |
| y1 = m1 * x1 + b | |
| y2 = m1 * x2 + b | |
| result = 1.0 | |
| while result > 0: | |
| result = cv2.pointPolygonTest(cnt, (x1, y1), False) | |
| x1 += 1 | |
| y1 = m1 * x1 + b | |
| x1 -= 1 | |
| result = 1.0 | |
| while result > 0: | |
| result = cv2.pointPolygonTest(cnt, (x2, y2), False) | |
| x2 -= 1 | |
| y2 = m1 * x2 + b | |
| x2 += 1 | |
| d_mm, marked_img = calSize(x1, y1, x2, y2, (255, 0, 0), (255, 0, 255), original_img) | |
| return original_img, d_mm, imgcpy, marked_img | |
| def show_resized_image(images, titles, scale=0.5): | |
| num_images = len(images) | |
| fig, axes = plt.subplots(1, num_images, figsize=(15, 5)) | |
| if num_images == 1: | |
| axes = [axes] | |
| for ax, img, title in zip(axes, images, titles): | |
| resized_image = cv2.resize(img, None, fx=scale, fy=scale, interpolation=cv2.INTER_LINEAR) | |
| ax.imshow(cv2.cvtColor(resized_image, cv2.COLOR_BGR2RGB)) | |
| ax.set_title(title) | |
| ax.axis('off') | |
| plt.tight_layout() | |
| img_stream = BytesIO.BytesIO() | |
| plt.savefig(img_stream, format='png') | |
| img_stream.seek(0) | |
| plt.close(fig) | |
| return img_stream | |
| def get_ring_size(mm_value): | |
| if mm_value in ring_size_dict: | |
| return ring_size_dict[mm_value] | |
| else: | |
| closest_mm = min(ring_size_dict.keys(), key=lambda x: abs(x - mm_value)) | |
| return ring_size_dict[closest_mm] | |
| st.set_page_config(layout="wide", page_title="Ring Size Measurement") | |
| st.write("## Determine Your Ring Size") | |
| st.write( | |
| "๐ Upload an image of your finger to measure the width and determine your ring size. The measurement will be displayed along with a visual breakdown of the image processing flow." | |
| ) | |
| st.sidebar.write("## Upload :gear:") | |
| MAX_FILE_SIZE = 5 * 1024 * 1024 # 5MB | |
| def process_image_and_get_results(upload): | |
| image = Image.open(upload) | |
| # image = cv2.imread(upload) | |
| image_np = np.array(image) | |
| image_np = cv2.cvtColor(image_np, cv2.COLOR_RGB2BGR) | |
| original_img = image_np.copy() | |
| og_img1 = image_np.copy() | |
| og_img2 = image_np.copy() | |
| pixel_per_metric, mm_per_pixel, image_with_coin_info = calculate_pixel_per_metric(image_np) | |
| processed_image = process_image(og_img1) | |
| image_with_pip_width, width_mm, contour_image, pip_mark_img = calculate_pip_width(processed_image, original_img, pixel_per_metric) | |
| ring_size = get_ring_size(width_mm) | |
| return { | |
| "processed_image": pip_mark_img, | |
| "original_image": og_img2, | |
| "image_with_coin_info": image_with_coin_info, | |
| "contour_image": contour_image, | |
| "width_mm": width_mm, | |
| "ring_size": ring_size | |
| } | |
| def show_how_it_works(processed_image): | |
| st.write("## How It Works") | |
| st.write("Here's a step-by-step breakdown of how your image is processed to determine your ring size:") | |
| st.image(processed_image, caption="Image Processing Flow", use_column_width=True) | |
| col1, col2 = st.columns(2) | |
| my_upload = st.sidebar.file_uploader("Upload an image", type=["png", "jpg", "jpeg"]) | |
| if my_upload is not None: | |
| if my_upload.size > MAX_FILE_SIZE: | |
| st.error("The uploaded file is too large. Please upload an image smaller than 5MB.") | |
| else: | |
| st.write("## Image Processing Flow") | |
| results = process_image_and_get_results(my_upload) | |
| col1.write("Uploaded Image :camera:") | |
| col1.image(cv2.cvtColor(results["original_image"], cv2.COLOR_BGR2RGB), caption="Uploaded Image") | |
| col2.write("Processed Image :wrench:") | |
| col2.image(cv2.cvtColor(results["processed_image"], cv2.COLOR_BGR2RGB), caption="Processed Image with PIP Width") | |
| st.write(f"๐ The width of your finger is {results['width_mm']:.2f} mm, and the estimated ring size is {results['ring_size']:.1f}.") | |
| if st.button("How it Works"): | |
| st.write("## How It Works") | |
| st.write("Here's a step-by-step breakdown of how your image is processed to determine your ring size:") | |
| img_stream = show_resized_image( | |
| [results["original_image"], results["image_with_coin_info"], results["contour_image"], results["processed_image"]], | |
| ['Original Image', 'Image with Coin Info', 'Contour Boundary Image', 'Ring Finger Width'], | |
| scale=0.5 | |
| ) | |
| st.image(img_stream, caption="Processing Flow", use_column_width=True) | |
| else: | |
| st.info("Please upload an image to get started.") | |