import cv2 import gradio as gr import numpy as np from sklearn.linear_model import LinearRegression checker_large_real = 10.8 checker_small_real = 6.35 def check_orientation(image): #image = cv2.rotate(image, cv2.ROTATE_90_CLOCKWISE) orientation = np.argmax(image.shape) if orientation == 0: image = cv2.rotate(image, cv2.ROTATE_90_CLOCKWISE) return image, orientation def get_color_checker_table(data_points, y, yend): sorted_points = sorted(data_points, key=lambda point: (point[1], point[0])) differences_y = [sorted_points[0][1] - y] + \ [abs(sorted_points[i][1] - sorted_points[i + 1][1]) for i in range(len(sorted_points) - 1)] + \ [yend - sorted_points[-1][1]] most_usual_y = 10 local_max = round((yend - y) * 0.2184) lines = [] last_id = 0 label_upper = differences_y[0] // local_max if differences_y[0] > local_max + 10 else 0 label_lower = differences_y[-1] // local_max if differences_y[-1] > local_max + 10 else 0 for j in range(len(differences_y) - 1): if differences_y[j] > local_max + 10: lines.extend([[] for _ in range(label_upper)]) break for i in range(1, len(differences_y) - 1): if differences_y[i] > most_usual_y: lines.append(sorted_points[last_id:i]) last_id = i if differences_y[-1] < local_max + 10: lines.append(sorted_points[last_id:]) else: lines.append(sorted_points[last_id:]) lines.extend([[] for _ in range(label_lower)]) lines = [sorted(line, key=lambda point: point[0]) for line in lines] return label_upper, label_lower, local_max, lines def check_points(data_points, x, xend, y, yend, image): most_usual = int((xend - x) / 7.016) label_upper, label_lower, usual_y, lines = get_color_checker_table(data_points, y, yend) for q in lines: if not q: continue differences_x = [q[0][0] - x] + [q[i + 1][0] - q[i][0] for i in range(len(q) - 1)] + [xend - q[-1][0]] threshold_x = int(most_usual * (1 + 1 / 5.6)) for j, distance in enumerate(differences_x[:-1]): if distance > threshold_x: positions = distance // int(most_usual * (1 - 1 / 11.2)) - 1 for t in range(positions): cnt = (q[j][0] - (t + 1) * most_usual, q[j][1]) # cv2.circle(image, cnt, 5, (255, 0, 0), -1) data_points.append(cnt) if differences_x[-1] > threshold_x: positions = differences_x[-1] // int(most_usual * (1 - 1 / 11.2)) - 1 for t in range(positions): cnt = (q[-1][0] + (t + 1) * most_usual, q[-1][1]) # cv2.circle(image, cnt, 5, (255, 0, 0), -1) data_points.append(cnt) data_points.sort(key=lambda point: (point[1], point[0])) _, _, _, new_lines = get_color_checker_table(data_points, y, yend) return label_upper, label_lower, usual_y, image, new_lines, data_points def get_reference_values(points, image): values = [] for i in points: point_value = image[i[1], i[0]] values.append(point_value) return values def detect_RGB_values(image, dst): x1, y1 = map(round, dst[0][0]) x2, y2 = map(round, dst[2][0]) y2 = max(0, y2) image_checker = image[y1:y2, x2:x1] if image_checker.size != 0: # Apply GaussianBlur to reduce noise and improve edge detection blurred = cv2.GaussianBlur(image_checker, (5, 5), 0) # Apply edge detection edges = cv2.Canny(blurred, 50, 120) # Find contours contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) centers = [ (x + w // 2 + x2, y + h // 2 + y1) for contour in contours for x, y, w, h in [cv2.boundingRect(contour)] if 0.8 < (aspect_ratio := w / float(h)) < 1.2 and (area := cv2.contourArea(contour)) > 100 ] if centers: # Filter out centers too close to the edges centers = [ center for center in centers if abs(center[0] - x2) >= (x1 - x2) / 7.29 and abs(center[0] - x1) >= (x1 - x2) / 7.29 ] if centers: label_upper, label_lower, usual, image, new_lines, M_T = check_points(centers, x2, x1, y1, y2, image) else: label_upper, label_lower, M_T = 0, 0, [] else: label_upper, label_lower, M_T = 0, 0, [] else: label_upper, label_lower, M_T = 0, 0, [] M_R = np.array([ [52, 52, 52], [85, 85, 85], [122, 122, 121], [160, 160, 160], [200, 200, 200], [243, 243, 242], [8, 133, 161], [187, 86, 149], [231, 199, 31], [175, 54, 60], [70, 148, 73], [56, 61, 150], [224, 163, 46], [157, 188, 64], [94, 60, 108], [193, 90, 99], [80, 91, 166], [214, 126, 44], [103, 189, 170], [133, 128, 177], [87, 108, 67], [98, 122, 157], [194, 150, 130], [115, 82, 68] ]) if len(M_T) < 24: for i in range(label_upper): new_lines[0] = [(x, y - round(usual)) for x, y in new_lines[1]] for j in range(label_lower): new_lines[-1] = [(x, y + round(usual)) for x, y in new_lines[-2]] if len(M_T) != 24: new_lines = [] M_T = [point for sublist in new_lines for point in sublist] M_T_values = np.array(get_reference_values(M_T, image)) return M_T_values, M_R css = ".input_image {height: 10% !important; width: 10% !important;}" def detect_template(image, orientation): MIN_MATCH_COUNT = 10 template_path = 'template_img.png' template_image = cv2.imread(template_path, cv2.IMREAD_GRAYSCALE) gray_image = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY) image_c = image.copy() # Initiate SIFT detector sift = cv2.SIFT_create() keypoints1, descriptors1 = sift.detectAndCompute(template_image, None) keypoints2, descriptors2 = sift.detectAndCompute(gray_image, None) # FLANN parameters index_params = dict(algorithm=1, trees=5) search_params = dict(checks=50) flann = cv2.FlannBasedMatcher(index_params, search_params) matches = flann.knnMatch(descriptors1, descriptors2, k=2) # Apply ratio test good_matches = [m for m, n in matches if m.distance < 0.7 * n.distance] if len(good_matches) > MIN_MATCH_COUNT: src_points = np.float32([keypoints1[m.queryIdx].pt for m in good_matches]).reshape(-1, 1, 2) dst_points = np.float32([keypoints2[m.trainIdx].pt for m in good_matches]).reshape(-1, 1, 2) M, mask = cv2.findHomography(src_points, dst_points, cv2.RANSAC, 5.0) h, w = template_image.shape template_corners = np.float32([[0, 0], [0, h - 1], [w - 1, h - 1], [w - 1, 0]]).reshape(-1, 1, 2) dst_corners = cv2.perspectiveTransform(template_corners, M) x1, y1 = map(round, dst_corners[0][0]) x2, y2 = map(round, dst_corners[2][0]) # if orientation == 0: # checker_large_im = abs(y2 - y1) # checker_small_im = abs(x2 - x1) # else: checker_small_im = abs(y2 - y1) checker_large_im = abs(x2 - x1) if checker_small_im != 0 and checker_large_im != 0: px_cm_ratio_small = checker_small_real / checker_small_im px_cm_ratio_large = checker_large_real / checker_large_im else: px_cm_ratio_small = 0 px_cm_ratio_large = 0 annotated_image = cv2.polylines(image_c, [np.int32(dst_corners)], True, 255, 3, cv2.LINE_AA) if orientation == 0: annotated_image = cv2.rotate(annotated_image, cv2.ROTATE_90_COUNTERCLOCKWISE) else: print(f"Not enough matches are found - {len(good_matches)}/{MIN_MATCH_COUNT}") return None, 0, 0 if orientation ==0: cm_per_pixel_width = px_cm_ratio_small cm_per_pixel_height = px_cm_ratio_large else: cm_per_pixel_width = px_cm_ratio_large cm_per_pixel_height = px_cm_ratio_small return annotated_image, dst_corners, cm_per_pixel_width, cm_per_pixel_height,checker_small_im,checker_large_im def srgb_to_linear(rgb): rgb = rgb / 255.0 linear_rgb = np.where(rgb <= 0.04045, rgb / 12.92, ((rgb + 0.055) / 1.055) ** 2.4) return linear_rgb def linear_to_srgb(linear_rgb): # Clip linear_rgb to ensure no negative values linear_rgb = np.clip(linear_rgb, 0, 1) srgb = np.where(linear_rgb <= 0.0031308, linear_rgb * 12.92, 1.055 * (linear_rgb ** (1 / 2.4)) - 0.055) srgb = np.clip(srgb * 255, 0, 255) return srgb.astype(np.uint8) def calculate_color_correction_matrix_ransac(sample_rgb, reference_rgb): sample_rgb = sample_rgb[::-1] sample_rgb_linear = srgb_to_linear(sample_rgb) reference_rgb_linear = srgb_to_linear(reference_rgb) # Reshape the data for RANSAC X = sample_rgb_linear y = reference_rgb_linear # Initialize RANSAC regressor for each color channel models = [] scores = [] for i in range(3): # For each RGB channel ransac = LinearRegression() ransac.fit(X, y[:, i]) scores.append(ransac.score(X, y[:, i])) models.append(ransac.coef_) score = np.mean(scores) # Stack coefficients to form the transformation matrix M = np.stack(models, axis=-1) return M, score def apply_color_correction(image, M): image_linear = srgb_to_linear(image) corrected_image_linear = np.dot(image_linear, M) corrected_image_srgb = linear_to_srgb(corrected_image_linear) return corrected_image_srgb def calibrate_img(img): image, orientation = check_orientation(img) annotated_image, polygon, px_width, px_height,small_side,large_side = detect_template(image, orientation) a, b = detect_RGB_values(image, polygon) if len(a) == 24: M, score = calculate_color_correction_matrix_ransac(a, b) if orientation == 0: image = cv2.rotate(image, cv2.ROTATE_90_COUNTERCLOCKWISE) corrected_image = apply_color_correction(image, M) #corrected_image = cv2.cvtColor(corrected_image, cv2.COLOR_BGR2RGB) if orientation == 0: width= small_side height= large_side else: width = large_side height = small_side return annotated_image, corrected_image, px_width, px_height, width, height def process_img(img): return calibrate_img(img) app = gr.Interface( fn=process_img, inputs=gr.Image(label="Input"), css=css, outputs=[gr.Image(label="Output"), gr.Image(label="Corrected"), gr.Label(label='Cm/px for Width'), gr.Label(label='Cm/px for Height'), gr.Label(label='Checker Width'), gr.Label(label='Checker Height'),], allow_flagging='never') app.launch(share=True)