Spaces:
Runtime error
Runtime error
| import cv2 | |
| import numpy as np | |
| import math | |
| from sklearn.linear_model import LinearRegression | |
| import pytesseract | |
| import re | |
| pytesseract.pytesseract.tesseract_cmd = "C:/Program Files/Tesseract-OCR/tesseract.exe" | |
| pytesseract.pytesseract.tesseract_cmd = "/usr/bin/tesseract" | |
| def first_preprocessing(image): | |
| gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY) | |
| canny = cv2.Canny(gray,75,25) | |
| contours,hierarchies = cv2.findContours(canny,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_NONE) | |
| sorted_contours = sorted(contours,key = cv2.contourArea,reverse = True) | |
| largest_contour = sorted_contours[0] | |
| box = cv2.boundingRect(sorted_contours[0]) | |
| x = box[0] | |
| y = box[1] | |
| w = box[2] | |
| h = box[3] | |
| result = cv2.rectangle(image, (x, y), (x + w, y + h), (255, 255, 255), -1) | |
| return result | |
| def remove_head(image): | |
| custom_config = r'--oem 3 --psm 6' # Tesseract OCR configuration | |
| detected_text = pytesseract.image_to_string(image, config=custom_config) | |
| lines = detected_text.split('\n') | |
| # Find the first line containing some text | |
| line_index = 0 | |
| for i, line in enumerate(lines): | |
| if line.strip() != '': | |
| line_index = i | |
| break | |
| first_newline_idx = detected_text.find('\n') | |
| result = cv2.rectangle(image, (0, line_index), (image.shape[1], first_newline_idx), (255,255,255), thickness=cv2.FILLED) | |
| return result | |
| def second_preprocessing(image): | |
| gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY) | |
| canny = cv2.Canny(gray,75,25) | |
| contours,hierarchies = cv2.findContours(canny,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_NONE) | |
| sorted_contours = sorted(contours,key = cv2.contourArea,reverse = True) | |
| largest_contour = sorted_contours[0] | |
| box2 = cv2.boundingRect(sorted_contours[0]) | |
| x = box2[0] | |
| y = box2[1] | |
| w = box2[2] | |
| h = box2[3] | |
| result2 = cv2.rectangle(image, (x, y), (x + w, y + h), (255, 255, 255), -1) | |
| return result2 | |
| def find_vertical_profile(image): | |
| gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) | |
| _, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU) | |
| vertical_profile = np.sum(binary, axis=0) | |
| return vertical_profile | |
| def detect_steepest_changes(projection_profile, threshold=0.4, start_idx=0, min_valley_width=10, min_search_width=50): | |
| differences = np.diff(projection_profile) | |
| change_points = np.where(np.abs(differences) > threshold * np.max(np.abs(differences)))[0] | |
| left_boundaries = [] | |
| right_boundaries = [] | |
| for idx in change_points: | |
| if idx <= start_idx: | |
| continue | |
| if idx - start_idx >= min_search_width: | |
| decreasing_profile = projection_profile[idx:] | |
| if np.any(decreasing_profile > 0): | |
| right_boundary = idx + np.argmin(decreasing_profile) | |
| right_boundaries.append(right_boundary) | |
| else: | |
| continue | |
| valley_start = max(start_idx, idx - min_valley_width) | |
| valley_start = valley_start-40 | |
| valley_end = min(idx + min_valley_width, len(projection_profile) - 1) | |
| valley = valley_start + np.argmin(projection_profile[valley_start:valley_end]) | |
| left_boundaries.append(valley) | |
| break | |
| return left_boundaries, right_boundaries | |
| def crop_text_columns(image, projection_profile, threshold=0.4): | |
| start_idx = 0 | |
| text_columns = [] | |
| while True: | |
| left_boundaries, right_boundaries = detect_steepest_changes(projection_profile, threshold, start_idx) | |
| if not left_boundaries or not right_boundaries: | |
| break | |
| left = left_boundaries[0] | |
| right = right_boundaries[0] | |
| text_column = image[:, left:right] | |
| text_columns.append(text_column) | |
| start_idx = right | |
| return text_columns | |
| def parse_clues(clue_text): | |
| lines = clue_text.split('\n') | |
| clues = {} | |
| number = None | |
| column = 0 | |
| for line in lines: | |
| if "column separation" in line: | |
| column += 1 | |
| continue | |
| pattern = r"^(\d+(?:\.\d+)?)\s*(.+)" # Updated pattern to handle decimal point numbers for clues | |
| match = re.search(pattern, line) | |
| if match: | |
| number = float(match.group(1)) # Convert the matched number to float if there is a decimal point | |
| if number not in clues: | |
| clues[number] = [column,match.group(2).strip()] | |
| else: | |
| continue | |
| elif number is None: | |
| continue | |
| elif clues[number][0] != column: | |
| continue | |
| else: | |
| clues[number][1] += " " + line.strip() # Append to the previous clue if it's a multiline clue | |
| return clues | |
| def parse_crossword_clues(text): | |
| # Check if "Down" clues are present | |
| match = re.search(r'[dD][oO][wW][nN]\n', text) | |
| if match: | |
| across_clues, down_clues = re.split(r'[dD][oO][wW][nN]\n', text) | |
| else: | |
| # If "Down" clues are not present, set down_clues to an empty string | |
| across_clues, down_clues = text, "" | |
| across = parse_clues(across_clues) | |
| down = parse_clues(down_clues) | |
| return across, down | |
| def classify_text(filtered_columns): | |
| text = "" | |
| custom_config = r'--oem 3 --psm 6' | |
| for i, column in enumerate(filtered_columns): | |
| column2 = cv2.cvtColor(column, cv2.COLOR_BGR2RGB) | |
| scale_factor = 2.0 # You can adjust this value | |
| # Calculate the new dimensions after scaling | |
| new_width = int(column2.shape[1] * scale_factor) | |
| new_height = int(column2.shape[0] * scale_factor) | |
| # Resize the image using OpenCV | |
| scaled_image = cv2.resize(column2, (new_width, new_height), interpolation=cv2.INTER_LINEAR) | |
| # Apply image enhancement techniques | |
| denoised_image = cv2.fastNlMeansDenoising(scaled_image, None, h=10, templateWindowSize=7, searchWindowSize=21) | |
| enhanced_image = cv2.cvtColor(denoised_image, cv2.COLOR_BGR2GRAY) # Convert to grayscale # Apply histogram equalization | |
| detected_text = pytesseract.image_to_string(enhanced_image, config=custom_config) | |
| # print(detected_text) | |
| text+=detected_text | |
| across_clues, down_clues = parse_crossword_clues(text) | |
| return across_clues,down_clues | |
| def get_text(image): | |
| image = cv2.cvtColor(image,cv2.COLOR_GRAY2BGR) | |
| result = first_preprocessing(image) | |
| result1 = remove_head(result) | |
| result2 = second_preprocessing(result1) | |
| vertical_profile = find_vertical_profile(result2) | |
| combined_columns = crop_text_columns(result2,vertical_profile) | |
| across,down = classify_text(combined_columns) | |
| return across,down | |
| ################################ Grid Extraction begins here ########################### | |
| ######################################################################################## | |
| # for applying non max suppression of the contours | |
| def calculate_iou(image, contour1, contour2): | |
| # Create masks for each contour | |
| mask1 = np.zeros_like(image, dtype=np.uint8) | |
| cv2.drawContours(mask1, [contour1], -1, 255, thickness=cv2.FILLED) | |
| mask2 = np.zeros_like(image, dtype=np.uint8) | |
| cv2.drawContours(mask2, [contour2], -1, 255, thickness=cv2.FILLED) | |
| # Find the intersection between the two masks | |
| intersection = cv2.bitwise_and(mask1, mask2) | |
| # Calculate the intersection area | |
| intersection_area = cv2.countNonZero(intersection) | |
| # Calculate the union area (Not the accurate one but works alright XD !) | |
| union_area = cv2.contourArea(cv2.convexHull(np.concatenate((contour1, contour2)))) | |
| # Calculate the IoU | |
| iou = intersection_area / union_area | |
| return iou | |
| # remove overlapping contours, non square and not quardatic contours | |
| # this check every contour with every other contour so be careful | |
| def filter_contours(img_gray2, contours, iou_threshold = 0.6, asp_ratio = 1,tolerance = 0.5): | |
| # Remove overlapping contours, removing that are not square | |
| filtered_contours = [] | |
| epsilon = 0.02 | |
| for contour in contours: | |
| # Approximate the contour to reduce the number of points | |
| epsilon_multiplier = epsilon * cv2.arcLength(contour, True) | |
| approximated_contour = cv2.approxPolyDP(contour, epsilon_multiplier, True) | |
| # find the aspect ratio of the contour, if it is close to 1 then keep it otherwise discard | |
| _,_,w,h = cv2.boundingRect(approximated_contour) | |
| if(abs(float(w)/h - asp_ratio) > tolerance ): continue | |
| # Calculate the IoU with all existing contours | |
| iou_values = [calculate_iou(img_gray2,np.array(approximated_contour), np.array(existing_contour)) for existing_contour in filtered_contours] | |
| # If the IoU value with all existing contours is below the threshold, add the current contour | |
| if not any(iou_value > iou_threshold for iou_value in iou_values): | |
| filtered_contours.append(approximated_contour) | |
| return filtered_contours | |
| # https://stackoverflow.com/questions/383480/intersection-of-two-lines-defined-in-rho-theta-parameterization/383527#383527 | |
| # Define the parametricIntersect function | |
| def parametricIntersect(r1, t1, r2, t2): | |
| ct1 = np.cos(t1) | |
| st1 = np.sin(t1) | |
| ct2 = np.cos(t2) | |
| st2 = np.sin(t2) | |
| d = ct1 * st2 - st1 * ct2 | |
| if d != 0.0: | |
| x = int((st2 * r1 - st1 * r2) / d) | |
| y = int((-ct2 * r1 + ct1 * r2) / d) | |
| return x, y | |
| else: | |
| return None | |
| # Group the coordinate to a list such that each point in a list may belong to a line | |
| def group_lines(coordinates,axis=0,threshold=10): | |
| sorted_coordinates = list(sorted(coordinates,key=lambda x: x[axis])) | |
| groups = [] | |
| current_group = [] | |
| for i in range(len(sorted_coordinates)): | |
| if i!=0 and abs(current_group[0][axis] - sorted_coordinates[i][axis]) > threshold: # condition to change the group | |
| if len(current_group) > 4: | |
| groups.append(current_group) | |
| current_group = [] | |
| current_group.append(sorted_coordinates[i]) # condition to append to the group | |
| if(len(current_group) > 4): | |
| groups.append(current_group) | |
| return groups | |
| # Use the Grouped Lines to Fit a line using Linear Regression | |
| def fit_lines(grouped_lines,is_horizontal = False): | |
| actual_lines = [] | |
| for coordinates in grouped_lines: | |
| # Converting into numpy array | |
| coordinates_arr = np.array(coordinates) | |
| # Separate the x and y coordinates | |
| x = coordinates_arr[:, 0] | |
| y = coordinates_arr[:, 1] | |
| # Fit a linear regression model | |
| regressor = LinearRegression() | |
| regressor.fit(y.reshape(-1, 1), x) | |
| # Get the slope and intercept of the fitted line | |
| slope = regressor.coef_[0] | |
| intercept = regressor.intercept_ | |
| if(is_horizontal): | |
| intercept = np.mean(y) | |
| actual_lines.append((slope,intercept)) | |
| return actual_lines | |
| # Calculates difference between two consecutive elements in an array | |
| def average_distance(arr): | |
| n = len(arr) | |
| distance_sum = 0 | |
| for i in range(n - 1): | |
| distance_sum += abs(arr[i+1] - arr[i]) | |
| average = distance_sum / (n - 1) | |
| return average | |
| # If two adjacent lines are near than some threshold, then merge them | |
| # Returns Results in y = mx + b from | |
| def average_out_similar_lines(lines_m_c,lines_coord,del_threshold,is_horizontal=False): | |
| averaged_lines = [] | |
| i = 0 | |
| while(i < len(lines_m_c) - 1): | |
| _, intercept1 = lines_m_c[i] | |
| _, intercept2 = lines_m_c[i + 1] | |
| if abs(intercept2 - intercept1) < del_threshold: | |
| new_points = np.array(lines_coord[i] + lines_coord[i+1][:-1]) | |
| # Separate the x and y coordinates | |
| x = new_points[:, 0] | |
| y = new_points[:, 1] | |
| # Fit a linear regression model | |
| regressor = LinearRegression() | |
| regressor.fit(y.reshape(-1, 1), x) | |
| # Get the slope and intercept of the fitted line | |
| slope = regressor.coef_[0] | |
| intercept = regressor.intercept_ | |
| if(is_horizontal): | |
| intercept = np.mean(y) | |
| averaged_lines.append((slope,intercept)) | |
| i+=2 | |
| else: | |
| averaged_lines.append(lines_m_c[i]) | |
| i+=1 | |
| if(i < len(lines_m_c)): | |
| averaged_lines.append(lines_m_c[i]) | |
| return averaged_lines | |
| # If two adjacent lines are near than some threshold, then merge them | |
| # Returns Results in normalized vector form | |
| def average_out_similar_lines1(lines_m_c,lines_coord,del_threshold): | |
| averaged_lines = [] | |
| i = 0 | |
| while(i < len(lines_m_c) - 1): | |
| _, intercept1 = lines_m_c[i] | |
| _, intercept2 = lines_m_c[i + 1] | |
| if abs(intercept2 - intercept1) < del_threshold: | |
| new_points = np.array(lines_coord[i] + lines_coord[i+1][:-1]) | |
| coordinates = np.array(new_points) | |
| points = coordinates[:, None, :].astype(np.int32) | |
| # Fit a line using linear regression | |
| [vx, vy, x, y] = cv2.fitLine(points, cv2.DIST_L2, 0, 0.01, 0.01) | |
| averaged_lines.append((vx, vy, x, y)) | |
| i+=2 | |
| else: | |
| new_points = np.array(lines_coord[i]) | |
| coordinates = np.array(new_points) | |
| points = coordinates[:, None, :].astype(np.int32) | |
| # Fit a line using linear regression | |
| [vx, vy, x, y] = cv2.fitLine(points, cv2.DIST_L2, 0, 0.01, 0.01) | |
| averaged_lines.append((vx, vy, x, y)) | |
| i+=1 | |
| if(i < len(lines_m_c)): | |
| new_points = np.array(lines_coord[i]) | |
| coordinates = np.array(new_points) | |
| points = coordinates[:, None, :].astype(np.int32) | |
| # Fit a line using linear regression | |
| [vx, vy, x, y] = cv2.fitLine(points, cv2.DIST_L2, 0, 0.01, 0.01) | |
| averaged_lines.append((vx, vy, x, y)) | |
| return averaged_lines | |
| def get_square_color(image, box): | |
| # Determine the size of the square region | |
| square_size = (box[1][0] - box[0][0]) / 3 | |
| # Determine the coordinates of the square region inside the box | |
| top_left = (box[0][0] + square_size, box[0][1] + square_size) | |
| bottom_right = (box[0][0] + square_size*2, box[0][1] + square_size*2) | |
| # Extract the square region from the image | |
| square_region = image[int(top_left[1]):int(bottom_right[1]), int(top_left[0]):int(bottom_right[0])] | |
| # Calculate the mean pixel value of the square region | |
| mean_value = np.mean(square_region) | |
| # Determine whether the square region is predominantly black or white | |
| if mean_value < 128: | |
| square_color = "." | |
| else: | |
| square_color = "A" | |
| return square_color | |
| # accepts image in grayscale | |
| def extract_grid(image): | |
| # Apply Gaussian blur to reduce noise and improve edge detection | |
| blurred = cv2.GaussianBlur(image, (3, 3), 0) | |
| # Apply Canny edge detection | |
| edges = cv2.Canny(blurred, 50, 150) | |
| # Apply dilation to connect nearby edges and make them more contiguous | |
| kernel = np.ones((5, 5), np.uint8) | |
| dilated = cv2.dilate(edges, kernel, iterations=1) | |
| # # Applying canny edge detector | |
| # detecting contours on the canny image | |
| contours, _ = cv2.findContours(dilated, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE) | |
| # sorting the contours by the descending order area of the contour | |
| sorted_contours = list(sorted(contours, key=cv2.contourArea,reverse=True)) | |
| # filtering out the top 10 largest by applying NMS and only selecting square ones (Apsect ratio 1) | |
| filtered_contours = filter_contours(image, sorted_contours[0:10],iou_threshold=0.6,asp_ratio=1,tolerance=0.2) | |
| # largest Contour Extraction | |
| largest_contour = [] | |
| if(len(filtered_contours)): | |
| largest_contour = filtered_contours[0] | |
| else: | |
| largest_contour = sorted_contours[0] | |
| # --- Performing Perspective warp of the largest contour --- | |
| coordinates_list = [] | |
| if(largest_contour.shape != (4,1,2)): | |
| largest_contour = cv2.convexHull(largest_contour) | |
| if(largest_contour.shape != (4,1,2)): | |
| rect = cv2.minAreaRect(largest_contour) | |
| largest_contour = cv2.boxPoints(rect) | |
| largest_contour = largest_contour.astype('int') | |
| coordinates_list = largest_contour.reshape(4, 2).tolist() | |
| # Convert coordinates_list to a numpy array | |
| coordinates_array = np.array(coordinates_list) | |
| # Find the convex hull of the points | |
| hull = cv2.convexHull(coordinates_array) | |
| # Find the extreme points of the convex hull | |
| extreme_points = np.squeeze(hull) | |
| # Sort the extreme points by their x and y coordinates to determine the order | |
| sorted_points = extreme_points[np.lexsort((extreme_points[:, 1], extreme_points[:, 0]))] | |
| # Extract top left, bottom right, top right, and bottom left points | |
| tl = sorted_points[0] | |
| tr = sorted_points[1] | |
| bl = sorted_points[2] | |
| br = sorted_points[3] | |
| if(tr[1] < tl[1]): | |
| tl,tr = tr,tl | |
| if(br[1] < bl[1]): | |
| bl,br = br,bl | |
| # Define pts1 | |
| pts1 = [tl, bl, tr, br] | |
| # Calculate the bounding rectangle coordinates | |
| x, y, w, h = 0,0,400,400 | |
| # Define pts2 as the corners of the bounding rectangle | |
| pts2 = [[3, 3], [400, 3], [3, 400], [400, 400]] | |
| # Calculate the perspective transformation matrix | |
| matrix = cv2.getPerspectiveTransform(np.float32(pts1), np.float32(pts2)) | |
| # Apply the perspective transformation to the cropped_image | |
| transformed_img = cv2.warpPerspective(image, matrix, (403, 403)) | |
| cropped_image = transformed_img.copy() | |
| # if the largest contour was not exactly quadilateral | |
| # -- Performing Hough Transform -- | |
| similarity_threshold = math.floor(w/30) # Thresholds for filtering Similar Hough Lines | |
| # Applying Gaussian Blur to reduce noice and improve dege detection | |
| blurred = cv2.GaussianBlur(cropped_image, (5, 5), 0) | |
| # Perform Canny edge detection on the GrayScale Image | |
| edges = cv2.Canny(blurred, 50, 150) | |
| lines = cv2.HoughLines(edges, 1, np.pi/180, 200) | |
| # Filter out similar lines | |
| filtered_lines = [] | |
| for line in lines: | |
| for r_theta in lines: | |
| arr = np.array(r_theta[0], dtype=np.float64) | |
| rho, theta = arr | |
| is_similar = False | |
| for filtered_line in filtered_lines: | |
| filtered_rho, filtered_theta = filtered_line | |
| # similarity threshold is 10 | |
| if abs(rho - filtered_rho) < similarity_threshold and abs(theta - filtered_theta) < np.pi/180 * similarity_threshold: | |
| is_similar = True | |
| break | |
| if not is_similar: | |
| filtered_lines.append((rho, theta)) | |
| # Filter out the horizontal and the vertical lines | |
| horizontal_lines = [] | |
| vertical_lines = [] | |
| for rho, theta in filtered_lines: | |
| a = np.cos(theta) | |
| b = np.sin(theta) | |
| x0 = a * rho | |
| y0 = b * rho | |
| x1 = int(x0 + 1000 * (-b)) | |
| y1 = int(y0 + 1000 * (a)) | |
| x2 = int(x0 - 1000 * (-b)) | |
| y2 = int(y0 - 1000 * (a)) | |
| slope = (y2 - y1) / (x2 - x1 + 0.0001) | |
| # do taninv(0.17) it is nearly equal to 10 | |
| if( abs(slope) <= 0.18 ): | |
| horizontal_lines.append((rho,theta)) | |
| elif (abs(slope) > 6): | |
| vertical_lines.append((rho,theta)) | |
| # Find the intersection points of horizontal and vertical lines | |
| hough_corners = [] | |
| for h_rho, h_theta in horizontal_lines: | |
| for v_rho, v_theta in vertical_lines: | |
| x, y = parametricIntersect(h_rho, h_theta, v_rho, v_theta) | |
| if x is not None and y is not None: | |
| hough_corners.append((x, y)) | |
| # -- Performing Harris Corner Detection -- | |
| # Create CLAHE object with specified clip limit | |
| clahe = cv2.createCLAHE(clipLimit=3, tileGridSize=(8, 8)) | |
| clahe_image = clahe.apply(cropped_image) | |
| # harris corner detection for CLHAE IMAGE | |
| dst = cv2.cornerHarris(clahe_image,2,3,0.04) | |
| ret,dst = cv2.threshold(dst,0.1*dst.max(),255,0) | |
| dst = np.uint8(dst) | |
| dst = cv2.dilate(dst,None) | |
| ret, labels, stats, centroids = cv2.connectedComponentsWithStats(dst) | |
| criteria = (cv2.TERM_CRITERIA_EPS+cv2.TermCriteria_MAX_ITER,100,0.001) | |
| harris_corners = cv2.cornerSubPix(clahe_image,np.float32(centroids),(5,5),(-1,-1),criteria) | |
| drawn_image = cv2.cvtColor(cropped_image, cv2.COLOR_GRAY2BGR) | |
| for i in harris_corners: | |
| x,y = i | |
| image2 = cv2.circle(drawn_image, (int(x),int(y)), radius=0, color=(0, 0, 255), thickness=3) | |
| # -- Using Regression Model to approximate horizontal and vertical Lines | |
| # reducing to 0 decimal places | |
| corners1 = list(map(lambda coord: (round(coord[0], 0), round(coord[1], 0)), harris_corners)) | |
| # adding the corners obtained from hough transform | |
| corners1 += hough_corners | |
| # removing the duplicate corners | |
| corners_no_dup = list(set(corners1)) | |
| min_cell_width = w/30 | |
| min_cell_height = h/30 | |
| # grouping coordinates into probabale array that could fit a horizontal and vertical lien | |
| vertical_lines = group_lines(corners_no_dup,0,min_cell_height) | |
| horizontal_lines = group_lines(corners_no_dup,1,min_cell_height) | |
| actual_vertical_lines = fit_lines(vertical_lines) | |
| actual_horizontal_lines = fit_lines(horizontal_lines,is_horizontal=True) | |
| # Lines obtained from above method are not appropriate, we have to refine them | |
| x_probable = [i[1] for i in actual_horizontal_lines] # looking at the intercepts | |
| y_probable = [i[1] for i in actual_vertical_lines] | |
| del_x_avg = average_distance(x_probable) | |
| del_y_avg = average_distance(y_probable) | |
| averaged_horizontal_lines1 = [] # This step here is fishy and needs refinement | |
| averaged_vertical_lines1 = [] | |
| multiplier = 0.95 | |
| i = 0 | |
| while(1): | |
| averaged_horizontal_lines = average_out_similar_lines(actual_horizontal_lines,horizontal_lines,del_y_avg*multiplier,is_horizontal=True) | |
| averaged_vertical_lines = average_out_similar_lines(actual_vertical_lines,vertical_lines,del_x_avg*multiplier,is_horizontal=False) | |
| i += 1 | |
| if(i >= 20 or len(averaged_horizontal_lines) == len(averaged_vertical_lines)): | |
| break | |
| else: | |
| multiplier -= 0.05 | |
| averaged_horizontal_lines1 = average_out_similar_lines1(actual_horizontal_lines,horizontal_lines,del_y_avg*multiplier) | |
| averaged_vertical_lines1 = average_out_similar_lines1(actual_vertical_lines,vertical_lines,del_x_avg*multiplier) | |
| # plotting the lines to image to find the intersection points | |
| drawn_image6 = np.ones_like(cropped_image)*255 | |
| for vx,vy,cx,cy in averaged_horizontal_lines1 + averaged_vertical_lines1: | |
| w = cropped_image.shape[1] | |
| cv2.line(drawn_image6, (int(cx-vx*w), int(cy-vy*w)), (int(cx+vx*w), int(cy+vy*w)), (0, 0, 255),1,cv2.LINE_AA) | |
| # -- Finding Intersection points -- | |
| # Applying Harris Corner Detection to find the intersection points | |
| mesh_image = drawn_image6.copy() | |
| dst = cv2.cornerHarris(mesh_image,2,3,0.04) | |
| ret,dst = cv2.threshold(dst,0.1*dst.max(),255,0) | |
| dst = np.uint8(dst) | |
| dst = cv2.dilate(dst,None) | |
| ret, labels, stats, centroids = cv2.connectedComponentsWithStats(dst) | |
| criteria = (cv2.TERM_CRITERIA_EPS+cv2.TermCriteria_MAX_ITER,100,0.001) | |
| harris_corners = cv2.cornerSubPix(mesh_image,np.float32(centroids),(5,5),(-1,-1),criteria) | |
| drawn_image = cv2.cvtColor(drawn_image6, cv2.COLOR_GRAY2BGR) | |
| harris_corners = list(sorted(harris_corners[1:],key = lambda x : x[1])) | |
| # -- Finding out the grid color -- | |
| grayscale = cropped_image.copy() | |
| # Perform adaptive thresholding to obtain binary image | |
| _, binary = cv2.threshold(grayscale, 128, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU) | |
| # Perform morphological operations to remove small text regions | |
| kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3)) | |
| binary = cv2.morphologyEx(binary, cv2.MORPH_ELLIPSE, kernel, iterations=1) | |
| # Invert the binary image | |
| inverted_binary = cv2.bitwise_not(binary) | |
| # Restore the image by blending the inverted binary image with the grayscale image | |
| restored_image = cv2.bitwise_or(inverted_binary, grayscale) | |
| # Apply morphological opening to remove small black dots | |
| kernel_opening = np.ones((3, 3), np.uint8) | |
| opened_image = cv2.morphologyEx(restored_image, cv2.MORPH_OPEN, kernel_opening, iterations=1) | |
| # Apply morphological closing to further refine the restored image | |
| kernel_closing = np.ones((5, 5), np.uint8) | |
| refined_image = cv2.morphologyEx(opened_image, cv2.MORPH_CLOSE, kernel_closing, iterations=1) | |
| # finding out the grid corner | |
| grid = [] | |
| grid_nums = [] | |
| across_clue_num = [] | |
| down_clue_num = [] | |
| # extracting the grid color and filling up the grid variable | |
| sorted_corners = np.array(list(sorted(harris_corners,key=lambda x:x[1]))) | |
| if(len(sorted_corners) == len(averaged_horizontal_lines1) * len(averaged_vertical_lines1)): | |
| sorted_corners_grouped = [] | |
| for i in range(0,len(sorted_corners),len(averaged_vertical_lines1)): | |
| temp_arr = sorted_corners[i:i+len(averaged_vertical_lines1)] | |
| temp_arr = list(sorted(temp_arr,key=lambda x: x[0])) | |
| sorted_corners_grouped.append(temp_arr) | |
| for h_line_idx in range(0,len(sorted_corners_grouped)-1): | |
| for corner_idx in range(0,len(sorted_corners_grouped[h_line_idx])-1): | |
| # grabbing the four box coordinates | |
| box = [sorted_corners_grouped[h_line_idx][corner_idx],sorted_corners_grouped[h_line_idx][corner_idx+1], | |
| sorted_corners_grouped[h_line_idx+1][corner_idx],sorted_corners_grouped[h_line_idx+1][corner_idx+1]] | |
| grid.append(get_square_color(refined_image,box)) | |
| grid_formatted = [] | |
| for i in range(0, len(grid), len(averaged_vertical_lines1) - 1): | |
| grid_formatted.append(grid[i:i + len(averaged_vertical_lines1) - 1]) | |
| # if (x,y) is present in these array the cell (x,y) is already accounted as a part of answer of across or down | |
| in_horizontal = [] | |
| in_vertical = [] | |
| num = 0 | |
| for x in range(0, len(averaged_vertical_lines1) - 1): | |
| for y in range(0, len(averaged_horizontal_lines1) - 1): | |
| # if the cell is black there's no need to number | |
| if grid_formatted[x][y] == '.': | |
| grid_nums.append(0) | |
| continue | |
| # if the cell is part of both horizontal and vertical cell then there's no need to number | |
| horizontal_presence = (x, y) in in_horizontal | |
| vertical_presence = (x, y) in in_vertical | |
| # present in both 1 1 | |
| if horizontal_presence and vertical_presence: | |
| grid_nums.append(0) | |
| continue | |
| # present in one i.e 1 0 | |
| if not horizontal_presence and vertical_presence: | |
| horizontal_length = 0 | |
| temp_horizontal_arr = [] | |
| # iterate in x direction until the end of the grid or until a black box is found | |
| while x + horizontal_length < len(averaged_horizontal_lines1) - 1 and grid_formatted[x + horizontal_length][y] != '.': | |
| temp_horizontal_arr.append((x + horizontal_length, y)) | |
| horizontal_length += 1 | |
| # if horizontal length is greater than 1, then append the temp_horizontal_arr to in_horizontal array | |
| if horizontal_length > 1: | |
| in_horizontal.extend(temp_horizontal_arr) | |
| num += 1 | |
| across_clue_num.append(num) | |
| grid_nums.append(num) | |
| continue | |
| grid_nums.append(0) | |
| # present in one 1 0 | |
| if not vertical_presence and horizontal_presence: | |
| # do the same for vertical | |
| vertical_length = 0 | |
| temp_vertical_arr = [] | |
| # iterate in y direction until the end of the grid or until a black box is found | |
| while y + vertical_length < len(averaged_vertical_lines1) - 1 and grid_formatted[x][y+vertical_length] != '.': | |
| temp_vertical_arr.append((x, y+vertical_length)) | |
| vertical_length += 1 | |
| # if vertical length is greater than 1, then append the temp_vertical_arr to in_vertical array | |
| if vertical_length > 1: | |
| in_vertical.extend(temp_vertical_arr) | |
| num += 1 | |
| down_clue_num.append(num) | |
| grid_nums.append(num) | |
| continue | |
| grid_nums.append(0) | |
| if(not horizontal_presence and not vertical_presence): | |
| horizontal_length = 0 | |
| temp_horizontal_arr = [] | |
| # iterate in x direction until the end of the grid or until a black box is found | |
| while x + horizontal_length < len(averaged_horizontal_lines1) - 1 and grid_formatted[x + horizontal_length][y] != '.': | |
| temp_horizontal_arr.append((x + horizontal_length, y)) | |
| horizontal_length += 1 | |
| # if horizontal length is greater than 1, then append the temp_horizontal_arr to in_horizontal array | |
| # do the same for vertical | |
| vertical_length = 0 | |
| temp_vertical_arr = [] | |
| # iterate in y direction until the end of the grid or until a black box is found | |
| while y + vertical_length < len(averaged_vertical_lines1) - 1 and grid_formatted[x][y+vertical_length] != '.': | |
| temp_vertical_arr.append((x, y+vertical_length)) | |
| vertical_length += 1 | |
| # if vertical length is greater than 1, then append the temp_vertical_arr to in_vertical array | |
| if horizontal_length > 1 and horizontal_length > 1: | |
| in_horizontal.extend(temp_horizontal_arr) | |
| in_vertical.extend(temp_vertical_arr) | |
| num += 1 | |
| across_clue_num.append(num) | |
| down_clue_num.append(num) | |
| grid_nums.append(num) | |
| elif vertical_length > 1: | |
| in_vertical.extend(temp_vertical_arr) | |
| num += 1 | |
| down_clue_num.append(num) | |
| grid_nums.append(num) | |
| elif horizontal_length > 1: | |
| in_horizontal.extend(temp_horizontal_arr) | |
| num += 1 | |
| across_clue_num.append(num) | |
| grid_nums.append(num) | |
| else: | |
| grid_nums.append(0) | |
| size = { 'rows' : len(averaged_horizontal_lines1)-1, | |
| 'cols' : len(averaged_vertical_lines1)-1, | |
| } | |
| dict = { | |
| 'size' : size, | |
| 'grid' : grid, | |
| 'gridnums': grid_nums, | |
| 'across_nums': down_clue_num, | |
| 'down_nums' : across_clue_num, | |
| } | |
| return dict | |
| if __name__ == "__main__": | |
| img = cv2.imread("D:\\D\\Major Project files\\opencv\\movie.png",0) | |
| down = extract_grid(img) | |
| print(down) | |
| # img = Image.open("chalena3.jpg") | |
| # img_gray = img.convert("L") | |
| # print(extract_grid(img_gray)) | |