kr4phy commited on
Commit
63ea6ab
ยท
1 Parent(s): 2c5dee6

Enhance lane detection functionality with method selection and GPU support

Browse files
Files changed (4) hide show
  1. .vscode/launch.json +11 -0
  2. app.py +72 -21
  3. cli.py +33 -11
  4. lane_detection.py +491 -48
.vscode/launch.json ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "configurations": [
3
+ {
4
+ "name": "Python ๋””๋ฒ„๊ฑฐ: ํ˜„์žฌ ํŒŒ์ผ",
5
+ "type": "debugpy",
6
+ "request": "launch",
7
+ "program": "${file}",
8
+ "console": "integratedTerminal"
9
+ }
10
+ ]
11
+ }
app.py CHANGED
@@ -3,22 +3,22 @@ import tempfile
3
  from lane_detection import process_video as process_video_file
4
 
5
 
6
- def process_video(video_path):
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
13
-
14
  # Create temporary output file
15
  temp_output = tempfile.NamedTemporaryFile(delete=False, suffix='.mp4')
16
  output_path = temp_output.name
17
  temp_output.close()
18
-
19
- # Process the video
20
- success = process_video_file(video_path, output_path)
21
-
22
  if success:
23
  return output_path
24
  else:
@@ -28,34 +28,85 @@ def process_video(video_path):
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. The result will show the original video on the left and the lane-detected video on the right.")
32
-
33
  with gr.Row():
34
  with gr.Column():
35
  video_input = gr.Video(label="Upload Video")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  process_btn = gr.Button("Process Video", variant="primary")
37
-
38
  with gr.Column():
39
  video_output = gr.Video(label="Result (Original | Lane Detection)")
40
-
 
 
 
 
 
 
 
 
 
 
 
 
41
  process_btn.click(
42
  fn=process_video,
43
- inputs=video_input,
44
  outputs=video_output
45
  )
46
-
47
  gr.Markdown("""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  ### How it works:
49
  1. Upload a video file containing road scenes
50
- 2. Click "Process Video" button
51
- 3. The system will:
52
- - Convert frames to grayscale
53
- - Apply Gaussian blur to reduce noise
54
- - Use Canny edge detection to find edges
55
- - Apply region of interest (ROI) mask to focus on the road
56
- - Use Hough transform to detect lane lines
57
- - Draw detected lanes on the original video
58
- 4. View the side-by-side comparison result
 
 
59
  """)
60
 
61
 
 
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
13
+
14
  # Create temporary output file
15
  temp_output = tempfile.NamedTemporaryFile(delete=False, suffix='.mp4')
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
24
  else:
 
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():
35
  video_input = gr.Video(label="Upload Video")
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(
46
+ value=True,
47
+ label="Enhanced Thresholding",
48
+ info="Better accuracy but slightly slower (Advanced method only)",
49
+ visible=True
50
+ )
51
+
52
+ segmented_checkbox = gr.Checkbox(
53
+ value=False,
54
+ label="Segmented Lines",
55
+ info="Multiple segments for better curve representation (Basic method only)",
56
+ visible=False
57
+ )
58
+
59
  process_btn = gr.Button("Process Video", variant="primary")
60
+
61
  with gr.Column():
62
  video_output = gr.Video(label="Result (Original | Lane Detection)")
63
+
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(
71
+ fn=update_checkboxes,
72
+ inputs=method_selector,
73
+ outputs=[enhanced_checkbox, segmented_checkbox]
74
+ )
75
+
76
  process_btn.click(
77
  fn=process_video,
78
+ inputs=[video_input, method_selector, enhanced_checkbox, segmented_checkbox],
79
  outputs=video_output
80
  )
81
+
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
 
cli.py CHANGED
@@ -1,7 +1,7 @@
1
  #!/usr/bin/env python3
2
  """
3
  Command-line interface for lane detection
4
- Usage: python cli.py <input_video> <output_video>
5
  """
6
  import sys
7
  import os
@@ -9,31 +9,53 @@ from lane_detection import process_video
9
 
10
 
11
  def main():
12
- if len(sys.argv) != 3:
13
- print("Usage: python cli.py <input_video> <output_video>")
14
- print("\nExample:")
 
 
 
 
 
 
15
  print(" python cli.py road_video.mp4 output_result.mp4")
 
 
 
16
  sys.exit(1)
17
-
18
  input_path = sys.argv[1]
19
  output_path = sys.argv[2]
20
-
 
 
 
 
 
 
 
 
21
  # Check if input file exists
22
  if not os.path.exists(input_path):
23
  print(f"Error: Input file '{input_path}' not found!")
24
  sys.exit(1)
25
-
26
  print(f"Processing video: {input_path}")
27
  print(f"Output will be saved to: {output_path}")
 
 
 
 
 
28
  print("\nProcessing...")
29
-
30
  try:
31
- success = process_video(input_path, output_path)
32
-
33
  if success:
34
  print("\nโœ“ Video processing completed successfully!")
35
  print(f"โœ“ Result saved to: {output_path}")
36
-
37
  if os.path.exists(output_path):
38
  size = os.path.getsize(output_path)
39
  print(f"โœ“ File size: {size:,} bytes")
 
1
  #!/usr/bin/env python3
2
  """
3
  Command-line interface for lane detection
4
+ Usage: python cli.py <input_video> <output_video> [method] [enhanced] [segmented]
5
  """
6
  import sys
7
  import os
 
9
 
10
 
11
  def main():
12
+ if len(sys.argv) < 3 or len(sys.argv) > 6:
13
+ print("Usage: python cli.py <input_video> <output_video> [method] [enhanced] [segmented]")
14
+ print("\nArguments:")
15
+ print(" input_video: Path to input video file")
16
+ print(" output_video: Path to output video file")
17
+ print(" method: 'basic' or 'advanced' (default: advanced)")
18
+ print(" enhanced: 'true' or 'false' for enhanced thresholding (default: true, advanced only)")
19
+ print(" segmented: 'true' or 'false' for segmented lines (default: false, basic only)")
20
+ print("\nExamples:")
21
  print(" python cli.py road_video.mp4 output_result.mp4")
22
+ print(" python cli.py road_video.mp4 output_result.mp4 basic")
23
+ print(" python cli.py road_video.mp4 output_result.mp4 basic false true")
24
+ print(" python cli.py road_video.mp4 output_result.mp4 advanced true false")
25
  sys.exit(1)
26
+
27
  input_path = sys.argv[1]
28
  output_path = sys.argv[2]
29
+ method = sys.argv[3] if len(sys.argv) >= 4 else "advanced"
30
+ enhanced = sys.argv[4].lower() == "true" if len(sys.argv) >= 5 else True
31
+ segmented = sys.argv[5].lower() == "true" if len(sys.argv) >= 6 else False
32
+
33
+ # Validate method
34
+ if method not in ["basic", "advanced"]:
35
+ print(f"Error: Invalid method '{method}'. Use 'basic' or 'advanced'")
36
+ sys.exit(1)
37
+
38
  # Check if input file exists
39
  if not os.path.exists(input_path):
40
  print(f"Error: Input file '{input_path}' not found!")
41
  sys.exit(1)
42
+
43
  print(f"Processing video: {input_path}")
44
  print(f"Output will be saved to: {output_path}")
45
+ print(f"Method: {method}")
46
+ if method == "advanced":
47
+ print(f"Enhanced thresholding: {'enabled' if enhanced else 'disabled'}")
48
+ if method == "basic":
49
+ print(f"Segmented lines: {'enabled' if segmented else 'disabled'}")
50
  print("\nProcessing...")
51
+
52
  try:
53
+ success = process_video(input_path, output_path, method, enhanced, segmented)
54
+
55
  if success:
56
  print("\nโœ“ Video processing completed successfully!")
57
  print(f"โœ“ Result saved to: {output_path}")
58
+
59
  if os.path.exists(output_path):
60
  size = os.path.getsize(output_path)
61
  print(f"โœ“ File size: {size:,} bytes")
lane_detection.py CHANGED
@@ -1,10 +1,26 @@
1
  """
2
  Lane detection module using OpenCV
 
3
  This module contains the core lane detection logic without UI dependencies.
4
  """
5
  import cv2
6
  import numpy as np
7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
 
9
  def region_of_interest(img, vertices):
10
  """
@@ -16,61 +32,61 @@ def region_of_interest(img, vertices):
16
  return masked_image
17
 
18
 
19
- def draw_lines(img, lines, color=[0, 255, 0], thickness=3):
20
  """
21
- Draw lines on the image with filled lane area.
22
  - Lane lines: Red color
23
  - Lane interior: Green semi-transparent fill
24
  """
25
  if lines is None:
26
  return img
27
-
28
  line_img = np.zeros_like(img)
29
-
30
  # Separate left and right lane lines
31
  left_lines = []
32
  right_lines = []
33
-
34
  for line in lines:
35
  x1, y1, x2, y2 = line[0]
36
  if x2 == x1:
37
  continue
38
  slope = (y2 - y1) / (x2 - x1)
39
-
40
  # Filter by slope to separate left and right lanes
41
  if slope < -0.5: # Left lane (negative slope)
42
  left_lines.append(line[0])
43
  elif slope > 0.5: # Right lane (positive slope)
44
  right_lines.append(line[0])
45
-
46
  # Average lines for left and right lanes
47
  def average_lines(lines, img_shape):
48
  if len(lines) == 0:
49
  return None
50
-
51
  x_coords = []
52
  y_coords = []
53
-
54
  for line in lines:
55
  x1, y1, x2, y2 = line
56
  x_coords.extend([x1, x2])
57
  y_coords.extend([y1, y2])
58
-
59
  # Fit a polynomial to the points
60
  poly = np.polyfit(y_coords, x_coords, 1)
61
-
62
  # Calculate line endpoints
63
  y1 = img_shape[0]
64
  y2 = int(img_shape[0] * 0.6)
65
  x1 = int(poly[0] * y1 + poly[1])
66
  x2 = int(poly[0] * y2 + poly[1])
67
-
68
  return [x1, y1, x2, y2]
69
-
70
  # Draw averaged lines
71
  left_line = average_lines(left_lines, img.shape)
72
  right_line = average_lines(right_lines, img.shape)
73
-
74
  # Fill the lane area with green color
75
  if left_line is not None and right_line is not None:
76
  # Create polygon points for the lane area
@@ -80,38 +96,166 @@ def draw_lines(img, lines, color=[0, 255, 0], thickness=3):
80
  (right_line[2], right_line[3]), # Right top
81
  (right_line[0], right_line[1]) # Right bottom
82
  ]], dtype=np.int32)
83
-
84
  # Fill the lane area with green (semi-transparent)
85
  cv2.fillPoly(line_img, lane_polygon, (0, 255, 0))
86
-
87
  # Draw the lane lines in red with thicker lines
88
  if left_line is not None:
89
- cv2.line(line_img, (left_line[0], left_line[1]), (left_line[2], left_line[3]),
90
  (0, 0, 255), thickness * 2) # Red color (BGR format)
91
-
92
  if right_line is not None:
93
- cv2.line(line_img, (right_line[0], right_line[1]), (right_line[2], right_line[3]),
94
  (0, 0, 255), thickness * 2) # Red color (BGR format)
95
-
96
  # Blend with original image (make the overlay semi-transparent)
97
  return cv2.addWeighted(img, 0.8, line_img, 0.5, 0)
98
 
99
 
100
- def process_frame(frame):
101
  """
102
- Process a single frame for lane detection.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
  """
104
  height, width = frame.shape[:2]
105
-
106
- # Convert to grayscale
107
- gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
108
-
109
- # Apply Gaussian blur
110
- blur = cv2.GaussianBlur(gray, (5, 5), 0)
111
-
112
- # Apply Canny edge detection
113
- edges = cv2.Canny(blur, 50, 150)
114
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
  # Define region of interest (ROI)
116
  vertices = np.array([[
117
  (int(width * 0.1), height),
@@ -119,10 +263,10 @@ def process_frame(frame):
119
  (int(width * 0.55), int(height * 0.6)),
120
  (int(width * 0.9), height)
121
  ]], dtype=np.int32)
122
-
123
  # Apply ROI mask
124
  masked_edges = region_of_interest(edges, vertices)
125
-
126
  # Apply Hough transform to detect lines
127
  lines = cv2.HoughLinesP(
128
  masked_edges,
@@ -132,54 +276,353 @@ def process_frame(frame):
132
  minLineLength=40,
133
  maxLineGap=100
134
  )
135
-
136
  # Draw detected lanes on the original frame
137
- result = draw_lines(frame.copy(), lines)
138
-
 
 
 
139
  return result
140
 
141
 
142
- def process_video(input_path, output_path):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
  """
144
  Process the video and create side-by-side comparison.
 
 
 
145
  Returns True if successful, False otherwise.
146
  """
147
  # Open the video
148
  cap = cv2.VideoCapture(input_path)
149
-
150
  if not cap.isOpened():
151
  return False
152
-
153
  # Get video properties
154
  fps = int(cap.get(cv2.CAP_PROP_FPS))
155
  width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
156
  height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
157
-
 
158
  # Video writer for output (side-by-side, so width is doubled)
159
  # Use H264 codec for better web browser compatibility
160
  fourcc = cv2.VideoWriter_fourcc(*'avc1')
161
  out = cv2.VideoWriter(output_path, fourcc, fps, (width * 2, height))
162
-
163
  frame_count = 0
 
 
 
 
 
 
164
  # Process each frame
165
  while True:
166
  ret, frame = cap.read()
167
  if not ret:
168
  break
169
-
170
  # Process frame for lane detection
171
- processed_frame = process_frame(frame)
172
-
173
  # Create side-by-side comparison
174
  # Original on left, processed on right
175
  combined = np.hstack((frame, processed_frame))
176
-
177
  # Write the combined frame
178
  out.write(combined)
179
  frame_count += 1
180
-
 
 
 
 
 
181
  # Release resources
182
  cap.release()
183
  out.release()
184
-
 
 
185
  return frame_count > 0
 
1
  """
2
  Lane detection module using OpenCV
3
+ Advanced lane detection with multiple methods and GPU acceleration.
4
  This module contains the core lane detection logic without UI dependencies.
5
  """
6
  import cv2
7
  import numpy as np
8
 
9
+ # GPU acceleration setup - prioritize NVIDIA GPU
10
+ USE_GPU = False
11
+ GPU_TYPE = "none"
12
+ try:
13
+ if cv2.cuda.getCudaEnabledDeviceCount() > 0:
14
+ USE_GPU = True
15
+ GPU_TYPE = "nvidia"
16
+ cv2.cuda.setDevice(0) # Use first GPU
17
+ print(f"โœ“ NVIDIA CUDA enabled! Using GPU acceleration on device: {cv2.cuda.printShortCudaDeviceInfo(cv2.cuda.getDevice())}")
18
+ else:
19
+ print("โ„น CUDA not available. Using CPU.")
20
+ except Exception as e:
21
+ print(f"โ„น GPU acceleration not available: {e}. Using CPU.")
22
+ GPU_TYPE = "none"
23
+
24
 
25
  def region_of_interest(img, vertices):
26
  """
 
32
  return masked_image
33
 
34
 
35
+ def draw_lines_basic(img, lines, color=[0, 255, 0], thickness=3):
36
  """
37
+ Draw lines on the image with filled lane area (Basic method).
38
  - Lane lines: Red color
39
  - Lane interior: Green semi-transparent fill
40
  """
41
  if lines is None:
42
  return img
43
+
44
  line_img = np.zeros_like(img)
45
+
46
  # Separate left and right lane lines
47
  left_lines = []
48
  right_lines = []
49
+
50
  for line in lines:
51
  x1, y1, x2, y2 = line[0]
52
  if x2 == x1:
53
  continue
54
  slope = (y2 - y1) / (x2 - x1)
55
+
56
  # Filter by slope to separate left and right lanes
57
  if slope < -0.5: # Left lane (negative slope)
58
  left_lines.append(line[0])
59
  elif slope > 0.5: # Right lane (positive slope)
60
  right_lines.append(line[0])
61
+
62
  # Average lines for left and right lanes
63
  def average_lines(lines, img_shape):
64
  if len(lines) == 0:
65
  return None
66
+
67
  x_coords = []
68
  y_coords = []
69
+
70
  for line in lines:
71
  x1, y1, x2, y2 = line
72
  x_coords.extend([x1, x2])
73
  y_coords.extend([y1, y2])
74
+
75
  # Fit a polynomial to the points
76
  poly = np.polyfit(y_coords, x_coords, 1)
77
+
78
  # Calculate line endpoints
79
  y1 = img_shape[0]
80
  y2 = int(img_shape[0] * 0.6)
81
  x1 = int(poly[0] * y1 + poly[1])
82
  x2 = int(poly[0] * y2 + poly[1])
83
+
84
  return [x1, y1, x2, y2]
85
+
86
  # Draw averaged lines
87
  left_line = average_lines(left_lines, img.shape)
88
  right_line = average_lines(right_lines, img.shape)
89
+
90
  # Fill the lane area with green color
91
  if left_line is not None and right_line is not None:
92
  # Create polygon points for the lane area
 
96
  (right_line[2], right_line[3]), # Right top
97
  (right_line[0], right_line[1]) # Right bottom
98
  ]], dtype=np.int32)
99
+
100
  # Fill the lane area with green (semi-transparent)
101
  cv2.fillPoly(line_img, lane_polygon, (0, 255, 0))
102
+
103
  # Draw the lane lines in red with thicker lines
104
  if left_line is not None:
105
+ cv2.line(line_img, (left_line[0], left_line[1]), (left_line[2], left_line[3]),
106
  (0, 0, 255), thickness * 2) # Red color (BGR format)
107
+
108
  if right_line is not None:
109
+ cv2.line(line_img, (right_line[0], right_line[1]), (right_line[2], right_line[3]),
110
  (0, 0, 255), thickness * 2) # Red color (BGR format)
111
+
112
  # Blend with original image (make the overlay semi-transparent)
113
  return cv2.addWeighted(img, 0.8, line_img, 0.5, 0)
114
 
115
 
116
+ def draw_lines_segmented(img, lines, color=[0, 255, 0], thickness=3):
117
  """
118
+ Draw multiple short line segments to represent curves.
119
+ Better curve representation with Hough Transform.
120
+ - Lane lines: Red segmented lines
121
+ - Lane interior: Green semi-transparent fill
122
+ """
123
+ if lines is None:
124
+ return img
125
+
126
+ line_img = np.zeros_like(img)
127
+ fill_img = np.zeros_like(img)
128
+
129
+ # Separate left and right lane lines
130
+ left_lines = []
131
+ right_lines = []
132
+
133
+ for line in lines:
134
+ x1, y1, x2, y2 = line[0]
135
+ if x2 == x1:
136
+ continue
137
+ slope = (y2 - y1) / (x2 - x1)
138
+
139
+ # Filter by slope to separate left and right lanes
140
+ if slope < -0.5: # Left lane (negative slope)
141
+ left_lines.append(line[0])
142
+ elif slope > 0.5: # Right lane (positive slope)
143
+ right_lines.append(line[0])
144
+
145
+ # Extract left and right lane boundaries
146
+ left_x = []
147
+ left_y = []
148
+ right_x = []
149
+ right_y = []
150
+
151
+ for line in left_lines:
152
+ x1, y1, x2, y2 = line
153
+ left_x.extend([x1, x2])
154
+ left_y.extend([y1, y2])
155
+
156
+ for line in right_lines:
157
+ x1, y1, x2, y2 = line
158
+ right_x.extend([x1, x2])
159
+ right_y.extend([y1, y2])
160
+
161
+ # Initialize sorted lists
162
+ left_x_sorted = []
163
+ left_y_sorted = []
164
+ right_x_sorted = []
165
+ right_y_sorted = []
166
+
167
+ # Sort by y coordinate to maintain order
168
+ if len(left_x) > 0:
169
+ left_coords = sorted(zip(left_y, left_x))
170
+ left_y_sorted = [c[0] for c in left_coords]
171
+ left_x_sorted = [c[1] for c in left_coords]
172
+
173
+ # Draw all individual line segments for left lane
174
+ for line in left_lines:
175
+ x1, y1, x2, y2 = line
176
+ cv2.line(line_img, (x1, y1), (x2, y2), (0, 0, 255), thickness + 2)
177
+
178
+ if len(right_x) > 0:
179
+ right_coords = sorted(zip(right_y, right_x))
180
+ right_y_sorted = [c[0] for c in right_coords]
181
+ right_x_sorted = [c[1] for c in right_coords]
182
+
183
+ # Draw all individual line segments for right lane
184
+ for line in right_lines:
185
+ x1, y1, x2, y2 = line
186
+ cv2.line(line_img, (x1, y1), (x2, y2), (0, 0, 255), thickness + 2)
187
+
188
+ # Fill the area between left and right lanes
189
+ if len(left_y_sorted) > 0 and len(right_y_sorted) > 0:
190
+ # Create a polygon by combining left and right points
191
+ min_y = max(min(left_y_sorted), min(right_y_sorted))
192
+ max_y = min(max(left_y_sorted), max(right_y_sorted))
193
+
194
+ if max_y > min_y:
195
+ # Interpolate to get matching y-coordinates
196
+ y_range = np.arange(int(min_y), int(max_y), 10)
197
+
198
+ poly_points = []
199
+
200
+ # Left points
201
+ for y in y_range:
202
+ if y >= min(left_y_sorted) and y <= max(left_y_sorted):
203
+ idx = np.searchsorted(left_y_sorted, y)
204
+ if idx > 0 and idx < len(left_x_sorted):
205
+ x = left_x_sorted[idx]
206
+ poly_points.append([x, y])
207
+
208
+ # Right points (reverse order for polygon)
209
+ for y in reversed(y_range):
210
+ if y >= min(right_y_sorted) and y <= max(right_y_sorted):
211
+ idx = np.searchsorted(right_y_sorted, y)
212
+ if idx > 0 and idx < len(right_x_sorted):
213
+ x = right_x_sorted[idx]
214
+ poly_points.append([x, y])
215
+
216
+ if len(poly_points) >= 3:
217
+ poly_points = np.array(poly_points, dtype=np.int32)
218
+ cv2.fillPoly(fill_img, [poly_points], (0, 255, 0))
219
+
220
+ # Combine filled area and lines
221
+ result_img = cv2.addWeighted(line_img, 0.6, fill_img, 0.7, 0)
222
+
223
+ # Blend with original image
224
+ return cv2.addWeighted(img, 0.8, result_img, 0.5, 0)
225
+
226
+
227
+ def process_frame_basic(frame, use_segmented=False):
228
+ """
229
+ Process a single frame for lane detection using basic Hough Transform method.
230
+ use_segmented: If True, draw multiple line segments for better curve representation.
231
+ If False, draw averaged single line (default).
232
  """
233
  height, width = frame.shape[:2]
234
+
235
+ if USE_GPU and GPU_TYPE == "nvidia":
236
+ # Upload frame to GPU
237
+ gpu_frame = cv2.cuda_GpuMat()
238
+ gpu_frame.upload(frame)
239
+
240
+ # Convert to grayscale on GPU
241
+ gpu_gray = cv2.cuda.cvtColor(gpu_frame, cv2.COLOR_BGR2GRAY)
242
+
243
+ # Apply Gaussian blur on GPU
244
+ gpu_blur = cv2.cuda.createGaussianFilter(cv2.CV_8UC1, cv2.CV_8UC1, (5, 5), 0)
245
+ gpu_blurred = gpu_blur.apply(gpu_gray)
246
+
247
+ # Apply Canny edge detection on GPU
248
+ gpu_canny = cv2.cuda.createCannyEdgeDetector(50, 150)
249
+ gpu_edges = gpu_canny.detect(gpu_blurred)
250
+
251
+ # Download edges from GPU
252
+ edges = gpu_edges.download()
253
+ else:
254
+ # CPU processing
255
+ gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
256
+ blur = cv2.GaussianBlur(gray, (5, 5), 0)
257
+ edges = cv2.Canny(blur, 50, 150)
258
+
259
  # Define region of interest (ROI)
260
  vertices = np.array([[
261
  (int(width * 0.1), height),
 
263
  (int(width * 0.55), int(height * 0.6)),
264
  (int(width * 0.9), height)
265
  ]], dtype=np.int32)
266
+
267
  # Apply ROI mask
268
  masked_edges = region_of_interest(edges, vertices)
269
+
270
  # Apply Hough transform to detect lines
271
  lines = cv2.HoughLinesP(
272
  masked_edges,
 
276
  minLineLength=40,
277
  maxLineGap=100
278
  )
279
+
280
  # Draw detected lanes on the original frame
281
+ if use_segmented:
282
+ result = draw_lines_segmented(frame.copy(), lines)
283
+ else:
284
+ result = draw_lines_basic(frame.copy(), lines)
285
+
286
  return result
287
 
288
 
289
+ def calibrate_perspective(img):
290
+ """
291
+ Apply perspective transform to get bird's eye view.
292
+ Converts trapezoidal ROI to rectangular view for easier lane detection.
293
+ """
294
+ height, width = img.shape[:2]
295
+
296
+ # Define source points (trapezoid in original image)
297
+ src = np.float32([
298
+ [width * 0.45, height * 0.65], # Bottom left
299
+ [width * 0.55, height * 0.65], # Bottom right
300
+ [width * 0.9, height], # Top right
301
+ [width * 0.1, height] # Top left
302
+ ])
303
+
304
+ # Define destination points (rectangle in bird's eye view)
305
+ dst = np.float32([
306
+ [width * 0.25, 0], # Top left
307
+ [width * 0.75, 0], # Top right
308
+ [width * 0.75, height], # Bottom right
309
+ [width * 0.25, height] # Bottom left
310
+ ])
311
+
312
+ # Calculate perspective transform matrix
313
+ M = cv2.getPerspectiveTransform(src, dst)
314
+ # Calculate inverse perspective transform matrix
315
+ Minv = cv2.getPerspectiveTransform(dst, src)
316
+
317
+ # Apply perspective transform
318
+ warped = cv2.warpPerspective(img, M, (width, height), flags=cv2.INTER_LINEAR)
319
+
320
+ return warped, M, Minv
321
+
322
+
323
+ def color_and_gradient_threshold(img, use_enhanced=True):
324
+ """
325
+ Apply color and gradient thresholding to isolate lane lines.
326
+ Enhanced version with better accuracy for various conditions.
327
+ Returns binary image with lane pixels set to 255.
328
+ """
329
+ # Convert to HLS color space
330
+ hls = cv2.cvtColor(img, cv2.COLOR_BGR2HLS)
331
+
332
+ # Extract channels
333
+ h_channel = hls[:, :, 0]
334
+ l_channel = hls[:, :, 1]
335
+ s_channel = hls[:, :, 2]
336
+
337
+ # Enhanced thresholding for better lane detection
338
+ if use_enhanced:
339
+ # Adaptive thresholding for saturation channel
340
+ s_thresh = (90, 255) # Lower threshold for yellow lanes
341
+ s_binary = np.zeros_like(s_channel)
342
+ s_binary[(s_channel >= s_thresh[0]) & (s_channel <= s_thresh[1])] = 255
343
+
344
+ # Adaptive thresholding for lightness channel
345
+ l_thresh = (180, 255) # Lower threshold for white lanes
346
+ l_binary = np.zeros_like(l_channel)
347
+ l_binary[(l_channel >= l_thresh[0]) & (l_channel <= l_thresh[1])] = 255
348
+
349
+ # Apply CLAHE (Contrast Limited Adaptive Histogram Equalization) for better contrast
350
+ clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
351
+ l_channel_enhanced = clahe.apply(l_channel.astype(np.uint8))
352
+
353
+ # Apply Sobel operator on enhanced channel
354
+ sobelx = cv2.Sobel(l_channel_enhanced, cv2.CV_64F, 1, 0, ksize=3)
355
+ abs_sobelx = np.absolute(sobelx)
356
+ scaled_sobel = np.uint8(255 * abs_sobelx / np.max(abs_sobelx))
357
+
358
+ # More sensitive gradient threshold
359
+ sobel_thresh = (15, 255) # Lower threshold for better edge detection
360
+ sobel_binary = np.zeros_like(scaled_sobel)
361
+ sobel_binary[(scaled_sobel >= sobel_thresh[0]) & (scaled_sobel <= sobel_thresh[1])] = 255
362
+
363
+ # Direction threshold to focus on vertical edges
364
+ sobely = cv2.Sobel(l_channel_enhanced, cv2.CV_64F, 0, 1, ksize=3)
365
+ abs_sobely = np.absolute(sobely)
366
+ scaled_sobely = np.uint8(255 * abs_sobely / np.max(abs_sobely))
367
+
368
+ # Calculate gradient direction
369
+ grad_dir = np.arctan2(abs_sobely, abs_sobelx)
370
+ dir_thresh = (0.7, 1.3) # Focus on near-vertical edges (lane lines)
371
+ dir_binary = np.zeros_like(scaled_sobel)
372
+ dir_binary[(grad_dir >= dir_thresh[0]) & (grad_dir <= dir_thresh[1])] = 255
373
+
374
+ # Combine all binary images with direction filter
375
+ combined_binary = np.zeros_like(s_binary)
376
+ combined_binary[((s_binary == 255) | (l_binary == 255) | (sobel_binary == 255)) & (dir_binary == 255)] = 255
377
+
378
+ # Apply morphological operations to reduce noise
379
+ kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
380
+ combined_binary = cv2.morphologyEx(combined_binary, cv2.MORPH_CLOSE, kernel)
381
+ combined_binary = cv2.morphologyEx(combined_binary, cv2.MORPH_OPEN, kernel)
382
+
383
+ else:
384
+ # Basic thresholding
385
+ s_thresh = (100, 255)
386
+ s_binary = np.zeros_like(s_channel)
387
+ s_binary[(s_channel >= s_thresh[0]) & (s_channel <= s_thresh[1])] = 255
388
+
389
+ l_thresh = (200, 255)
390
+ l_binary = np.zeros_like(l_channel)
391
+ l_binary[(l_channel >= l_thresh[0]) & (l_channel <= l_thresh[1])] = 255
392
+
393
+ sobelx = cv2.Sobel(l_channel, cv2.CV_64F, 1, 0, ksize=3)
394
+ abs_sobelx = np.absolute(sobelx)
395
+ scaled_sobel = np.uint8(255 * abs_sobelx / np.max(abs_sobelx))
396
+
397
+ sobel_thresh = (20, 255)
398
+ sobel_binary = np.zeros_like(scaled_sobel)
399
+ sobel_binary[(scaled_sobel >= sobel_thresh[0]) & (scaled_sobel <= sobel_thresh[1])] = 255
400
+
401
+ combined_binary = np.zeros_like(s_binary)
402
+ combined_binary[(s_binary == 255) | (l_binary == 255) | (sobel_binary == 255)] = 255
403
+
404
+ return combined_binary
405
+
406
+
407
+ def fit_polynomial_lanes(binary_warped):
408
+ """
409
+ Fit 2nd degree polynomials to lane lines using sliding window approach.
410
+ Returns left and right lane polynomial coefficients.
411
+ """
412
+ # Take a histogram of the bottom half of the image
413
+ histogram = np.sum(binary_warped[binary_warped.shape[0]//2:, :], axis=0)
414
+
415
+ # Find the peak of the left and right halves of the histogram
416
+ midpoint = len(histogram) // 2
417
+ leftx_base = np.argmax(histogram[:midpoint])
418
+ rightx_base = np.argmax(histogram[midpoint:]) + midpoint
419
+
420
+ # Sliding window parameters
421
+ nwindows = 9
422
+ window_height = binary_warped.shape[0] // nwindows
423
+ margin = 100
424
+ minpix = 50
425
+
426
+ # Find nonzero pixels
427
+ nonzero = binary_warped.nonzero()
428
+ nonzeroy = np.array(nonzero[0])
429
+ nonzerox = np.array(nonzero[1])
430
+
431
+ # Current positions
432
+ leftx_current = leftx_base
433
+ rightx_current = rightx_base
434
+
435
+ # Lists to store lane pixel indices
436
+ left_lane_inds = []
437
+ right_lane_inds = []
438
+
439
+ # Step through windows
440
+ for window in range(nwindows):
441
+ # Window boundaries
442
+ win_y_low = binary_warped.shape[0] - (window + 1) * window_height
443
+ win_y_high = binary_warped.shape[0] - window * window_height
444
+
445
+ win_xleft_low = leftx_current - margin
446
+ win_xleft_high = leftx_current + margin
447
+ win_xright_low = rightx_current - margin
448
+ win_xright_high = rightx_current + margin
449
+
450
+ # Find pixels within windows
451
+ good_left_inds = ((nonzeroy >= win_y_low) & (nonzeroy < win_y_high) &
452
+ (nonzerox >= win_xleft_low) & (nonzerox < win_xleft_high)).nonzero()[0]
453
+ good_right_inds = ((nonzeroy >= win_y_low) & (nonzeroy < win_y_high) &
454
+ (nonzerox >= win_xright_low) & (nonzerox < win_xright_high)).nonzero()[0]
455
+
456
+ left_lane_inds.append(good_left_inds)
457
+ right_lane_inds.append(good_right_inds)
458
+
459
+ # Recenter windows
460
+ if len(good_left_inds) > minpix:
461
+ leftx_current = int(np.mean(nonzerox[good_left_inds]))
462
+ if len(good_right_inds) > minpix:
463
+ rightx_current = int(np.mean(nonzerox[good_right_inds]))
464
+
465
+ # Concatenate indices
466
+ left_lane_inds = np.concatenate(left_lane_inds)
467
+ right_lane_inds = np.concatenate(right_lane_inds)
468
+
469
+ # Extract pixel positions
470
+ leftx = nonzerox[left_lane_inds]
471
+ lefty = nonzeroy[left_lane_inds]
472
+ rightx = nonzerox[right_lane_inds]
473
+ righty = nonzeroy[right_lane_inds]
474
+
475
+ # Fit polynomial (2nd degree)
476
+ left_fit = None
477
+ right_fit = None
478
+
479
+ if len(leftx) > 0:
480
+ left_fit = np.polyfit(lefty, leftx, 2)
481
+ if len(rightx) > 0:
482
+ right_fit = np.polyfit(righty, rightx, 2)
483
+
484
+ return left_fit, right_fit
485
+
486
+
487
+ def draw_poly_lines(img, binary_warped, left_fit, right_fit, Minv):
488
+ """
489
+ Draw polynomial lane lines on the original image using inverse perspective transform.
490
+ """
491
+ if left_fit is None or right_fit is None:
492
+ return img
493
+
494
+ # Create an image to draw on
495
+ warp_zero = np.zeros_like(binary_warped).astype(np.uint8)
496
+ color_warp = np.dstack((warp_zero, warp_zero, warp_zero))
497
+
498
+ # Generate y values
499
+ ploty = np.linspace(0, binary_warped.shape[0] - 1, binary_warped.shape[0])
500
+
501
+ # Calculate x values using polynomial
502
+ left_fitx = left_fit[0] * ploty**2 + left_fit[1] * ploty + left_fit[2]
503
+ right_fitx = right_fit[0] * ploty**2 + right_fit[1] * ploty + right_fit[2]
504
+
505
+ # Ensure values are within image bounds
506
+ left_fitx = np.clip(left_fitx, 0, binary_warped.shape[1] - 1)
507
+ right_fitx = np.clip(right_fitx, 0, binary_warped.shape[1] - 1)
508
+
509
+ # Create points for the lane area
510
+ pts_left = np.array([np.transpose(np.vstack([left_fitx, ploty]))])
511
+ pts_right = np.array([np.flipud(np.transpose(np.vstack([right_fitx, ploty])))])
512
+ pts = np.hstack((pts_left, pts_right))
513
+
514
+ # Fill the lane area with green
515
+ cv2.fillPoly(color_warp, np.int_([pts]), (0, 255, 0))
516
+
517
+ # Draw the lane lines in red
518
+ cv2.polylines(color_warp, np.int32([pts_left]), isClosed=False, color=(0, 0, 255), thickness=15)
519
+ cv2.polylines(color_warp, np.int32([pts_right]), isClosed=False, color=(0, 0, 255), thickness=15)
520
+
521
+ # Apply inverse perspective transform to project back to original image
522
+ newwarp = cv2.warpPerspective(color_warp, Minv, (img.shape[1], img.shape[0]))
523
+
524
+ # Combine with original image
525
+ result = cv2.addWeighted(img, 0.8, newwarp, 0.5, 0)
526
+
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):
546
+ """
547
+ Process a single frame for lane detection using advanced pipeline.
548
+ 1. Perspective transform to bird's eye view
549
+ 2. Enhanced color and gradient thresholding
550
+ 3. Polynomial fitting with sliding windows
551
+ 4. Draw lanes with inverse perspective transform
552
+ """
553
+ # Step 1: Apply perspective transform to get bird's eye view
554
+ warped, M, Minv = calibrate_perspective(frame)
555
+
556
+ # Step 2: Apply enhanced color and gradient thresholding
557
+ binary_warped = color_and_gradient_threshold(warped, use_enhanced)
558
+
559
+ # Step 3: Fit polynomial lanes using sliding window approach
560
+ left_fit, right_fit = fit_polynomial_lanes(binary_warped)
561
+
562
+ # Step 4: Draw polynomial lines on original image
563
+ result = draw_poly_lines(frame, binary_warped, left_fit, right_fit, Minv)
564
+
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
577
  cap = cv2.VideoCapture(input_path)
578
+
579
  if not cap.isOpened():
580
  return False
581
+
582
  # Get video properties
583
  fps = int(cap.get(cv2.CAP_PROP_FPS))
584
  width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
585
  height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
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
594
+ print(f"Processing {total_frames} frames using {method} method...")
595
+ if method == "advanced" and use_enhanced:
596
+ print("Enhanced thresholding enabled for better accuracy")
597
+ if method == "basic" and use_segmented:
598
+ print("Segmented line mode enabled for better curve representation")
599
+
600
  # Process each frame
601
  while True:
602
  ret, frame = cap.read()
603
  if not ret:
604
  break
605
+
606
  # Process frame for lane detection
607
+ processed_frame = process_frame(frame, method, use_enhanced, use_segmented)
608
+
609
  # Create side-by-side comparison
610
  # Original on left, processed on right
611
  combined = np.hstack((frame, processed_frame))
612
+
613
  # Write the combined frame
614
  out.write(combined)
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
+
622
  # Release resources
623
  cap.release()
624
  out.release()
625
+
626
+ print(f"โœ“ Completed! Processed {frame_count} frames using {method} method.")
627
+
628
  return frame_count > 0