copilot-swe-agent[bot] kr4phy commited on
Commit
4ae3a76
·
1 Parent(s): 63ea6ab

Add progress bar support and new lane detection methods

Browse files

Co-authored-by: kr4phy <168257476+kr4phy@users.noreply.github.com>

Files changed (2) hide show
  1. app.py +63 -20
  2. lane_detection.py +373 -9
app.py CHANGED
@@ -3,10 +3,10 @@ import tempfile
3
  from lane_detection import process_video as process_video_file
4
 
5
 
6
- def process_video(video_path, method, use_enhanced, use_segmented):
7
  """
8
  Process the uploaded video and return side-by-side comparison.
9
- Wrapper function for Gradio interface.
10
  """
11
  if video_path is None:
12
  return None
@@ -16,8 +16,12 @@ def process_video(video_path, method, use_enhanced, use_segmented):
16
  output_path = temp_output.name
17
  temp_output.close()
18
 
19
- # Process the video with selected method
20
- success = process_video_file(video_path, output_path, method, use_enhanced, use_segmented)
 
 
 
 
21
 
22
  if success:
23
  return output_path
@@ -28,7 +32,7 @@ def process_video(video_path, method, use_enhanced, use_segmented):
28
  # Create Gradio interface
29
  with gr.Blocks(title="Lane Detection Demo") as demo:
30
  gr.Markdown("# 🚗 OpenCV Lane Detection Demo")
31
- gr.Markdown("Upload a video to detect lane lines. Choose between basic and advanced methods.")
32
 
33
  with gr.Row():
34
  with gr.Column():
@@ -36,10 +40,17 @@ with gr.Blocks(title="Lane Detection Demo") as demo:
36
 
37
  with gr.Row():
38
  method_selector = gr.Radio(
39
- choices=["basic", "advanced"],
 
 
 
 
 
 
 
40
  value="advanced",
41
  label="Detection Method",
42
- info="Basic: Fast Hough Transform | Advanced: Accurate polynomial fitting"
43
  )
44
 
45
  enhanced_checkbox = gr.Checkbox(
@@ -64,7 +75,7 @@ with gr.Blocks(title="Lane Detection Demo") as demo:
64
  # Update checkbox visibility based on method
65
  def update_checkboxes(method):
66
  enhanced_visible = (method == "advanced")
67
- segmented_visible = (method == "basic")
68
  return gr.Checkbox(visible=enhanced_visible), gr.Checkbox(visible=segmented_visible)
69
 
70
  method_selector.change(
@@ -82,31 +93,63 @@ with gr.Blocks(title="Lane Detection Demo") as demo:
82
  gr.Markdown("""
83
  ### Detection Methods:
84
 
85
- **🔹 Basic Method (Hough Transform):**
86
  - Fast and lightweight
87
  - Good for straight lanes
88
- - **New: Segmented Mode** - Draws multiple line segments for better curve representation
89
- - Lower accuracy on sharp curves and dashed lines
 
 
 
 
 
 
90
 
91
- **🔹 Advanced Method (Perspective Transform + Polynomial):**
92
  - Perspective transform to bird's eye view
93
  - Polynomial fitting with sliding windows
94
  - Excellent for curved and dashed lanes
95
- - **Enhanced mode** uses CLAHE and gradient direction filtering for best accuracy
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
 
97
  ### How it works:
98
  1. Upload a video file containing road scenes
99
- 2. Select detection method and options:
100
- - For **Basic**: Enable "Segmented Lines" for curves
101
- - For **Advanced**: Enable "Enhanced Thresholding" for better accuracy
102
- 3. Click "Process Video" button
 
 
103
  4. The system will process each frame and create a side-by-side comparison
104
  5. Download the result video showing original and detected lanes
105
 
106
  ### Tips:
107
- - Use **Basic + Segmented** for fastest processing with decent curve handling
108
- - Use **Advanced + Enhanced** for best accuracy but slower processing
109
- - Adjust based on your video quality and road conditions
 
 
110
  """)
111
 
112
 
 
3
  from lane_detection import process_video as process_video_file
4
 
5
 
6
+ def process_video(video_path, method, use_enhanced, use_segmented, progress=gr.Progress()):
7
  """
8
  Process the uploaded video and return side-by-side comparison.
9
+ Wrapper function for Gradio interface with progress tracking.
10
  """
11
  if video_path is None:
12
  return None
 
16
  output_path = temp_output.name
17
  temp_output.close()
18
 
19
+ # Progress callback function
20
+ def update_progress(value, desc):
21
+ progress(value, desc=desc)
22
+
23
+ # Process the video with selected method and progress callback
24
+ success = process_video_file(video_path, output_path, method, use_enhanced, use_segmented, progress_callback=update_progress)
25
 
26
  if success:
27
  return output_path
 
32
  # Create Gradio interface
33
  with gr.Blocks(title="Lane Detection Demo") as demo:
34
  gr.Markdown("# 🚗 OpenCV Lane Detection Demo")
35
+ gr.Markdown("Upload a video to detect lane lines. Choose between multiple advanced methods.")
36
 
37
  with gr.Row():
38
  with gr.Column():
 
40
 
41
  with gr.Row():
42
  method_selector = gr.Radio(
43
+ choices=[
44
+ "basic_standard",
45
+ "basic_segmented",
46
+ "advanced",
47
+ "yolop",
48
+ "ufld",
49
+ "scnn"
50
+ ],
51
  value="advanced",
52
  label="Detection Method",
53
+ info="Select lane detection algorithm"
54
  )
55
 
56
  enhanced_checkbox = gr.Checkbox(
 
75
  # Update checkbox visibility based on method
76
  def update_checkboxes(method):
77
  enhanced_visible = (method == "advanced")
78
+ segmented_visible = (method in ["basic_standard", "basic_segmented"])
79
  return gr.Checkbox(visible=enhanced_visible), gr.Checkbox(visible=segmented_visible)
80
 
81
  method_selector.change(
 
93
  gr.Markdown("""
94
  ### Detection Methods:
95
 
96
+ **🔹 Basic Standard (Hough Transform):**
97
  - Fast and lightweight
98
  - Good for straight lanes
99
+ - Uses single averaged line per lane
100
+ - Fastest processing speed
101
+
102
+ **🔹 Basic Segmented (Hough Transform):**
103
+ - Fast processing
104
+ - Multiple line segments for better curve representation
105
+ - Good for moderately curved lanes
106
+ - Better than basic for curves
107
 
108
+ **🔹 Advanced (Perspective Transform + Polynomial):**
109
  - Perspective transform to bird's eye view
110
  - Polynomial fitting with sliding windows
111
  - Excellent for curved and dashed lanes
112
+ - Enhanced mode uses CLAHE and gradient filtering
113
+ - Best accuracy but slower
114
+
115
+ **🔹 YOLOP (Multi-task Learning):**
116
+ - Inspired by YOLOP (You Only Look Once for Panoptic Driving)
117
+ - Multi-color lane detection (white and yellow)
118
+ - Contour-based segmentation
119
+ - Good for various lane colors
120
+ - Fast with good accuracy
121
+
122
+ **🔹 UFLD (Ultra Fast Lane Detection):**
123
+ - Inspired by Ultra Fast Structure-aware Deep Lane Detection
124
+ - Row-wise classification approach
125
+ - Adaptive thresholding with CLAHE
126
+ - Excellent balance of speed and accuracy
127
+ - Real-time capable
128
+
129
+ **🔹 SCNN (Spatial CNN):**
130
+ - Inspired by Spatial CNN for traffic lane detection
131
+ - Spatial message passing in four directions
132
+ - Multi-scale edge detection
133
+ - Best for complex scenarios
134
+ - High accuracy for challenging conditions
135
 
136
  ### How it works:
137
  1. Upload a video file containing road scenes
138
+ 2. Select detection method:
139
+ - For fastest: Use **Basic Standard**
140
+ - For curves: Use **Basic Segmented**, **UFLD**, or **Advanced**
141
+ - For best accuracy: Use **SCNN** or **Advanced + Enhanced**
142
+ - For multi-color lanes: Use **YOLOP**
143
+ 3. Click "Process Video" button and monitor the progress bar
144
  4. The system will process each frame and create a side-by-side comparison
145
  5. Download the result video showing original and detected lanes
146
 
147
  ### Tips:
148
+ - **Basic Standard/Segmented**: Fastest, good for straight or gentle curves
149
+ - **YOLOP**: Best for detecting both white and yellow lanes
150
+ - **UFLD**: Excellent balance of speed and accuracy
151
+ - **Advanced + Enhanced**: Best for dashed and curved lanes
152
+ - **SCNN**: Best overall accuracy for complex road conditions
153
  """)
154
 
155
 
lane_detection.py CHANGED
@@ -527,19 +527,376 @@ def draw_poly_lines(img, binary_warped, left_fit, right_fit, Minv):
527
  return result
528
 
529
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
530
  def process_frame(frame, method="advanced", use_enhanced=True, use_segmented=False):
531
  """
532
  Process a single frame for lane detection.
533
- method: "basic" or "advanced"
534
  use_enhanced: Use enhanced thresholding for better accuracy (advanced method only)
535
  use_segmented: Use segmented lines for curve representation (basic method only)
536
  """
537
- if method == "basic":
538
- return process_frame_basic(frame, use_segmented)
 
 
539
  elif method == "advanced":
540
  return process_frame_advanced(frame, use_enhanced)
 
 
 
 
 
 
541
  else:
542
- raise ValueError(f"Unknown method: {method}. Use 'basic' or 'advanced'")
543
 
544
 
545
  def process_frame_advanced(frame, use_enhanced=True):
@@ -565,12 +922,13 @@ def process_frame_advanced(frame, use_enhanced=True):
565
  return result
566
 
567
 
568
- def process_video(input_path, output_path, method="advanced", use_enhanced=True, use_segmented=False):
569
  """
570
  Process the video and create side-by-side comparison.
571
- method: "basic" or "advanced"
572
  use_enhanced: Use enhanced thresholding for better accuracy (advanced method only)
573
  use_segmented: Use segmented lines for curve representation (basic method only)
 
574
  Returns True if successful, False otherwise.
575
  """
576
  # Open the video
@@ -586,8 +944,8 @@ def process_video(input_path, output_path, method="advanced", use_enhanced=True,
586
  total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
587
 
588
  # Video writer for output (side-by-side, so width is doubled)
589
- # Use H264 codec for better web browser compatibility
590
- fourcc = cv2.VideoWriter_fourcc(*'avc1')
591
  out = cv2.VideoWriter(output_path, fourcc, fps, (width * 2, height))
592
 
593
  frame_count = 0
@@ -615,7 +973,10 @@ def process_video(input_path, output_path, method="advanced", use_enhanced=True,
615
  frame_count += 1
616
 
617
  # Progress indicator
618
- if frame_count % 30 == 0:
 
 
 
619
  progress = (frame_count / total_frames) * 100 if total_frames > 0 else 0
620
  print(f"Progress: {frame_count}/{total_frames} frames ({progress:.1f}%)")
621
 
@@ -623,6 +984,9 @@ def process_video(input_path, output_path, method="advanced", use_enhanced=True,
623
  cap.release()
624
  out.release()
625
 
 
 
 
626
  print(f"✓ Completed! Processed {frame_count} frames using {method} method.")
627
 
628
  return frame_count > 0
 
527
  return result
528
 
529
 
530
+ def process_frame_yolop(frame):
531
+ """
532
+ YOLOP-inspired lane detection method.
533
+ Simulates multi-task learning approach with semantic segmentation.
534
+ Uses enhanced color-based segmentation with adaptive thresholding.
535
+ """
536
+ height, width = frame.shape[:2]
537
+
538
+ # Convert to HLS for better color segmentation
539
+ hls = cv2.cvtColor(frame, cv2.COLOR_BGR2HLS)
540
+ h_channel = hls[:, :, 0]
541
+ l_channel = hls[:, :, 1]
542
+ s_channel = hls[:, :, 2]
543
+
544
+ # Multi-threshold approach for different lane colors
545
+ # White lanes - high lightness
546
+ white_mask = cv2.inRange(l_channel, 200, 255)
547
+
548
+ # Yellow lanes - specific hue range
549
+ yellow_mask = cv2.inRange(h_channel, 15, 35) & cv2.inRange(s_channel, 80, 255)
550
+
551
+ # Combine masks
552
+ color_mask = cv2.bitwise_or(white_mask, yellow_mask)
553
+
554
+ # Apply morphological operations
555
+ kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
556
+ color_mask = cv2.morphologyEx(color_mask, cv2.MORPH_CLOSE, kernel)
557
+ color_mask = cv2.morphologyEx(color_mask, cv2.MORPH_OPEN, kernel)
558
+
559
+ # Apply ROI
560
+ vertices = np.array([[
561
+ (int(width * 0.1), height),
562
+ (int(width * 0.45), int(height * 0.6)),
563
+ (int(width * 0.55), int(height * 0.6)),
564
+ (int(width * 0.9), height)
565
+ ]], dtype=np.int32)
566
+
567
+ color_mask = region_of_interest(color_mask, vertices)
568
+
569
+ # Find contours for lane segments
570
+ contours, _ = cv2.findContours(color_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
571
+
572
+ # Create output image
573
+ result = frame.copy()
574
+ overlay = np.zeros_like(frame)
575
+
576
+ # Separate left and right lane contours
577
+ left_contours = []
578
+ right_contours = []
579
+
580
+ midpoint = width // 2
581
+ for contour in contours:
582
+ if cv2.contourArea(contour) > 100:
583
+ M = cv2.moments(contour)
584
+ if M["m00"] != 0:
585
+ cx = int(M["m10"] / M["m00"])
586
+ if cx < midpoint:
587
+ left_contours.append(contour)
588
+ else:
589
+ right_contours.append(contour)
590
+
591
+ # Draw lane regions
592
+ if len(left_contours) > 0 or len(right_contours) > 0:
593
+ # Fill lane area
594
+ if len(left_contours) > 0 and len(right_contours) > 0:
595
+ # Get bounding points
596
+ left_points = np.vstack(left_contours).squeeze()
597
+ right_points = np.vstack(right_contours).squeeze()
598
+
599
+ if len(left_points.shape) == 2 and len(right_points.shape) == 2:
600
+ # Sort by y coordinate
601
+ left_points = left_points[left_points[:, 1].argsort()]
602
+ right_points = right_points[right_points[:, 1].argsort()]
603
+
604
+ # Create polygon
605
+ poly_points = np.vstack([left_points, right_points[::-1]])
606
+ cv2.fillPoly(overlay, [poly_points], (0, 255, 0))
607
+
608
+ # Draw lane lines
609
+ for contour in left_contours:
610
+ cv2.drawContours(overlay, [contour], -1, (0, 0, 255), 5)
611
+ for contour in right_contours:
612
+ cv2.drawContours(overlay, [contour], -1, (0, 0, 255), 5)
613
+
614
+ # Blend with original
615
+ result = cv2.addWeighted(result, 0.8, overlay, 0.5, 0)
616
+
617
+ return result
618
+
619
+
620
+ def process_frame_ufld(frame):
621
+ """
622
+ UFLD-inspired (Ultra Fast Lane Detection) method.
623
+ Uses row-wise classification approach with efficient feature extraction.
624
+ Focuses on speed and accuracy for real-time applications.
625
+ """
626
+ height, width = frame.shape[:2]
627
+
628
+ # Convert to grayscale
629
+ gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
630
+
631
+ # Apply CLAHE for enhanced contrast
632
+ clahe = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(8, 8))
633
+ enhanced = clahe.apply(gray)
634
+
635
+ # Apply bilateral filter to preserve edges while reducing noise
636
+ filtered = cv2.bilateralFilter(enhanced, 9, 75, 75)
637
+
638
+ # Adaptive thresholding
639
+ binary = cv2.adaptiveThreshold(
640
+ filtered, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
641
+ cv2.THRESH_BINARY, 11, 2
642
+ )
643
+
644
+ # Apply ROI
645
+ vertices = np.array([[
646
+ (int(width * 0.1), height),
647
+ (int(width * 0.45), int(height * 0.6)),
648
+ (int(width * 0.55), int(height * 0.6)),
649
+ (int(width * 0.9), height)
650
+ ]], dtype=np.int32)
651
+
652
+ binary = region_of_interest(binary, vertices)
653
+
654
+ # Row-wise lane point detection
655
+ row_samples = 18 # Number of rows to sample
656
+ row_step = height // row_samples
657
+
658
+ left_lane_points = []
659
+ right_lane_points = []
660
+
661
+ midpoint = width // 2
662
+
663
+ for i in range(row_samples):
664
+ y = height - i * row_step - row_step // 2
665
+ if y < int(height * 0.6):
666
+ continue
667
+
668
+ row = binary[y, :]
669
+
670
+ # Find peaks in left and right halves
671
+ left_half = row[:midpoint]
672
+ right_half = row[midpoint:]
673
+
674
+ # Find lane positions
675
+ left_peaks = np.where(left_half > 200)[0]
676
+ right_peaks = np.where(right_half > 200)[0]
677
+
678
+ if len(left_peaks) > 0:
679
+ # Use the rightmost peak in left half
680
+ x = left_peaks[-1]
681
+ left_lane_points.append([x, y])
682
+
683
+ if len(right_peaks) > 0:
684
+ # Use the leftmost peak in right half
685
+ x = midpoint + right_peaks[0]
686
+ right_lane_points.append([x, y])
687
+
688
+ # Create result image
689
+ result = frame.copy()
690
+ overlay = np.zeros_like(frame)
691
+
692
+ # Fit curves to lane points
693
+ if len(left_lane_points) >= 3:
694
+ left_lane_points = np.array(left_lane_points)
695
+ left_fit = np.polyfit(left_lane_points[:, 1], left_lane_points[:, 0], 2)
696
+
697
+ # Generate smooth curve
698
+ ploty = np.linspace(int(height * 0.6), height, 100)
699
+ left_fitx = left_fit[0] * ploty**2 + left_fit[1] * ploty + left_fit[2]
700
+ left_fitx = np.clip(left_fitx, 0, width - 1)
701
+
702
+ left_curve = np.array([np.transpose(np.vstack([left_fitx, ploty]))], dtype=np.int32)
703
+ cv2.polylines(overlay, left_curve, False, (0, 0, 255), 8)
704
+
705
+ if len(right_lane_points) >= 3:
706
+ right_lane_points = np.array(right_lane_points)
707
+ right_fit = np.polyfit(right_lane_points[:, 1], right_lane_points[:, 0], 2)
708
+
709
+ # Generate smooth curve
710
+ ploty = np.linspace(int(height * 0.6), height, 100)
711
+ right_fitx = right_fit[0] * ploty**2 + right_fit[1] * ploty + right_fit[2]
712
+ right_fitx = np.clip(right_fitx, 0, width - 1)
713
+
714
+ right_curve = np.array([np.transpose(np.vstack([right_fitx, ploty]))], dtype=np.int32)
715
+ cv2.polylines(overlay, right_curve, False, (0, 0, 255), 8)
716
+
717
+ # Fill lane area
718
+ if len(left_lane_points) >= 3 and len(right_lane_points) >= 3:
719
+ ploty = np.linspace(int(height * 0.6), height, 100)
720
+ left_fitx = left_fit[0] * ploty**2 + left_fit[1] * ploty + left_fit[2]
721
+ right_fitx = right_fit[0] * ploty**2 + right_fit[1] * ploty + right_fit[2]
722
+
723
+ left_fitx = np.clip(left_fitx, 0, width - 1)
724
+ right_fitx = np.clip(right_fitx, 0, width - 1)
725
+
726
+ pts_left = np.array([np.transpose(np.vstack([left_fitx, ploty]))])
727
+ pts_right = np.array([np.flipud(np.transpose(np.vstack([right_fitx, ploty])))])
728
+ pts = np.hstack((pts_left, pts_right))
729
+
730
+ cv2.fillPoly(overlay, np.int32([pts]), (0, 255, 0))
731
+
732
+ # Blend
733
+ result = cv2.addWeighted(result, 0.8, overlay, 0.5, 0)
734
+
735
+ return result
736
+
737
+
738
+ def process_frame_scnn(frame):
739
+ """
740
+ SCNN-inspired (Spatial CNN) method.
741
+ Uses spatial message passing for lane detection.
742
+ Implements slice-by-slice convolutions in four directions.
743
+ """
744
+ height, width = frame.shape[:2]
745
+
746
+ # Preprocessing
747
+ hls = cv2.cvtColor(frame, cv2.COLOR_BGR2HLS)
748
+ l_channel = hls[:, :, 1]
749
+ s_channel = hls[:, :, 2]
750
+
751
+ # Enhanced preprocessing with CLAHE
752
+ clahe = cv2.createCLAHE(clipLimit=2.5, tileGridSize=(8, 8))
753
+ l_enhanced = clahe.apply(l_channel)
754
+
755
+ # Multi-scale edge detection
756
+ sobel_x = cv2.Sobel(l_enhanced, cv2.CV_64F, 1, 0, ksize=5)
757
+ sobel_y = cv2.Sobel(l_enhanced, cv2.CV_64F, 0, 1, ksize=5)
758
+
759
+ # Gradient magnitude and direction
760
+ magnitude = np.sqrt(sobel_x**2 + sobel_y**2)
761
+ magnitude = np.uint8(255 * magnitude / np.max(magnitude))
762
+
763
+ direction = np.arctan2(sobel_y, sobel_x)
764
+
765
+ # Focus on near-vertical edges (lane lines)
766
+ vertical_mask = np.zeros_like(magnitude)
767
+ vertical_mask[(np.abs(direction) > 0.6) & (np.abs(direction) < 1.5)] = 255
768
+
769
+ # Combine with color thresholding
770
+ s_binary = cv2.inRange(s_channel, 90, 255)
771
+ l_binary = cv2.inRange(l_enhanced, 180, 255)
772
+
773
+ combined = cv2.bitwise_or(s_binary, l_binary)
774
+ combined = cv2.bitwise_and(combined, magnitude)
775
+ combined = cv2.bitwise_and(combined, vertical_mask)
776
+
777
+ # Simulate spatial message passing with directional filtering
778
+ # Horizontal message passing (left-to-right and right-to-left)
779
+ kernel_h = np.ones((1, 15), np.uint8)
780
+ horizontal_pass = cv2.morphologyEx(combined, cv2.MORPH_CLOSE, kernel_h)
781
+
782
+ # Vertical message passing (top-to-bottom and bottom-to-top)
783
+ kernel_v = np.ones((15, 1), np.uint8)
784
+ spatial_features = cv2.morphologyEx(horizontal_pass, cv2.MORPH_CLOSE, kernel_v)
785
+
786
+ # Apply ROI
787
+ vertices = np.array([[
788
+ (int(width * 0.1), height),
789
+ (int(width * 0.45), int(height * 0.6)),
790
+ (int(width * 0.55), int(height * 0.6)),
791
+ (int(width * 0.9), height)
792
+ ]], dtype=np.int32)
793
+
794
+ spatial_features = region_of_interest(spatial_features, vertices)
795
+
796
+ # Lane fitting with sliding window
797
+ histogram = np.sum(spatial_features[spatial_features.shape[0]//2:, :], axis=0)
798
+ midpoint = len(histogram) // 2
799
+
800
+ leftx_base = np.argmax(histogram[:midpoint])
801
+ rightx_base = np.argmax(histogram[midpoint:]) + midpoint
802
+
803
+ # Sliding window parameters
804
+ nwindows = 12
805
+ window_height = spatial_features.shape[0] // nwindows
806
+ margin = 80
807
+ minpix = 40
808
+
809
+ nonzero = spatial_features.nonzero()
810
+ nonzeroy = np.array(nonzero[0])
811
+ nonzerox = np.array(nonzero[1])
812
+
813
+ leftx_current = leftx_base
814
+ rightx_current = rightx_base
815
+
816
+ left_lane_inds = []
817
+ right_lane_inds = []
818
+
819
+ for window in range(nwindows):
820
+ win_y_low = spatial_features.shape[0] - (window + 1) * window_height
821
+ win_y_high = spatial_features.shape[0] - window * window_height
822
+
823
+ win_xleft_low = leftx_current - margin
824
+ win_xleft_high = leftx_current + margin
825
+ win_xright_low = rightx_current - margin
826
+ win_xright_high = rightx_current + margin
827
+
828
+ good_left_inds = ((nonzeroy >= win_y_low) & (nonzeroy < win_y_high) &
829
+ (nonzerox >= win_xleft_low) & (nonzerox < win_xleft_high)).nonzero()[0]
830
+ good_right_inds = ((nonzeroy >= win_y_low) & (nonzeroy < win_y_high) &
831
+ (nonzerox >= win_xright_low) & (nonzerox < win_xright_high)).nonzero()[0]
832
+
833
+ left_lane_inds.append(good_left_inds)
834
+ right_lane_inds.append(good_right_inds)
835
+
836
+ if len(good_left_inds) > minpix:
837
+ leftx_current = int(np.mean(nonzerox[good_left_inds]))
838
+ if len(good_right_inds) > minpix:
839
+ rightx_current = int(np.mean(nonzerox[good_right_inds]))
840
+
841
+ left_lane_inds = np.concatenate(left_lane_inds)
842
+ right_lane_inds = np.concatenate(right_lane_inds)
843
+
844
+ leftx = nonzerox[left_lane_inds]
845
+ lefty = nonzeroy[left_lane_inds]
846
+ rightx = nonzerox[right_lane_inds]
847
+ righty = nonzeroy[right_lane_inds]
848
+
849
+ result = frame.copy()
850
+ overlay = np.zeros_like(frame)
851
+
852
+ if len(leftx) > 0 and len(rightx) > 0:
853
+ left_fit = np.polyfit(lefty, leftx, 2)
854
+ right_fit = np.polyfit(righty, rightx, 2)
855
+
856
+ ploty = np.linspace(0, spatial_features.shape[0] - 1, spatial_features.shape[0])
857
+ left_fitx = left_fit[0] * ploty**2 + left_fit[1] * ploty + left_fit[2]
858
+ right_fitx = right_fit[0] * ploty**2 + right_fit[1] * ploty + right_fit[2]
859
+
860
+ left_fitx = np.clip(left_fitx, 0, width - 1)
861
+ right_fitx = np.clip(right_fitx, 0, width - 1)
862
+
863
+ # Draw lane area
864
+ pts_left = np.array([np.transpose(np.vstack([left_fitx, ploty]))])
865
+ pts_right = np.array([np.flipud(np.transpose(np.vstack([right_fitx, ploty])))])
866
+ pts = np.hstack((pts_left, pts_right))
867
+
868
+ cv2.fillPoly(overlay, np.int32([pts]), (0, 255, 0))
869
+
870
+ # Draw lane lines
871
+ cv2.polylines(overlay, np.int32([pts_left]), False, (0, 0, 255), 12)
872
+ cv2.polylines(overlay, np.int32([pts_right]), False, (0, 0, 255), 12)
873
+
874
+ result = cv2.addWeighted(result, 0.8, overlay, 0.5, 0)
875
+
876
+ return result
877
+
878
+
879
  def process_frame(frame, method="advanced", use_enhanced=True, use_segmented=False):
880
  """
881
  Process a single frame for lane detection.
882
+ method: "basic", "basic_segmented", "advanced", "yolop", "ufld", "scnn"
883
  use_enhanced: Use enhanced thresholding for better accuracy (advanced method only)
884
  use_segmented: Use segmented lines for curve representation (basic method only)
885
  """
886
+ if method == "basic" or method == "basic_standard":
887
+ return process_frame_basic(frame, use_segmented=False)
888
+ elif method == "basic_segmented":
889
+ return process_frame_basic(frame, use_segmented=True)
890
  elif method == "advanced":
891
  return process_frame_advanced(frame, use_enhanced)
892
+ elif method == "yolop":
893
+ return process_frame_yolop(frame)
894
+ elif method == "ufld":
895
+ return process_frame_ufld(frame)
896
+ elif method == "scnn":
897
+ return process_frame_scnn(frame)
898
  else:
899
+ raise ValueError(f"Unknown method: {method}. Use 'basic', 'basic_segmented', 'advanced', 'yolop', 'ufld', or 'scnn'")
900
 
901
 
902
  def process_frame_advanced(frame, use_enhanced=True):
 
922
  return result
923
 
924
 
925
+ def process_video(input_path, output_path, method="advanced", use_enhanced=True, use_segmented=False, progress_callback=None):
926
  """
927
  Process the video and create side-by-side comparison.
928
+ method: "basic", "basic_segmented", "advanced", "yolop", "ufld", "scnn"
929
  use_enhanced: Use enhanced thresholding for better accuracy (advanced method only)
930
  use_segmented: Use segmented lines for curve representation (basic method only)
931
+ progress_callback: Optional callback function to report progress (value between 0 and 1)
932
  Returns True if successful, False otherwise.
933
  """
934
  # Open the video
 
944
  total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
945
 
946
  # Video writer for output (side-by-side, so width is doubled)
947
+ # Use mp4v codec for better compatibility
948
+ fourcc = cv2.VideoWriter_fourcc(*'mp4v')
949
  out = cv2.VideoWriter(output_path, fourcc, fps, (width * 2, height))
950
 
951
  frame_count = 0
 
973
  frame_count += 1
974
 
975
  # Progress indicator
976
+ if progress_callback and frame_count % 10 == 0:
977
+ progress = frame_count / total_frames if total_frames > 0 else 0
978
+ progress_callback(progress, f"Processing frame {frame_count}/{total_frames}")
979
+ elif frame_count % 30 == 0:
980
  progress = (frame_count / total_frames) * 100 if total_frames > 0 else 0
981
  print(f"Progress: {frame_count}/{total_frames} frames ({progress:.1f}%)")
982
 
 
984
  cap.release()
985
  out.release()
986
 
987
+ if progress_callback:
988
+ progress_callback(1.0, "Completed!")
989
+
990
  print(f"✓ Completed! Processed {frame_count} frames using {method} method.")
991
 
992
  return frame_count > 0