254 lines
14 KiB
Python
254 lines
14 KiB
Python
# 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() |