File size: 8,572 Bytes
5577bf7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
import cv2
import numpy as np
import gradio as gr
import tempfile

# Define Utility Functions
def region_of_interest(img, vertices):
    """Select the region of interest (ROI) from a defined list of vertices."""
    mask = np.zeros_like(img)
    if len(img.shape) > 2:
        channel_count = img.shape[2]  # 3 or 4 depending on your image.
        ignore_mask_color = (255,) * channel_count
    else:
        ignore_mask_color = 255
    cv2.fillPoly(mask, [vertices], ignore_mask_color)
    masked_image = cv2.bitwise_and(img, mask)
    return masked_image

def draw_lines(img, lines, color=[255, 0, 0], thickness=2):
    """Utility for drawing lines."""
    if lines is not None:
        for line in lines:
            for x1,y1,x2,y2 in line:
                cv2.line(img, (x1, y1), (x2, y2), color, thickness)

def hough_lines(img, rho, theta, threshold, min_line_len, max_line_gap):
    """Utility for defining Line Segments."""
    lines = cv2.HoughLinesP(
        img, rho, theta, threshold, np.array([]),
        minLineLength=min_line_len, maxLineGap=max_line_gap)
    line_img = np.zeros((img.shape[0], img.shape[1], 3), dtype=np.uint8)
    draw_lines(line_img, lines)
    return line_img, lines

def separate_left_right_lines(lines):
    """Separate left and right lines depending on the slope."""
    left_lines = []
    right_lines = []
    if lines is not None:
        for line in lines:
            for x1, y1, x2, y2 in line:
                if x1 == x2:
                    continue  # Skip vertical lines to avoid division by zero
                slope = (y2 - y1) / (x2 - x1)
                if slope < 0:  # Negative slope = left lane
                    left_lines.append([x1, y1, x2, y2])
                else:  # Positive slope = right lane
                    right_lines.append([x1, y1, x2, y2])
    return left_lines, right_lines

def cal_avg(values):
    """Calculate average value."""
    if values is not None and len(values) > 0:
        return sum(values) / len(values)
    else:
        return 0

def extrapolate_lines(lines, upper_border, lower_border):
    """Extrapolate lines keeping in mind the lower and upper border intersections."""
    slopes = []
    consts = []
    if lines is not None and len(lines) != 0:
        for x1, y1, x2, y2 in lines:
            if x1 == x2:
                continue  # Avoid division by zero
            slope = (y1 - y2) / (x1 - x2)
            slopes.append(slope)
            c = y1 - slope * x1
            consts.append(c)
        avg_slope = cal_avg(slopes)
        avg_consts = cal_avg(consts)
        if avg_slope == 0:
            return None
        x_lane_lower_point = int((lower_border - avg_consts) / avg_slope)
        x_lane_upper_point = int((upper_border - avg_consts) / avg_slope)
        return [x_lane_lower_point, lower_border, x_lane_upper_point, upper_border]
    else:
        return None

def extrapolated_lane_image(img, lines, roi_upper_border, roi_lower_border):
    """Main function called to get the final lane lines."""
    lanes_img = np.zeros((img.shape[0], img.shape[1], 3), dtype=np.uint8)
    lines_left, lines_right = separate_left_right_lines(lines)
    lane_left = extrapolate_lines(lines_left, roi_upper_border, roi_lower_border)
    lane_right = extrapolate_lines(lines_right, roi_upper_border, roi_lower_border)
    if lane_left is not None and lane_right is not None:
        draw_con(lanes_img, [[lane_left], [lane_right]])
    return lanes_img

def draw_con(img, lines):
    """Fill in lane area."""
    points = []
    if lines is not None:
        for x1, y1, x2, y2 in lines[0]:
            points.append([x1, y1])
            points.append([x2, y2])
        for x1, y1, x2, y2 in lines[1]:
            points.append([x2, y2])
            points.append([x1, y1])
    if points:
        points = np.array([points], dtype='int32')
        cv2.fillPoly(img, [points], (0, 255, 0))

def process_image(image, low_threshold, high_threshold, kernel_size, rho, theta, hough_threshold, min_line_len, max_line_gap, roi_vertices):  
    # Convert to grayscale.
    gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
    
    # Intensity selection.
    gray_select = cv2.inRange(gray, 150, 255)
    
    # Region masking: Select vertices according to the input image.
    roi_vertices_np = np.array(roi_vertices, dtype=np.int32)
    gray_select_roi = region_of_interest(gray_select, roi_vertices_np)
    
    # Canny Edge Detection.
    img_canny = cv2.Canny(gray_select_roi, low_threshold, high_threshold)
    
    # Remove noise using Gaussian blur.
    if kernel_size % 2 == 0:
        kernel_size += 1  # kernel_size must be odd
    canny_blur = cv2.GaussianBlur(img_canny, (kernel_size, kernel_size), 0)
    
    # Hough transform parameters set according to the input image.
    hough, lines = hough_lines(canny_blur, rho, theta, hough_threshold, min_line_len, max_line_gap)
    
    # Extrapolate lanes.
    roi_upper_border = min([vertex[1] for vertex in roi_vertices])  # smallest y value
    roi_lower_border = max([vertex[1] for vertex in roi_vertices])  # largest y value
    lane_img = extrapolated_lane_image(image, lines, roi_upper_border, roi_lower_border)
    
    # Combined using weighted image.
    image_result = cv2.addWeighted(image, 1, lane_img, 0.4, 0.0)
    return image_result

def process_video(input_video_path, low_threshold, high_threshold, kernel_size, rho, theta, hough_threshold, min_line_len, max_line_gap, roi_vertices):
    # Initialize video capture
    video_cap = cv2.VideoCapture(input_video_path)
    if not video_cap.isOpened():
        raise Exception("Error opening video stream or file")
    
    # Retrieve video frame properties.
    frame_w   = int(video_cap.get(cv2.CAP_PROP_FRAME_WIDTH))
    frame_h   = int(video_cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
    frame_fps = video_cap.get(cv2.CAP_PROP_FPS)
    
    # Output video file
    temp_video = tempfile.NamedTemporaryFile(suffix='.mp4', delete=False)
    output_video_path = temp_video.name
    
    # Video writer
    fourcc = cv2.VideoWriter_fourcc(*'mp4v')
    vid_out = cv2.VideoWriter(output_video_path, fourcc, frame_fps, (frame_w,frame_h))
    
    # Process each frame
    while True:
        ret, frame = video_cap.read()
        if not ret:
            break
        
        result = process_image(frame, low_threshold, high_threshold, kernel_size, rho, theta, hough_threshold, min_line_len, max_line_gap, roi_vertices)
        vid_out.write(result)
    
    # Release resources
    video_cap.release()
    vid_out.release()
    
    return output_video_path

def gradio_process_video(input_video_path, low_threshold, high_threshold, kernel_size, rho, theta_degree, hough_threshold, min_line_len, max_line_gap,
                         x1, y1, x2, y2, x3, y3, x4, y4):
    # Define ROI vertices from user input
    roi_vertices = [[x1, y1], [x2, y2], [x3, y3], [x4, y4]]
    
    # Convert theta_degree to radians
    theta = np.deg2rad(theta_degree)
    
    # Process the video
    output_video_path = process_video(input_video_path, low_threshold, high_threshold, kernel_size, rho, theta, hough_threshold, min_line_len, max_line_gap, roi_vertices)
    
    return output_video_path

# Create the Gradio interface
iface = gr.Interface(
    fn=gradio_process_video,
    inputs=[
        gr.Video(label="Input Video"),
        gr.Slider(0, 255, step=1, value=50, label="Canny Low Threshold"),
        gr.Slider(0, 255, step=1, value=150, label="Canny High Threshold"),
        gr.Slider(1, 31, step=2, value=5, label="Gaussian Kernel Size"),
        gr.Slider(1, 10, step=1, value=1, label="Hough Transform Rho"),
        gr.Slider(0, 180, step=1, value=1, label="Hough Transform Theta (degrees)"),
        gr.Slider(1, 500, step=1, value=100, label="Hough Transform Threshold"),
        gr.Slider(1, 500, step=1, value=50, label="Hough Transform Min Line Length"),
        gr.Slider(1, 500, step=1, value=300, label="Hough Transform Max Line Gap"),
        gr.Number(value=100, label="ROI x1"),
        gr.Number(value=540, label="ROI y1"),
        gr.Number(value=900, label="ROI x2"),
        gr.Number(value=540, label="ROI y2"),
        gr.Number(value=525, label="ROI x3"),
        gr.Number(value=330, label="ROI y3"),
        gr.Number(value=440, label="ROI x4"),
        gr.Number(value=330, label="ROI y4"),
    ],
    outputs=gr.Video(label="Processed Video"),
    title="Lane Detection Video Processing",
    description="Upload a video and adjust parameters to process the video for lane detection.",
)

# Launch the interface
iface.launch()