I developed an algorithm detecting headlight cutoff line, can be seen in the following figure:
Let me ask right from the start what I would normally ask at the end;
How to make the algorithm robust to the input data? In the presence of the challenges in the following, but not limited to:
- changing light conditions
- non easy visibility of the headlight cut-off line
Here is the Minimum Reproducible Example:
import cv2
import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import curve_fit
def curve_fit_tanh(x_data, y_data):
def tanh_model(x, a, b, c, d):
"""Hyperbolic tangent: a * tanh[b * (x - c)] + d."""
return a * np.tanh(b * (x - c)) + d
# Provide initial guesses for parameters based on data range
initial_guesses = [-max(y_data) + min(y_data), 0.01, np.median(x_data), np.mean(y_data)]
# print(f'initial_guesses: a={initial_guesses[0]}, b={initial_guesses[1]}, c={initial_guesses[2]}, d={initial_guesses[3]}')
# Fit the model using the requested tanh function
params, _ = curve_fit(tanh_model, x_data, y_data, p0=initial_guesses, maxfev=10000)
# Extract optimized parameters
a_opt, b_opt, c_opt, d_opt = params
# Generate fitted values
y_fit = tanh_model(x_data, a_opt, b_opt, c_opt, d_opt)
return params,y_fit,initial_guesses
def clahe_yuv(frame):
img_yuv = cv2.cvtColor(frame, cv2.COLOR_BGR2YUV)
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(2,2))
img_yuv[:,:,0] = clahe.apply(img_yuv[:,:,0])
img = cv2.cvtColor(img_yuv, cv2.COLOR_YUV2BGR)
return img
def debug_show_image(window_name, image):
cv2.imshow(window_name, image)
if cv2.waitKey(0) & 0xFF == ord('q'):
cv2.destroyAllWindows()
exit()
else:
cv2.destroyWindow(window_name)
def hl_cutoff_filter(frame):
"""
Detects the headlight cutoff shape by scanning rows and tracking edges across columns.
"""
# Convert to YUV color space and apply CLAHE to enhance contrast
yuv_frame = clahe_yuv(frame)
# Apply thresholding to extract bright regions (headlight cutoff)
_, binary = cv2.threshold(yuv_frame, 250, 255, cv2.THRESH_BINARY)
debug_show_image('binary YUV', binary)
b1 = binary[:,:,0]
b2 = binary[:,:,1]
b3 = binary[:,:,2]
binary = cv2.bitwise_or(b1, cv2.bitwise_or(b2, b3))
debug_show_image('binary YUV Combined', binary)
# Detect horizontal edges using a horizontal kernel
horizontal_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (75, 1))
horizontal_edges = cv2.morphologyEx(binary, cv2.MORPH_OPEN, horizontal_kernel)
# Find first edge point in each column
_, width = horizontal_edges.shape
x_vals = np.arange(width)
y_vals = np.argmax(horizontal_edges > 0, axis=0)
# Filter valid points
valid_mask = np.any(horizontal_edges > 0, axis=0)
x_vals = x_vals[valid_mask]
y_vals = y_vals[valid_mask]
if len(x_vals) < 5:
return frame
try:
params, y_fit, _ = curve_fit_tanh(x_vals, y_vals)
fitdata = np.column_stack((x_vals, y_fit.astype(int)))
fit_success = True # Set flag to True if fitting is successful
except Exception as e:
print(f"[APP] HL CUT-OFF FITTING failed: {e}")
fit_success = False # Set flag to False if an exception occurs
# Only draw if fitting was successful
if fit_success:
cv2.polylines(frame, [fitdata], isClosed=False, color=(0, 0, 255), thickness=3) # Red curve
cv2.putText(frame, "HL Cut-off", (fitdata[0][0]-10, fitdata[0][1]-10),
cv2.FONT_HERSHEY_SIMPLEX, 0.75, (0, 0, 255), 2)
# find the middle point of the curve in y axis
middle_y = round(np.mean(fitdata[:, 1]), 4)
cv2.putText(frame, f"HL Center in Y Axis: {middle_y}th Px", (1080//2, int(middle_y*1.1)),
cv2.FONT_HERSHEY_SIMPLEX, 0.75, (0, 255, 0), 2)
return frame,middle_y
# Test with a sample image
frame = cv2.imread("./data/original2.png")
# cv2.rectangle(frame, (100, 250), (700, 280), (255, 255, 255), -1)
processed_frame,_ = hl_cutoff_filter(frame)
# plot with opencv
cv2.imshow("Headlight Cutoff Detection", processed_frame)
cv2.waitKey(0)
cv2.destroyAllWindows()
# plt.imshow(cv2.cvtColor(processed_frame, cv2.COLOR_BGR2RGB))
# plt.title("Headlight Cutoff Detection")
# plt.show()
it works well, but, it is not robust to the different test images due to non-optimal parameter selections. The parameters which makes the algorithm very sensitive to the input image/video are:
- Binary threshold limit: 250 here
- Initial guess of the tanh model fit
cv2.getStructuringElement(cv2.MORPH_RECT, (75, 1))
, size of the structured filter, 75