Clean push: Removed heavy files & added only latest snapshot
This commit is contained in:
649
qt_app_pyside1/utils/crosswalk_utils1.py
Normal file
649
qt_app_pyside1/utils/crosswalk_utils1.py
Normal file
@@ -0,0 +1,649 @@
|
||||
print("🟡 [CROSSWALK_UTILS]1111 This is d:/Downloads/finale6/Khatam final/khatam/qt_app_pyside/utils/crosswalk_utils.py LOADED")
|
||||
import cv2
|
||||
import numpy as np
|
||||
from typing import Tuple, Optional
|
||||
|
||||
def detect_crosswalk_and_violation_line(frame: np.ndarray, traffic_light_position: Optional[Tuple[int, int]] = None, perspective_M: Optional[np.ndarray] = None):
|
||||
"""
|
||||
Detects crosswalk (zebra crossing) or fallback stop line in a traffic scene using classical CV.
|
||||
Args:
|
||||
frame: BGR image frame from video feed
|
||||
traffic_light_position: Optional (x, y) of traffic light in frame
|
||||
perspective_M: Optional 3x3 homography matrix for bird's eye view normalization
|
||||
Returns:
|
||||
result_frame: frame with overlays (for visualization)
|
||||
crosswalk_bbox: (x, y, w, h) or None if fallback used
|
||||
violation_line_y: int (y position for violation check)
|
||||
debug_info: dict (for visualization/debugging)
|
||||
"""
|
||||
debug_info = {}
|
||||
orig_frame = frame.copy()
|
||||
h, w = frame.shape[:2]
|
||||
|
||||
# 1. Perspective Normalization (Bird's Eye View)
|
||||
if perspective_M is not None:
|
||||
frame = cv2.warpPerspective(frame, perspective_M, (w, h))
|
||||
debug_info['perspective_warped'] = True
|
||||
else:
|
||||
debug_info['perspective_warped'] = False
|
||||
|
||||
# 1. White Color Filtering (relaxed)
|
||||
mask_white = cv2.inRange(frame, (160, 160, 160), (255, 255, 255))
|
||||
debug_info['mask_white_ratio'] = np.sum(mask_white > 0) / (h * w)
|
||||
|
||||
# 2. Grayscale for adaptive threshold
|
||||
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
|
||||
# Enhance contrast for night/low-light
|
||||
if np.mean(gray) < 80:
|
||||
gray = cv2.equalizeHist(gray)
|
||||
debug_info['hist_eq'] = True
|
||||
else:
|
||||
debug_info['hist_eq'] = False
|
||||
# 5. Adaptive threshold (tuned)
|
||||
thresh = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
|
||||
cv2.THRESH_BINARY, 15, 5)
|
||||
# Combine with color mask
|
||||
combined = cv2.bitwise_and(thresh, mask_white)
|
||||
# 2. Morphology (tuned)
|
||||
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (25, 3))
|
||||
morph = cv2.morphologyEx(combined, cv2.MORPH_CLOSE, kernel, iterations=1)
|
||||
# Find contours
|
||||
contours, _ = cv2.findContours(morph, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
||||
zebra_rects = []
|
||||
for cnt in contours:
|
||||
x, y, w, h = cv2.boundingRect(cnt)
|
||||
aspect_ratio = w / max(h, 1)
|
||||
area = w * h
|
||||
angle = 0 # For simplicity, assume horizontal stripes
|
||||
# Heuristic: wide, short, and not too small
|
||||
if aspect_ratio > 3 and 1000 < area < 0.5 * frame.shape[0] * frame.shape[1] and h < 60:
|
||||
zebra_rects.append((x, y, w, h, angle))
|
||||
cv2.rectangle(orig_frame, (x, y), (x+w, y+h), (0, 255, 0), 2)
|
||||
# --- Overlay drawing for debugging: draw all zebra candidates ---
|
||||
for r in zebra_rects:
|
||||
x, y, rw, rh, _ = r
|
||||
cv2.rectangle(orig_frame, (x, y), (x+rw, y+rh), (0, 255, 0), 2)
|
||||
# Draw all zebra candidate rectangles for debugging (no saving)
|
||||
for r in zebra_rects:
|
||||
x, y, rw, rh, _ = r
|
||||
cv2.rectangle(orig_frame, (x, y), (x+rw, y+rh), (0, 255, 0), 2)
|
||||
# --- Probabilistic Scoring for Groups ---
|
||||
def group_score(group):
|
||||
if len(group) < 3:
|
||||
return 0
|
||||
heights = [r[3] for r in group]
|
||||
x_centers = [r[0] + r[2]//2 for r in group]
|
||||
angles = [r[4] for r in group]
|
||||
# Stripe count (normalized)
|
||||
count_score = min(len(group) / 6, 1.0)
|
||||
# Height consistency
|
||||
height_score = 1.0 - min(np.std(heights) / (np.mean(heights) + 1e-6), 1.0)
|
||||
# X-center alignment
|
||||
x_score = 1.0 - min(np.std(x_centers) / (w * 0.2), 1.0)
|
||||
# Angle consistency (prefer near 0 or 90)
|
||||
mean_angle = np.mean([abs(a) for a in angles])
|
||||
angle_score = 1.0 - min(np.std(angles) / 10.0, 1.0)
|
||||
# Whiteness (mean mask_white in group area)
|
||||
whiteness = 0
|
||||
for r in group:
|
||||
x, y, rw, rh, _ = r
|
||||
whiteness += np.mean(mask_white[y:y+rh, x:x+rw]) / 255
|
||||
whiteness_score = whiteness / len(group)
|
||||
# Final score (weighted sum)
|
||||
score = 0.25*count_score + 0.2*height_score + 0.2*x_score + 0.15*angle_score + 0.2*whiteness_score
|
||||
return score
|
||||
# 4. Dynamic grouping tolerance
|
||||
y_tolerance = int(h * 0.05)
|
||||
crosswalk_bbox = None
|
||||
violation_line_y = None
|
||||
best_score = 0
|
||||
best_group = None
|
||||
if len(zebra_rects) >= 3:
|
||||
zebra_rects = sorted(zebra_rects, key=lambda r: r[1])
|
||||
groups = []
|
||||
group = [zebra_rects[0]]
|
||||
for rect in zebra_rects[1:]:
|
||||
if abs(rect[1] - group[-1][1]) < y_tolerance:
|
||||
group.append(rect)
|
||||
else:
|
||||
if len(group) >= 3:
|
||||
groups.append(group)
|
||||
group = [rect]
|
||||
if len(group) >= 3:
|
||||
groups.append(group)
|
||||
# Score all groups
|
||||
scored_groups = [(group_score(g), g) for g in groups if group_score(g) > 0.1]
|
||||
print(f"[CROSSWALK DEBUG] scored_groups: {[s for s, _ in scored_groups]}")
|
||||
if scored_groups:
|
||||
scored_groups.sort(reverse=True, key=lambda x: x[0])
|
||||
best_score, best_group = scored_groups[0]
|
||||
print("Best group score:", best_score)
|
||||
# Visualization for debugging
|
||||
debug_vis = orig_frame.copy()
|
||||
for r in zebra_rects:
|
||||
x, y, rw, rh, _ = r
|
||||
cv2.rectangle(debug_vis, (x, y), (x+rw, y+rh), (255, 0, 255), 2)
|
||||
for r in best_group:
|
||||
x, y, rw, rh, _ = r
|
||||
cv2.rectangle(debug_vis, (x, y), (x+rw, y+rh), (0, 255, 255), 3)
|
||||
cv2.imwrite(f"debug_crosswalk_group.png", debug_vis)
|
||||
# Optionally, filter by vanishing point as before
|
||||
# ...existing vanishing point code...
|
||||
xs = [r[0] for r in best_group] + [r[0] + r[2] for r in best_group]
|
||||
ys = [r[1] for r in best_group] + [r[1] + r[3] for r in best_group]
|
||||
x1, x2 = min(xs), max(xs)
|
||||
y1, y2 = min(ys), max(ys)
|
||||
crosswalk_bbox = (x1, y1, x2 - x1, y2 - y1)
|
||||
violation_line_y = y2 - 5
|
||||
debug_info['crosswalk_group'] = best_group
|
||||
debug_info['crosswalk_score'] = best_score
|
||||
debug_info['crosswalk_angles'] = [r[4] for r in best_group]
|
||||
# --- Fallback: Stop line detection ---
|
||||
if crosswalk_bbox is None:
|
||||
edges = cv2.Canny(gray, 80, 200)
|
||||
lines = cv2.HoughLinesP(edges, 1, np.pi / 180, threshold=80, minLineLength=60, maxLineGap=20)
|
||||
stop_lines = []
|
||||
if lines is not None:
|
||||
for l in lines:
|
||||
x1, y1, x2, y2 = l[0]
|
||||
angle = np.degrees(np.arctan2(y2 - y1, x2 - x1))
|
||||
if abs(angle) < 20 or abs(angle) > 160: # horizontal
|
||||
if y1 > h // 2 or y2 > h // 2: # lower half
|
||||
stop_lines.append((x1, y1, x2, y2))
|
||||
debug_info['stop_lines'] = stop_lines
|
||||
print(f"[CROSSWALK DEBUG] stop_lines: {len(stop_lines)} found")
|
||||
if stop_lines:
|
||||
if traffic_light_position:
|
||||
tx, ty = traffic_light_position
|
||||
best_line = min(stop_lines, key=lambda l: abs(((l[1]+l[3])//2) - ty))
|
||||
else:
|
||||
best_line = max(stop_lines, key=lambda l: max(l[1], l[3]))
|
||||
x1, y1, x2, y2 = best_line
|
||||
crosswalk_bbox = None
|
||||
violation_line_y = min(y1, y2) - 5
|
||||
debug_info['stop_line'] = best_line
|
||||
print(f"[CROSSWALK DEBUG] using stop_line: {best_line}")
|
||||
# Draw fallback violation line overlay for debugging (no saving)
|
||||
if crosswalk_bbox is None and violation_line_y is not None:
|
||||
print(f"[DEBUG] Drawing violation line at y={violation_line_y} (frame height={orig_frame.shape[0]})")
|
||||
if 0 <= violation_line_y < orig_frame.shape[0]:
|
||||
orig_frame = draw_violation_line(orig_frame, violation_line_y, color=(0, 255, 255), thickness=8, style='solid', label='Fallback Stop Line')
|
||||
else:
|
||||
print(f"[WARNING] Invalid violation line position: {violation_line_y}")
|
||||
# --- Manual overlay for visualization pipeline test ---
|
||||
# Removed fake overlays that could overwrite the real violation line
|
||||
print(f"[CROSSWALK DEBUG] crosswalk_bbox: {crosswalk_bbox}, violation_line_y: {violation_line_y}")
|
||||
return orig_frame, crosswalk_bbox, violation_line_y, debug_info
|
||||
|
||||
def draw_violation_line(frame: np.ndarray, y: int, color=(0, 255, 255), thickness=8, style='solid', label='Violation Line'):
|
||||
"""
|
||||
Draws a thick, optionally dashed, labeled violation line at the given y-coordinate.
|
||||
Args:
|
||||
frame: BGR image
|
||||
y: y-coordinate for the line
|
||||
color: BGR color tuple
|
||||
thickness: line thickness
|
||||
style: 'solid' or 'dashed'
|
||||
label: Optional label to draw above the line
|
||||
Returns:
|
||||
frame with line overlay
|
||||
"""
|
||||
import cv2
|
||||
h, w = frame.shape[:2]
|
||||
x1, x2 = 0, w
|
||||
overlay = frame.copy()
|
||||
if style == 'dashed':
|
||||
dash_len = 30
|
||||
gap = 20
|
||||
for x in range(x1, x2, dash_len + gap):
|
||||
x_end = min(x + dash_len, x2)
|
||||
cv2.line(overlay, (x, y), (x_end, y), color, thickness, lineType=cv2.LINE_AA)
|
||||
else:
|
||||
cv2.line(overlay, (x1, y), (x2, y), color, thickness, lineType=cv2.LINE_AA)
|
||||
# Blend for semi-transparency
|
||||
cv2.addWeighted(overlay, 0.7, frame, 0.3, 0, frame)
|
||||
# Draw label
|
||||
if label:
|
||||
font = cv2.FONT_HERSHEY_SIMPLEX
|
||||
text_size, _ = cv2.getTextSize(label, font, 0.8, 2)
|
||||
text_x = max(10, (w - text_size[0]) // 2)
|
||||
text_y = max(0, y - 12)
|
||||
cv2.rectangle(frame, (text_x - 5, text_y - text_size[1] - 5), (text_x + text_size[0] + 5, text_y + 5), (0,0,0), -1)
|
||||
cv2.putText(frame, label, (text_x, text_y), font, 0.8, color, 2, cv2.LINE_AA)
|
||||
return frame
|
||||
|
||||
def get_violation_line_y(frame, traffic_light_bbox=None, crosswalk_bbox=None):
|
||||
"""
|
||||
Returns the y-coordinate of the violation line using the following priority:
|
||||
1. Crosswalk bbox (most accurate)
|
||||
2. Stop line detection via image processing (CV)
|
||||
3. Traffic light bbox heuristic
|
||||
4. Fallback (default)
|
||||
"""
|
||||
height, width = frame.shape[:2]
|
||||
# 1. Crosswalk bbox
|
||||
if crosswalk_bbox is not None and len(crosswalk_bbox) == 4:
|
||||
return int(crosswalk_bbox[1]) - 15
|
||||
# 2. Stop line detection (CV)
|
||||
roi_height = int(height * 0.4)
|
||||
roi_y = height - roi_height
|
||||
roi = frame[roi_y:height, 0:width]
|
||||
gray = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY)
|
||||
binary = cv2.adaptiveThreshold(
|
||||
gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
|
||||
cv2.THRESH_BINARY, 15, -2
|
||||
)
|
||||
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (15, 1))
|
||||
processed = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel)
|
||||
contours, _ = cv2.findContours(processed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
||||
stop_line_candidates = []
|
||||
for cnt in contours:
|
||||
x, y, w, h = cv2.boundingRect(cnt)
|
||||
aspect_ratio = w / max(h, 1)
|
||||
normalized_width = w / width
|
||||
if (aspect_ratio > 5 and normalized_width > 0.3 and h < 15 and y > roi_height * 0.5):
|
||||
abs_y = y + roi_y
|
||||
stop_line_candidates.append((abs_y, w))
|
||||
if stop_line_candidates:
|
||||
stop_line_candidates.sort(key=lambda x: x[1], reverse=True)
|
||||
return stop_line_candidates[0][0]
|
||||
# 3. Traffic light bbox heuristic
|
||||
if traffic_light_bbox is not None and len(traffic_light_bbox) == 4:
|
||||
traffic_light_bottom = traffic_light_bbox[3]
|
||||
traffic_light_height = traffic_light_bbox[3] - traffic_light_bbox[1]
|
||||
estimated_distance = min(5 * traffic_light_height, height * 0.3)
|
||||
return min(int(traffic_light_bottom + estimated_distance), height - 20)
|
||||
# 4. Fallback
|
||||
return int(height * 0.75)
|
||||
|
||||
# Example usage:
|
||||
# bbox, vline, dbg = detect_crosswalk_and_violation_line(frame, (tl_x, tl_y), perspective_M)
|
||||
##working
|
||||
print("🟡 [CROSSWALK_UTILS] This is d:/Downloads/finale6/Khatam final/khatam/qt_app_pyside/utils/crosswalk_utils.py LOADED")
|
||||
import cv2
|
||||
import numpy as np
|
||||
from sklearn import linear_model
|
||||
|
||||
def detect_crosswalk_and_violation_line(frame, traffic_light_position=None, debug=False):
|
||||
"""
|
||||
Robust crosswalk and violation line detection for red-light violation system.
|
||||
Returns:
|
||||
frame_with_overlays, crosswalk_bbox, violation_line_y, debug_info
|
||||
"""
|
||||
frame_out = frame.copy()
|
||||
h, w = frame.shape[:2]
|
||||
debug_info = {}
|
||||
|
||||
# === Step 1: Robust white color mask (HSV) ===
|
||||
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
|
||||
lower_white = np.array([0, 0, 180])
|
||||
upper_white = np.array([180, 80, 255])
|
||||
mask = cv2.inRange(hsv, lower_white, upper_white)
|
||||
|
||||
# === Step 2: Morphological filtering ===
|
||||
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (7, 3))
|
||||
mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)
|
||||
mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
|
||||
|
||||
# === Step 3: Contour extraction and filtering ===
|
||||
contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
||||
crosswalk_bars = []
|
||||
for cnt in contours:
|
||||
x, y, cw, ch = cv2.boundingRect(cnt)
|
||||
if cw > w * 0.05 and ch < h * 0.15:
|
||||
crosswalk_bars.append((x, y, cw, ch))
|
||||
|
||||
# === Step 4: Draw detected bars for debug ===
|
||||
for (x, y, cw, ch) in crosswalk_bars:
|
||||
cv2.rectangle(frame_out, (x, y), (x + cw, y + ch), (0, 255, 255), 2) # yellow
|
||||
|
||||
# === Step 5: Violation line placement at bottom of bars ===
|
||||
ys = np.array([y for (x, y, w, h) in crosswalk_bars])
|
||||
hs = np.array([h for (x, y, w, h) in crosswalk_bars])
|
||||
if len(ys) >= 3:
|
||||
bottom_edges = ys + hs
|
||||
violation_line_y = int(np.max(bottom_edges)) + 5 # +5 offset
|
||||
violation_line_y = min(violation_line_y, h - 1)
|
||||
crosswalk_bbox = (0, int(np.min(ys)), w, int(np.max(bottom_edges)) - int(np.min(ys)))
|
||||
# Draw semi-transparent crosswalk region
|
||||
overlay = frame_out.copy()
|
||||
cv2.rectangle(overlay, (0, int(np.min(ys))), (w, int(np.max(bottom_edges))), (0, 255, 0), -1)
|
||||
frame_out = cv2.addWeighted(overlay, 0.2, frame_out, 0.8, 0)
|
||||
cv2.rectangle(frame_out, (0, int(np.min(ys))), (w, int(np.max(bottom_edges))), (0, 255, 0), 2)
|
||||
cv2.putText(frame_out, "Crosswalk", (10, int(np.min(ys)) - 10),
|
||||
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
|
||||
else:
|
||||
violation_line_y = int(h * 0.65)
|
||||
crosswalk_bbox = None
|
||||
|
||||
# === Draw violation line ===
|
||||
cv2.line(frame_out, (0, violation_line_y), (w, violation_line_y), (0, 0, 255), 3)
|
||||
cv2.putText(frame_out, "Violation Line", (10, violation_line_y - 10),
|
||||
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
|
||||
|
||||
debug_info['crosswalk_bars'] = crosswalk_bars
|
||||
debug_info['violation_line_y'] = violation_line_y
|
||||
debug_info['crosswalk_bbox'] = crosswalk_bbox
|
||||
|
||||
return frame_out, crosswalk_bbox, violation_line_y, debug_info
|
||||
|
||||
def draw_violation_line(frame: np.ndarray, y: int, color=(0, 0, 255), thickness=4, style='solid', label='Violation Line'):
|
||||
h, w = frame.shape[:2]
|
||||
x1, x2 = 0, w
|
||||
overlay = frame.copy()
|
||||
if style == 'dashed':
|
||||
dash_len = 30
|
||||
gap = 20
|
||||
for x in range(x1, x2, dash_len + gap):
|
||||
x_end = min(x + dash_len, x2)
|
||||
cv2.line(overlay, (x, y), (x_end, y), color, thickness, lineType=cv2.LINE_AA)
|
||||
else:
|
||||
cv2.line(overlay, (x1, y), (x2, y), color, thickness, lineType=cv2.LINE_AA)
|
||||
cv2.addWeighted(overlay, 0.7, frame, 0.3, 0, frame)
|
||||
if label:
|
||||
font = cv2.FONT_HERSHEY_SIMPLEX
|
||||
text_size, _ = cv2.getTextSize(label, font, 0.8, 2)
|
||||
text_x = max(10, (w - text_size[0]) // 2)
|
||||
text_y = max(0, y - 12)
|
||||
cv2.rectangle(frame, (text_x - 5, text_y - text_size[1] - 5), (text_x + text_size[0] + 5, text_y + 5), (0,0,0), -1)
|
||||
cv2.putText(frame, label, (text_x, text_y), font, 0.8, color, 2, cv2.LINE_AA)
|
||||
return frame
|
||||
|
||||
def get_violation_line_y(frame, traffic_light_bbox=None, crosswalk_bbox=None):
|
||||
"""
|
||||
Returns the y-coordinate of the violation line using the following priority:
|
||||
1. Crosswalk bbox (most accurate)
|
||||
2. Stop line detection via image processing (CV)
|
||||
3. Traffic light bbox heuristic
|
||||
4. Fallback (default)
|
||||
"""
|
||||
height, width = frame.shape[:2]
|
||||
# 1. Crosswalk bbox
|
||||
if crosswalk_bbox is not None and len(crosswalk_bbox) == 4:
|
||||
return int(crosswalk_bbox[1]) - 15
|
||||
# 2. Stop line detection (CV)
|
||||
roi_height = int(height * 0.4)
|
||||
roi_y = height - roi_height
|
||||
roi = frame[roi_y:height, 0:width]
|
||||
gray = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY)
|
||||
binary = cv2.adaptiveThreshold(
|
||||
gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
|
||||
cv2.THRESH_BINARY, 15, -2
|
||||
)
|
||||
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (15, 1))
|
||||
processed = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel)
|
||||
contours, _ = cv2.findContours(processed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
||||
stop_line_candidates = []
|
||||
for cnt in contours:
|
||||
x, y, w, h = cv2.boundingRect(cnt)
|
||||
aspect_ratio = w / max(h, 1)
|
||||
normalized_width = w / width
|
||||
if (aspect_ratio > 5 and normalized_width > 0.3 and h < 15 and y > roi_height * 0.5):
|
||||
abs_y = y + roi_y
|
||||
stop_line_candidates.append((abs_y, w))
|
||||
if stop_line_candidates:
|
||||
stop_line_candidates.sort(key=lambda x: x[1], reverse=True)
|
||||
return stop_line_candidates[0][0]
|
||||
# 3. Traffic light bbox heuristic
|
||||
if traffic_light_bbox is not None and len(traffic_light_bbox) == 4:
|
||||
traffic_light_bottom = traffic_light_bbox[3]
|
||||
traffic_light_height = traffic_light_bbox[3] - traffic_light_bbox[1]
|
||||
estimated_distance = min(5 * traffic_light_height, height * 0.3)
|
||||
return min(int(traffic_light_bottom + estimated_distance), height - 20)
|
||||
# 4. Fallback
|
||||
return int(height * 0.75)
|
||||
|
||||
# Example usage:
|
||||
# bbox, vline, dbg = detect_crosswalk_and_violation_line(frame, (tl_x, tl_y), perspective_M)
|
||||
print("🟡 [CROSSWALK_UTILS]222 This is d:/Downloads/finale6/Khatam final/khatam/qt_app_pyside/utils/crosswalk_utils.py LOADED")
|
||||
import cv2
|
||||
import numpy as np
|
||||
from typing import Tuple, Optional
|
||||
|
||||
def detect_crosswalk_and_violation_line(frame: np.ndarray, traffic_light_position: Optional[Tuple[int, int]] = None, perspective_M: Optional[np.ndarray] = None):
|
||||
"""
|
||||
Detects crosswalk (zebra crossing) or fallback stop line in a traffic scene using classical CV.
|
||||
Args:
|
||||
frame: BGR image frame from video feed
|
||||
traffic_light_position: Optional (x, y) of traffic light in frame
|
||||
perspective_M: Optional 3x3 homography matrix for bird's eye view normalization
|
||||
Returns:
|
||||
result_frame: frame with overlays (for visualization)
|
||||
crosswalk_bbox: (x, y, w, h) or None if fallback used
|
||||
violation_line_y: int (y position for violation check)
|
||||
debug_info: dict (for visualization/debugging)
|
||||
"""
|
||||
debug_info = {}
|
||||
orig_frame = frame.copy()
|
||||
h, w = frame.shape[:2]
|
||||
|
||||
# 1. Perspective Normalization (Bird's Eye View)
|
||||
if perspective_M is not None:
|
||||
frame = cv2.warpPerspective(frame, perspective_M, (w, h))
|
||||
debug_info['perspective_warped'] = True
|
||||
else:
|
||||
debug_info['perspective_warped'] = False
|
||||
|
||||
# 1. White Color Filtering (relaxed)
|
||||
mask_white = cv2.inRange(frame, (160, 160, 160), (255, 255, 255))
|
||||
debug_info['mask_white_ratio'] = np.sum(mask_white > 0) / (h * w)
|
||||
|
||||
# 2. Grayscale for adaptive threshold
|
||||
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
|
||||
# Enhance contrast for night/low-light
|
||||
if np.mean(gray) < 80:
|
||||
gray = cv2.equalizeHist(gray)
|
||||
debug_info['hist_eq'] = True
|
||||
else:
|
||||
debug_info['hist_eq'] = False
|
||||
# 5. Adaptive threshold (tuned)
|
||||
thresh = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
|
||||
cv2.THRESH_BINARY, 15, 5)
|
||||
# Combine with color mask
|
||||
combined = cv2.bitwise_and(thresh, mask_white)
|
||||
# 2. Morphology (tuned)
|
||||
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (25, 3))
|
||||
morph = cv2.morphologyEx(combined, cv2.MORPH_CLOSE, kernel, iterations=1)
|
||||
# Find contours
|
||||
contours, _ = cv2.findContours(morph, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
||||
zebra_rects = []
|
||||
for cnt in contours:
|
||||
x, y, w, h = cv2.boundingRect(cnt)
|
||||
aspect_ratio = w / max(h, 1)
|
||||
area = w * h
|
||||
angle = 0 # For simplicity, assume horizontal stripes
|
||||
# Heuristic: wide, short, and not too small
|
||||
if aspect_ratio > 3 and 1000 < area < 0.5 * frame.shape[0] * frame.shape[1] and h < 60:
|
||||
zebra_rects.append((x, y, w, h, angle))
|
||||
cv2.rectangle(orig_frame, (x, y), (x+w, y+h), (0, 255, 0), 2)
|
||||
# --- Overlay drawing for debugging: draw all zebra candidates ---
|
||||
for r in zebra_rects:
|
||||
x, y, rw, rh, _ = r
|
||||
cv2.rectangle(orig_frame, (x, y), (x+rw, y+rh), (0, 255, 0), 2)
|
||||
# Draw all zebra candidate rectangles for debugging (no saving)
|
||||
for r in zebra_rects:
|
||||
x, y, rw, rh, _ = r
|
||||
cv2.rectangle(orig_frame, (x, y), (x+rw, y+rh), (0, 255, 0), 2)
|
||||
# --- Probabilistic Scoring for Groups ---
|
||||
def group_score(group):
|
||||
if len(group) < 3:
|
||||
return 0
|
||||
heights = [r[3] for r in group]
|
||||
x_centers = [r[0] + r[2]//2 for r in group]
|
||||
angles = [r[4] for r in group]
|
||||
# Stripe count (normalized)
|
||||
count_score = min(len(group) / 6, 1.0)
|
||||
# Height consistency
|
||||
height_score = 1.0 - min(np.std(heights) / (np.mean(heights) + 1e-6), 1.0)
|
||||
# X-center alignment
|
||||
x_score = 1.0 - min(np.std(x_centers) / (w * 0.2), 1.0)
|
||||
# Angle consistency (prefer near 0 or 90)
|
||||
mean_angle = np.mean([abs(a) for a in angles])
|
||||
angle_score = 1.0 - min(np.std(angles) / 10.0, 1.0)
|
||||
# Whiteness (mean mask_white in group area)
|
||||
whiteness = 0
|
||||
for r in group:
|
||||
x, y, rw, rh, _ = r
|
||||
whiteness += np.mean(mask_white[y:y+rh, x:x+rw]) / 255
|
||||
whiteness_score = whiteness / len(group)
|
||||
# Final score (weighted sum)
|
||||
score = 0.25*count_score + 0.2*height_score + 0.2*x_score + 0.15*angle_score + 0.2*whiteness_score
|
||||
return score
|
||||
# 4. Dynamic grouping tolerance
|
||||
y_tolerance = int(h * 0.05)
|
||||
crosswalk_bbox = None
|
||||
violation_line_y = None
|
||||
best_score = 0
|
||||
best_group = None
|
||||
if len(zebra_rects) >= 3:
|
||||
zebra_rects = sorted(zebra_rects, key=lambda r: r[1])
|
||||
groups = []
|
||||
group = [zebra_rects[0]]
|
||||
for rect in zebra_rects[1:]:
|
||||
if abs(rect[1] - group[-1][1]) < y_tolerance:
|
||||
group.append(rect)
|
||||
else:
|
||||
if len(group) >= 3:
|
||||
groups.append(group)
|
||||
group = [rect]
|
||||
if len(group) >= 3:
|
||||
groups.append(group)
|
||||
# Score all groups
|
||||
scored_groups = [(group_score(g), g) for g in groups if group_score(g) > 0.1]
|
||||
print(f"[CROSSWALK DEBUG] scored_groups: {[s for s, _ in scored_groups]}")
|
||||
if scored_groups:
|
||||
scored_groups.sort(reverse=True, key=lambda x: x[0])
|
||||
best_score, best_group = scored_groups[0]
|
||||
print("Best group score:", best_score)
|
||||
# Visualization for debugging
|
||||
debug_vis = orig_frame.copy()
|
||||
for r in zebra_rects:
|
||||
x, y, rw, rh, _ = r
|
||||
cv2.rectangle(debug_vis, (x, y), (x+rw, y+rh), (255, 0, 255), 2)
|
||||
for r in best_group:
|
||||
x, y, rw, rh, _ = r
|
||||
cv2.rectangle(debug_vis, (x, y), (x+rw, y+rh), (0, 255, 255), 3)
|
||||
cv2.imwrite(f"debug_crosswalk_group.png", debug_vis)
|
||||
# Optionally, filter by vanishing point as before
|
||||
# ...existing vanishing point code...
|
||||
xs = [r[0] for r in best_group] + [r[0] + r[2] for r in best_group]
|
||||
ys = [r[1] for r in best_group] + [r[1] + r[3] for r in best_group]
|
||||
x1, x2 = min(xs), max(xs)
|
||||
y1, y2 = min(ys), max(ys)
|
||||
crosswalk_bbox = (x1, y1, x2 - x1, y2 - y1)
|
||||
violation_line_y = y2 - 5
|
||||
debug_info['crosswalk_group'] = best_group
|
||||
debug_info['crosswalk_score'] = best_score
|
||||
debug_info['crosswalk_angles'] = [r[4] for r in best_group]
|
||||
# --- Fallback: Stop line detection ---
|
||||
if crosswalk_bbox is None:
|
||||
edges = cv2.Canny(gray, 80, 200)
|
||||
lines = cv2.HoughLinesP(edges, 1, np.pi / 180, threshold=80, minLineLength=60, maxLineGap=20)
|
||||
stop_lines = []
|
||||
if lines is not None:
|
||||
for l in lines:
|
||||
x1, y1, x2, y2 = l[0]
|
||||
angle = np.degrees(np.arctan2(y2 - y1, x2 - x1))
|
||||
if abs(angle) < 20 or abs(angle) > 160: # horizontal
|
||||
if y1 > h // 2 or y2 > h // 2: # lower half
|
||||
stop_lines.append((x1, y1, x2, y2))
|
||||
debug_info['stop_lines'] = stop_lines
|
||||
print(f"[CROSSWALK DEBUG] stop_lines: {len(stop_lines)} found")
|
||||
if stop_lines:
|
||||
if traffic_light_position:
|
||||
tx, ty = traffic_light_position
|
||||
best_line = min(stop_lines, key=lambda l: abs(((l[1]+l[3])//2) - ty))
|
||||
else:
|
||||
best_line = max(stop_lines, key=lambda l: max(l[1], l[3]))
|
||||
x1, y1, x2, y2 = best_line
|
||||
crosswalk_bbox = None
|
||||
violation_line_y = min(y1, y2) - 5
|
||||
debug_info['stop_line'] = best_line
|
||||
print(f"[CROSSWALK DEBUG] using stop_line: {best_line}")
|
||||
# Draw fallback violation line overlay for debugging (no saving)
|
||||
|
||||
return orig_frame, crosswalk_bbox, violation_line_y, debug_info
|
||||
|
||||
def draw_violation_line(frame: np.ndarray, y: int, color=(0, 0, 255), thickness=8, style='solid', label='Violation Line'):
|
||||
"""
|
||||
Draws a thick, optionally dashed, labeled violation line at the given y-coordinate.
|
||||
Args:
|
||||
frame: BGR image
|
||||
y: y-coordinate for the line
|
||||
color: BGR color tuple
|
||||
thickness: line thickness
|
||||
style: 'solid' or 'dashed'
|
||||
label: Optional label to draw above the line
|
||||
Returns:
|
||||
frame with line overlay
|
||||
"""
|
||||
import cv2
|
||||
h, w = frame.shape[:2]
|
||||
x1, x2 = 0, w
|
||||
overlay = frame.copy()
|
||||
if style == 'dashed':
|
||||
dash_len = 30
|
||||
gap = 20
|
||||
for x in range(x1, x2, dash_len + gap):
|
||||
x_end = min(x + dash_len, x2)
|
||||
cv2.line(overlay, (x, y), (x_end, y), color, thickness, lineType=cv2.LINE_AA)
|
||||
else:
|
||||
cv2.line(overlay, (x1, y), (x2, y), color, thickness, lineType=cv2.LINE_AA)
|
||||
# Blend for semi-transparency
|
||||
cv2.addWeighted(overlay, 0.7, frame, 0.3, 0, frame)
|
||||
# Draw label
|
||||
if label:
|
||||
font = cv2.FONT_HERSHEY_SIMPLEX
|
||||
text_size, _ = cv2.getTextSize(label, font, 0.8, 2)
|
||||
text_x = max(10, (w - text_size[0]) // 2)
|
||||
text_y = max(0, y - 12)
|
||||
cv2.rectangle(frame, (text_x - 5, text_y - text_size[1] - 5), (text_x + text_size[0] + 5, text_y + 5), (0,0,0), -1)
|
||||
cv2.putText(frame, label, (text_x, text_y), font, 0.8, color, 2, cv2.LINE_AA)
|
||||
return frame
|
||||
|
||||
def get_violation_line_y(frame, traffic_light_bbox=None, crosswalk_bbox=None):
|
||||
"""
|
||||
Returns the y-coordinate of the violation line using the following priority:
|
||||
1. Crosswalk bbox (most accurate)
|
||||
2. Stop line detection via image processing (CV)
|
||||
3. Traffic light bbox heuristic
|
||||
4. Fallback (default)
|
||||
"""
|
||||
height, width = frame.shape[:2]
|
||||
# 1. Crosswalk bbox
|
||||
if crosswalk_bbox is not None and len(crosswalk_bbox) == 4:
|
||||
return int(crosswalk_bbox[1]) - 15
|
||||
# 2. Stop line detection (CV)
|
||||
roi_height = int(height * 0.4)
|
||||
roi_y = height - roi_height
|
||||
roi = frame[roi_y:height, 0:width]
|
||||
gray = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY)
|
||||
binary = cv2.adaptiveThreshold(
|
||||
gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
|
||||
cv2.THRESH_BINARY, 15, -2
|
||||
)
|
||||
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (15, 1))
|
||||
processed = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel)
|
||||
contours, _ = cv2.findContours(processed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
||||
stop_line_candidates = []
|
||||
for cnt in contours:
|
||||
x, y, w, h = cv2.boundingRect(cnt)
|
||||
aspect_ratio = w / max(h, 1)
|
||||
normalized_width = w / width
|
||||
if (aspect_ratio > 5 and normalized_width > 0.3 and h < 15 and y > roi_height * 0.5):
|
||||
abs_y = y + roi_y
|
||||
stop_line_candidates.append((abs_y, w))
|
||||
if stop_line_candidates:
|
||||
stop_line_candidates.sort(key=lambda x: x[1], reverse=True)
|
||||
return stop_line_candidates[0][0]
|
||||
# 3. Traffic light bbox heuristic
|
||||
if traffic_light_bbox is not None and len(traffic_light_bbox) == 4:
|
||||
traffic_light_bottom = traffic_light_bbox[3]
|
||||
traffic_light_height = traffic_light_bbox[3] - traffic_light_bbox[1]
|
||||
estimated_distance = min(5 * traffic_light_height, height * 0.3)
|
||||
return min(int(traffic_light_bottom + estimated_distance), height - 20)
|
||||
# 4. Fallback
|
||||
return int(height * 0.75)
|
||||
|
||||
# Example usage:
|
||||
# bbox, vline, dbg = detect_crosswalk_and_violation_line(frame, (tl_x, tl_y), perspective_M)
|
||||
Reference in New Issue
Block a user