Detect arrows in map using open cv

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

since that is a digitally generated map, you could just look at how it was made, and alter how it was made, or extract the information that must be there, before it is drawn.

1 Like

actually its a tiff file a raster image rather than a vector-based image so cant extract info

my point being, some program drew the map, so there is a chance that you can spare yourself the extra effort of scraping data using CV, that already existed previously.

crosspost:

https://stackoverflow.com/staging-ground/79411198

1 Like

Thanks for the response.There no data in there ! It’s just a simple map

I guess you are confused the image is the output image after applying open cv processing like gaussian blur, grayscale conversion I have attached the code