Saturday, October 4, 2025

 One of the shortcomings of drone and unmanned aerial vehicles that other aircrafts don’t have is their trackability. For example, it is easy to get all kinds of airport and flight information with FlightAware AeroAPI such as the following but unless the UAV are part of the same swarm, there is little or no communication.  

API that demonstrates all flight activity at a point of interest: 

import requests 

import os 

from datetime import datetime 

 

AEROAPI_KEY = os.getenv("AEROAPI_KEY")   

AEROAPI_LOCATION = os.getenv("AEROAPI_LOCATION") 

def next_flights_at_kapa(type_="arrivals", how_many=1): 

    """ 

    Fetches the next arrival or departure flights at a specific location 

    Args: 

        type_: "arrivals" or "departures" 

        how_many: Number of flights to fetch 

    """ 

    headers = {"x-apikey": f"{AEROAPI_KEY}", "Accept": "application/json; charset=UTF-8"} 

    url = f"{AEROAPI_BASE_URL}/airports/{AEROAPI_LOCATION}/flights/{type_}?max_pages={how_many}" 

    response = requests.get(url, headers=headers) 

    response.raise_for_status() 

    flights = response.json().get(type_, []) 

    print(f"Count of flights: {len(flights)}") 

    for flight in flights: 

      if flight: 

        print( 

            f"Flight {flight['ident']} {type_[:-1]}: " 

            f"{flight.get('origin', {}).get('code', '') if type_ == 'arrivals' else flight.get('destination', {}).get('code', '') if flight.get('destination') else None} " 

            f"{flight.get('origin', {}).get('city', '') if type_ == 'arrivals' else flight.get('destination', {}).get('city', '') if flight.get('destination') else None} " 

            f"Scheduled: {flight.get('scheduled_in' if type_=='arrivals' else 'scheduled_out')}, " 

            f"Status: {flight.get('status', 'Unknown')}" 

        ) 

 

# Example usage: fetch the next arrival 

next_flights_at_kapa("arrivals", how_many=1) 

# Or for departures 

next_flights_at_kapa("departures", how_many=1) 

 

Federation of swarms allows for possibilities to share information otherwise restricted to the swarm but there must be interoperability standards or protocols over and on top of RFCs for networking stack. 

Friday, October 3, 2025

 Counting parking spots

Just like analytics of objects detected in aerial drone images is done just-in-time of query prompting and not for every drone image extracted from a drone video, unoccupied parking spots qualify as reasonable detections to be asked in a query. The corresponding analytics could take one of the following approaches:

1. Using color segmentation, contour detection and simple heuristics assuming that parking spots are regions in the parking lot not covered by cars.

import cv2

import numpy as np

def count_empty_parking_spots(image_path, debug=False):

    image = cv2.imread(image_path)

    hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)

    # Segment pavement (brightness, not car colors)

    # Pavement is usually light gray under sunlight in aerial images

    lower_pavement = np.array([0, 0, 180]) # min hue/sat, high V (brightness)

    upper_pavement = np.array([180, 35, 255]) # low saturation to ignore cars

    mask_pavement = cv2.inRange(hsv, lower_pavement, upper_pavement)

    # Remove small noise

    kernel = np.ones((7, 7), np.uint8)

    mask_clean = cv2.morphologyEx(mask_pavement, cv2.MORPH_OPEN, kernel)

    mask_clean = cv2.morphologyEx(mask_clean, cv2.MORPH_CLOSE, kernel)

    # Adaptive thresholding as backup (if pavement color varies)

    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

    adaptive = cv2.adaptiveThreshold(

        gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 25, -14)

    combined = cv2.bitwise_and(mask_clean, adaptive)

    # Find contours (Connected bright regions interpreted as parking spots)

    contours, _ = cv2.findContours(combined, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

    empty_count = 0

    display = image.copy()

    # Filter contours: parking spots should be rectangular and large enough

    for contour in contours:

        area = cv2.contourArea(contour)

        x, y, w, h = cv2.boundingRect(contour)

        aspect = float(w) / float(h) if h > 0 else 0

        # Tune these heuristics to ignore stray bright patches & small noise

        if 400 < area < 2500 and 1.3 < aspect < 2.7 and w > 20 and h > 11:

            empty_count += 1

            cv2.rectangle(display, (x, y), (x+w, y+h), (0,255,0), 2)

    print(f"Detected {empty_count} empty parking spots")

    if debug:

        cv2.imshow("Parking Detection", display)

        cv2.imshow("Mask", mask_clean)

        cv2.imshow("Adaptive", adaptive)

        cv2.imshow("Combined", combined)

        cv2.imwrite(image_path.replace(".jpg","")+"-detectedspots.jpg", display)

        cv2.waitKey(0)

        cv2.destroyAllWindows()

    return empty_count

# Example usage for your two frames:

count1 = count_empty_parking_spots("parking1.jpg", debug=True)

count2 = count_empty_parking_spots("parking2.jpg", debug=True)

print(f"parking1.jpg: {count1} empty spots (should be about 2)")

print(f"parking2.jpg: {count2} empty spot (should be about 1)")

"""

Results: Detected 1 empty parking spots

Detected 1 empty parking spots

parking1.jpg: 1 empty spots (should be about 2)

parking2.jpg: 1 empty spot (should be about 1)

"""

2. Using YOLOv5 for speed, precision and adaptability to aerial views:

import torch

import cv2

import numpy as np

from matplotlib import pyplot as plt

# Load YOLOv5 model (you can use 'yolov5s', 'yolov5m', or a custom-trained model)

model = torch.hub.load('ultralytics/yolov5', 'yolov5s', pretrained=True)

# Load image

image_path = 'parking2.jpg'

img = cv2.imread(image_path)

img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

# Run inference

results = model(img_rgb)

# Parse results

detections = results.pandas().xyxy[0]

cars = detections[detections['name'] == 'car']

# Optional: If you have predefined parking spot coordinates

# Define parking spots manually or load from a JSON file

parking_spots = [

     {'id': 1, 'bbox': [815, 184, 992, 387]},

     {'id': 2, 'bbox': [230, 502, 273, 619]},

     {'id': 3, 'bbox': [427, 8, 468, 691]},

    # Add more spots...

]

# Check overlap between cars and parking spots

def is_occupied(spot_bbox, car_bboxes):

    x1, y1, x2, y2 = spot_bbox

    spot_area = (x2 - x1) * (y2 - y1)

    for _, car in car_bboxes.iterrows():

        cx1, cy1, cx2, cy2 = car[['xmin', 'ymin', 'xmax', 'ymax']]

        ix1, iy1 = max(x1, cx1), max(y1, cy1)

        ix2, iy2 = min(x2, cx2), min(y2, cy2)

        iw, ih = max(0, ix2 - ix1), max(0, iy2 - iy1)

        intersection = iw * ih

        if intersection / spot_area > 0.3: # Threshold for overlap

            return True

    return False

# Count unoccupied spots

unoccupied_count = 0

for spot in parking_spots:

    if not is_occupied(spot['bbox'], cars):

        unoccupied_count += 1

print(f"Unoccupied parking spots: {unoccupied_count}")

The results of the second option appear as

YOLOv5 2025-7-21 Python-3.13.1 torch-2.7.1+cpu CPU

Fusing layers...

YOLOv5s summary: 213 layers, 7225885 parameters, 0 gradients, 16.4 GFLOPs

Adding AutoShape...

C:\Users\ravib/.cache\torch\hub\ultralytics_yolov5_master\models\common.py:906: FutureWarning: `torch.cuda.amp.autocast(args...)` is deprecated. Please use `torch.amp.autocast('cuda', args...)` instead.

  with amp.autocast(autocast):

Unoccupied parking spots: 3

#codingexercise: https://1drv.ms/w/c/d609fb70e39b65c8/EcmsFKLaR-hJqkh7Kz7vaHIB_SkOxADjDoskhScjPYa9kA?e=HwP9gp