-
Notifications
You must be signed in to change notification settings - Fork 0
/
scale.py
149 lines (121 loc) · 5.08 KB
/
scale.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
import cv2
import numpy as np
import pytesseract
import re
import csv
def load_image(image_path):
"""Load an image in grayscale and color formats."""
gray = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
color = cv2.imread(image_path, cv2.IMREAD_COLOR)
return gray, color
def find_horizontal_scale_bar_with_text_color(gray_image, mode="dark"):
"""
Detect and measure a horizontal scale bar (black or white) with its text.
"""
if mode == "dark":
_, binary = cv2.threshold(gray_image, 50, 255, cv2.THRESH_BINARY_INV)
elif mode == "light":
_, binary = cv2.threshold(gray_image, 200, 255, cv2.THRESH_BINARY)
else:
raise ValueError("Invalid mode. Choose 'dark' or 'light'.")
contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
horizontal_bars = []
for contour in contours:
x, y, w, h = cv2.boundingRect(contour)
aspect_ratio = w / h
if aspect_ratio > 5:
horizontal_bars.append((w, x, y, w, h))
if horizontal_bars:
horizontal_bars = sorted(horizontal_bars, key=lambda b: b[0], reverse=True)
selected_bar = horizontal_bars[0]
x, y, w, h = selected_bar[1:]
text_region_above = gray_image[max(0, y - 50) : y, x : x + w]
text_region_below = gray_image[y + h : y + h + 50, x : x + w]
return selected_bar, text_region_above, text_region_below
return None, None, None
def validate_text_and_extract_value(scale_text):
"""Validate the text and extract the numeric value if valid."""
match = re.match(r"^\d+\.?\d*", scale_text) # Must start with a numeric value
if match:
return float(match.group()) # Extract numeric value as float
return None
def annotate_image_with_scale_and_text(color_image, x, y, w, h, text, point1, point2):
"""Draw a rectangle around the detected scale bar, display the text, and annotate points."""
annotated_image = color_image.copy()
cv2.rectangle(annotated_image, (x, y), (x + w, y + h), (0, 255, 0), 2)
cv2.putText(
annotated_image,
f"Scale: {text}",
(x, y - 10 if y - 10 > 10 else y + h + 20),
cv2.FONT_HERSHEY_SIMPLEX,
0.6,
(0, 255, 0),
2,
)
for idx, point in enumerate([point1, point2], start=1):
cv2.circle(annotated_image, point, 5, (0, 0, 255), -1)
cv2.putText(
annotated_image,
f"P{idx}: {point}",
(point[0] + 10, point[1] - 10),
cv2.FONT_HERSHEY_SIMPLEX,
0.5,
(255, 0, 0),
1,
)
return annotated_image
def measure_distance_between_points(
point1, point2, scale_bar_length_pixels, scale_bar_mm
):
"""Calculate the real-world distance between two points given the scale bar information."""
pixel_distance = np.sqrt(
(point2[0] - point1[0]) ** 2 + (point2[1] - point1[1]) ** 2
)
pixel_to_mm_ratio = scale_bar_mm / scale_bar_length_pixels
return pixel_distance * pixel_to_mm_ratio
def read_scale(text_region_above, text_region_below):
"""Read the text above and below the scale bar."""
scale_text = pytesseract.image_to_string(
text_region_above, config="--psm 6"
).strip()
scale_value = validate_text_and_extract_value(scale_text)
if scale_value is None:
scale_text = pytesseract.image_to_string(
text_region_below, config="--psm 6"
).strip()
scale_value = validate_text_and_extract_value(scale_text)
if scale_value is None:
raise ValueError(
f"Could not find a valid scale value. Detected text: {scale_text}"
)
return scale_value
def find_horizontal_scale_bar_with_text(gray_image):
scale_bar_info, text_region_above, text_region_below = (
find_horizontal_scale_bar_with_text_color(gray_image, mode="dark")
)
if not scale_bar_info:
scale_bar_info, text_region_above, text_region_below = (
find_horizontal_scale_bar_with_text_color(gray_image, mode="light")
)
return scale_bar_info, text_region_above, text_region_below
def give_length_scale(image_path, point1, point2):
gray_image, color_image = load_image(image_path)
scale_bar_info, text_region_above, text_region_below = (
find_horizontal_scale_bar_with_text(gray_image)
)
if scale_bar_info:
scale_bar_length, x, y, w, h = scale_bar_info
scale_value = read_scale(text_region_above, text_region_below)
real_distance = measure_distance_between_points(
point1, point2, scale_bar_length, scale_value
)
return real_distance
else:
return None
if __name__ == "__main__":
#image_path = "/Users/massirashidi/original/08costa-1723_p_1.jpg"
image_path = "/Users/massirashidi/Library/Mobile Documents/com~apple~CloudDocs/CS/machine_learning/ml-project-2-jmw/jtlc000008642_p_1.jpg"
point1 = (415, 355)
point2 = (975, 415)
real_distance = give_length_scale(image_path, point1, point2, print_image=True)
print(f"The real-world distance between the two points is: {real_distance} mm.")