# Creation Date: 01.03.2022 # Author: Kenan Gömek # This program is for the development of the lane detection algorithm on a windows machine # It uses images, which were captured previously import cv2 as cv import numpy as np import os # input folder_path=r"U:\bwsyncshare\images_input" # Parameters pixels_per_mm = 71/24.25 #[px/mm] for 120 mm camera height for resolution: 416x320 # pixels_per_mm = 107/24.25 #[px/mm] for 120 mm camera height for resolution: 640x480 cut_off_brightness_grayscale = 45 # 15 pixels with a value less than this will bet set to zero (black) # Parameters for HoughCircles dp = 1 # Inverse ratio of the accumulator resolution to the image resolution. For example, if dp=1 , the accumulator has the same resolution as the input image. If dp=2 , the accumulator has half as big width and height. minDist_mm = 10 # [mm] minimal distance between two circles minDist_px = int(minDist_mm*pixels_per_mm) # in [px] Minimum distance in px between the centers of the detected circles. If the parameter is too small, multiple neighbor circles may be falsely detected in addition to a true one. If it is too large, some circles may be missed. minRadius_mm = 5 # [mm] minimum radius of a circle minRadius_px = int(minRadius_mm*pixels_per_mm) # [px] Minimum circle radius. maxRadius_mm = 7 # [mm] maximum radius of a circle maxRadius_px = int(maxRadius_mm*pixels_per_mm) # [px] Maximum circle radius. If <= 0, uses the maximum image dimension. If < 0, returns centers without finding the radius. param1 = 100 # 30 First method-specific parameter. In case of HOUGH_GRADIENT , it is the higher threshold of the two passed to the Canny edge detector (the lower one is twice smaller). # If circles/LEDs should be detected at low shutter speeds, than lower this value # Upper threshold for the internal Canny edge detector. # "Gradient value between dark and white" param2 = 5 # 12 Second method-specific parameter. In case of HOUGH_GRADIENT , it is the accumulator threshold for the circle centers at the detection stage. The smaller it is, the more false circles may be detected. Circles, corresponding to the larger accumulator values, will be returned first. # By increasing this threshold value, we can ensure that only the best circles, corresponding to larger accumulator values, are returned. # Define color numbers to identify the color channels in the matrix with all detected LEDs. No string, because numpy array should stay uint16 color_number_red = 1 color_number_green = 2 color_number_blue = 3 def preprocess_grayscale_image_1(image_gray_copy, color_channel): # Set all pixels with a value less than "cut_off_brightness_grayscale" to zero image_gray_cutoff = image_gray_copy.copy() image_gray_cutoff[image_gray_cutoff < cut_off_brightness_grayscale]=0 cv.imshow(f"{color_channel} channel cutoff", image_gray_cutoff[150:,:]) preprocessed_image = image_gray_cutoff return preprocessed_image def preprocess_grayscale_image_2(image_gray_copy, color_channel): # Set all pixels with a value less than "cut_off_brightness_grayscale" to zero # This prevents the false detection of color-channels which are not intended to be detected image_gray_cutoff = image_gray_copy.copy() image_gray_cutoff[image_gray_cutoff < cut_off_brightness_grayscale]=0 # cv.imshow(f"{color_channel} channel grayscale cutoff", image_gray_cutoff[150:,:]) # For better accuracy, binary images are used before finding contours --> apply treshold image_gray_binary = image_gray_cutoff perc = np.percentile(image_gray_binary, 95) # calculate 95 % percentile ret, tresh=cv.threshold(image_gray_binary, perc, 255, cv.THRESH_BINARY) # set pixels above specified percentile to 255, the resto to 0 preprocessed_image = tresh return preprocessed_image def preprocess_grayscale_image_3(image_gray_copy, color_channel): # Set all pixels with a value less than "cut_off_brightness_grayscale" to zero # This prevents the false detection of color-channels which are not intended to be detected image_gray_cutoff = image_gray_copy.copy() image_gray_cutoff[image_gray_cutoff < cut_off_brightness_grayscale]=0 perc = np.percentile(image_gray_cutoff, 95) # calculate 95 % percentile image_gray_cutoff[image_gray_cutoff < perc]=0 cv.imshow(f"{color_channel} channel grayscale cutoff", image_gray_cutoff[150:,:]) # For better accuracy, binary images are used before finding contours --> apply treshold image_gray_binary = image_gray_cutoff perc = np.percentile(image_gray_binary, 95) # calculate 95 % percentile ret, tresh=cv.threshold(image_gray_binary, perc, 255, cv.THRESH_BINARY) # set pixels above specified percentile to 255, the resto to 0 preprocessed_image = image_gray_cutoff return preprocessed_image def preprocess_grayscale_image_4_cannyedge(image_gray_copy, color_channel): # Set all pixels with a value less than "cut_off_brightness_grayscale" to zero # This prevents the false detection of color-channels which are not intended to be detected t_lower = 1 t_upper = 5 edge = cv.Canny(image_gray_copy, t_lower, t_upper) cv.imshow(f"{color_channel} channel edge", edge[150:,:]) preprocessed_image = edge return preprocessed_image def detect_LEDs_in_color_channel(image_gray, color_channel, image_gray_copy, image_bgr_copy): # preprocessed_gray_image = preprocess_grayscale_image_1(image_gray_copy=image_gray_copy, color_channel=color_channel) preprocessed_gray_image = preprocess_grayscale_image_2(image_gray_copy=image_gray_copy, color_channel=color_channel) # preprocessed_gray_image = preprocess_grayscale_image_3(image_gray_copy=image_gray_copy, color_channel=color_channel) # preprocessed_gray_image = preprocess_grayscale_image_4_cannyedge(image_gray_copy=image_gray_copy, color_channel=color_channel) # preprocessed_gray_image = image_gray_copy detected_LEDs = cv.HoughCircles(preprocessed_gray_image, cv.HOUGH_GRADIENT, dp=dp, minDist = minDist_px , param1=param1, param2=param2, minRadius=minRadius_px, maxRadius=maxRadius_px) # specify color number, for adding to matrix of detected LEDs if color_channel == "blue": color_number = color_number_blue elif color_channel == "green": color_number = color_number_green elif color_channel == "red": color_number = color_number_red # check if at least one circle was found in the image if detected_LEDs is not None: detected_LEDs = np.uint16(np.round(detected_LEDs)) # convert the (x, y) coordinates and radius of the circles to integers detected_LEDs = detected_LEDs[0,:] detected_LEDs=np.hstack((detected_LEDs, np.full((detected_LEDs.shape[0],1), color_number, dtype=np.uint16))) # matrix with columns: x, y, r number_of_detected_LEDs = detected_LEDs.shape[0] print(f"detected {color_channel} LEDs: {number_of_detected_LEDs}") # paramters for drawing line_thickness = 1 circle_color = (0,255,0) vertex_offset = 2 rectangle_color = (0,128,255) # R G B for (x, y, r, cn) in detected_LEDs: print(f"x:{x} px, y:{y} px, r:{r} px, r:{round(r*1/(pixels_per_mm),2)} mm, D: {round(2*r*1/(pixels_per_mm),2)} mm, color: {color_channel}") cv.circle(image_bgr_copy, (x, y), r, circle_color, thickness=line_thickness) # draw detected circumference of the cirle cv.circle(image_gray_copy, (x, y), r, circle_color, thickness=line_thickness) # draw detected circumference of the cirle cv.circle(preprocessed_gray_image, (x, y), r, circle_color, thickness=1) # draw detected circumference of the cirle cv.rectangle(img=image_bgr_copy, pt1=(x-vertex_offset, y-vertex_offset), pt2=(x+vertex_offset, y+vertex_offset), \ color=rectangle_color, thickness=cv.FILLED) cv.rectangle(img=image_gray_copy, pt1=(x-vertex_offset, y-vertex_offset), pt2=(x+vertex_offset, y+vertex_offset), \ color=rectangle_color, thickness=cv.FILLED) cv.rectangle(img=preprocessed_gray_image, pt1=(x-vertex_offset, y-vertex_offset), pt2=(x+vertex_offset, y+vertex_offset), \ color=rectangle_color, thickness=cv.FILLED) cv.imshow(f"{color_channel} channel binary", preprocessed_gray_image[150:,:]) return detected_LEDs else: print(f"No {color_channel} LEDs were found in the image") return None def detect_blue_LEDs(image_colorchannel_gray, image_colorchannel_gray_copy, image_bgr_copy): color_channel = "blue" detected_LEDs_blue = detect_LEDs_in_color_channel(image_gray=image_colorchannel_gray, color_channel=color_channel, \ image_gray_copy=image_colorchannel_gray_copy, image_bgr_copy=image_bgr_copy) if detected_LEDs_blue is not None: return detected_LEDs_blue else: return None def detect_green_LEDs(image_colorchannel_gray, image_colorchannel_gray_copy, image_bgr_copy): color_channel = "green" detected_LEDs_green = detect_LEDs_in_color_channel(image_gray=image_colorchannel_gray, color_channel=color_channel, \ image_gray_copy=image_colorchannel_gray_copy, image_bgr_copy=image_bgr_copy) if detected_LEDs_green is not None: return detected_LEDs_green else: return None def detect_red_LEDs(image_colorchannel_gray, image_colorchannel_gray_copy, image_bgr_copy): color_channel = "red" detected_LEDs_red = detect_LEDs_in_color_channel(image_gray=image_colorchannel_gray, color_channel=color_channel, \ image_gray_copy=image_colorchannel_gray_copy, image_bgr_copy=image_bgr_copy) if detected_LEDs_red is not None: return detected_LEDs_red else: return None def detect_LEDs(image_b, image_g, image_r, image_b_copy, image_g_copy, image_r_copy, image_bgr_copy): detected_LEDs_blue = detect_blue_LEDs(image_colorchannel_gray=image_b, image_colorchannel_gray_copy=image_b_copy, image_bgr_copy=image_bgr_copy) detected_LEDs_green = detect_green_LEDs(image_colorchannel_gray=image_g, image_colorchannel_gray_copy=image_g_copy, image_bgr_copy=image_bgr_copy) detected_LEDs_red = detect_red_LEDs(image_colorchannel_gray=image_r, image_colorchannel_gray_copy=image_r_copy, image_bgr_copy=image_bgr_copy) # check the cases: # case 1: r if detected_LEDs_blue is None and detected_LEDs_green is None and detected_LEDs_red is not None: detected_LEDs = detected_LEDs_red # case 2: g elif detected_LEDs_blue is None and detected_LEDs_green is not None and detected_LEDs_red is None: detected_LEDs = detected_LEDs_green # case 3: b elif detected_LEDs_blue is not None and detected_LEDs_green is None and detected_LEDs_red is None: detected_LEDs = detected_LEDs_blue # case 4: y = r+g elif detected_LEDs_blue is None and detected_LEDs_green is not None and detected_LEDs_red is not None: detected_LEDs_all = np.vstack((detected_LEDs_red, detected_LEDs_green)) detected_LEDs = detected_LEDs_all # case 5: m = r+b elif detected_LEDs_blue is not None and detected_LEDs_green is None and detected_LEDs_red is not None: detected_LEDs_all = np.vstack((detected_LEDs_red, detected_LEDs_blue)) detected_LEDs = detected_LEDs_all # case 6: c = g+b elif detected_LEDs_blue is not None and detected_LEDs_green is not None and detected_LEDs_red is None: detected_LEDs_all = np.vstack((detected_LEDs_green, detected_LEDs_blue)) detected_LEDs = detected_LEDs_all # case 7: w = r+g+b elif detected_LEDs_blue is not None and detected_LEDs_green is not None and detected_LEDs_red is not None: detected_LEDs_all = np.vstack((detected_LEDs_red, detected_LEDs_green, detected_LEDs_blue)) detected_LEDs = detected_LEDs_all return detected_LEDs def lane_detection(image_b, image_g, image_r, image_b_copy, image_g_copy, image_r_copy, image_bgr_copy): # Detect LEDs detected_LEDs = detect_LEDs(image_b, image_g, image_r, image_b_copy, image_g_copy, image_r_copy, image_bgr_copy) return detected_LEDs def main(): filenames_of_images = [f for f in os.listdir(folder_path) if f.endswith('.png')] for i, filename_of_image in enumerate(filenames_of_images): print(f"image:{filename_of_image}") full_path_of_image = os.path.join(folder_path, filename_of_image) image_bgr = cv.imread(full_path_of_image, cv.IMREAD_COLOR) # load original image image_b,image_g,image_r = cv.split(image_bgr) # Split colour channels and get grayscale images # create copy of images, to draw on them image_bgr_copy = image_bgr.copy() image_b_copy = image_b.copy() image_g_copy = image_g.copy() image_r_copy = image_r.copy() detected_LEDs = lane_detection(image_b, image_g, image_r, image_b_copy, image_g_copy, image_r_copy, image_bgr_copy) print(detected_LEDs) print(f"_____________________________________") # show images: only region of interest cv.imshow("Original image", image_bgr[150:,:]) cv.imshow("All detected LEDs", image_bgr_copy[150:,:]) cv.imshow("Blue channel", image_b[150:,:]) cv.imshow("Blue channel detected", image_b_copy[150:,:]) cv.imshow("Green channel", image_g[150:,:]) cv.imshow("Green channel detected", image_g_copy[150:,:]) cv.imshow("Red channel", image_r[150:,:]) cv.imshow("Red channel detected", image_r_copy[150:,:]) cv.waitKey(0) # display and wait if a key is pressed and then continue cv.destroyAllWindows() if __name__ == "__main__": main()