i want to detect arrows on this map and highlight them with red. But i have no idea how to do as i havent done any of cv work. If anyone can help i would really appreciate that
i tried calculating area of a single arrow and tried match template of an template arrow png but it is detecting text as well as few logos of map.I feel the arrows on map have no angle + same size(i guess) and in map 3,4 arrows are also present together.
import rasterio
import numpy as np
import cv2
import matplotlib.pyplot as plt
import shapefile
from skimage.io import imread
def calculate_angle(p1, p2, p3):
"""Calculate the angle between three points."""
vector1 = np.array(p1) - np.array(p2)
vector2 = np.array(p3) - np.array(p2)
angle = np.arccos(np.dot(vector1, vector2) / (np.linalg.norm(vector1) * np.linalg.norm(vector2)))
return np.degrees(angle)
def detect_arrows(tiff_path):
# Step 1: Load the TIFF file (Grayscale)
with rasterio.open(tiff_path) as src:
img = src.read(1) # Read the first band (grayscale)
transform = src.transform # To convert pixel to geographic coordinates
# Normalize for better visibility
img = cv2.normalize(img, None, 0, 255, cv2.NORM_MINMAX, dtype=cv2.CV_8U)
# Step 2: Preprocessing
blurred = cv2.GaussianBlur(img, (5, 5), 0)
edges = cv2.Canny(blurred, 80, 150)
# Step 3: Morphological closing to remove small holes
kernel = np.ones((5, 5), np.uint8)
closed = cv2.morphologyEx(edges, cv2.MORPH_CLOSE, kernel)
# Step 4: Find contours
contours, _ = cv2.findContours(closed, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# Step 5: Filter only arrow-like shapes
arrow_points = []
arrow_contours = []
for cnt in contours:
approx = cv2.approxPolyDP(cnt, 0.02 * cv2.arcLength(cnt, True), True)
x, y, w, h = cv2.boundingRect(cnt)
bbox_area = w * h
if bbox_area >= 500: # Ignore large objects
continue
aspect_ratio = float(w) / h
contour_area = cv2.contourArea(cnt)
# Filtering based on aspect ratio to avoid text-like structures
if aspect_ratio > 5 or aspect_ratio < 0.2:
continue
if 0.5 <= aspect_ratio <= 2.5 and len(approx) >= 7 and contour_area > 50:
sharp_angles = 0
for i in range(len(approx)):
prev_point = approx[i - 1]
current_point = approx[i]
next_point = approx[(i + 1) % len(approx)]
angle = calculate_angle(prev_point[0], current_point[0], next_point[0])
if 30 <= angle <= 120:
sharp_angles += 1
if sharp_angles >= 3: # Arrows typically have sharp edges
arrow_contours.append(cnt)
# Get centroid of arrow
M = cv2.moments(cnt)
if M["m00"] != 0:
cx = int(M["m10"] / M["m00"])
cy = int(M["m01"] / M["m00"])
arrow_points.append((cx, cy))
# Step 6: Convert pixel coordinates to geographic coordinates
geo_points = []
with rasterio.open(tiff_path) as src:
for x, y in arrow_points:
lon, lat = src.xy(y, x) # Convert pixel (x, y) to geo-coordinates
geo_points.append((lon, lat))
# Step 7: Draw detected arrows
img_marked = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
for point in arrow_points:
cv2.circle(img_marked, point, 5, (0, 0, 255), -1)
# Show the final detection result
plt.imshow(img_marked)
plt.title("Detected Arrows")
plt.show()
return geo_points
def save_as_shapefile(geo_points, shp_path):
"""Save detected arrow points as a shapefile."""
w = shapefile.Writer(shp_path)
w.field("ID", "N")
for i, (lon, lat) in enumerate(geo_points):
w.point(lon, lat)
w.record(i)
w.close()
print(f"Shapefile saved at: {shp_path}")
# Run detection and save results
tiff_path = "Mumbai_32643.tif"
shp_path = "arrows_detected.shp"
geo_points = detect_arrows(tiff_path)
save_as_shapefile(geo_points, shp_path)
output i am getting