Line detection issue – OpenCV in Python

Tags: , , ,

I have written the following script with which I aim to detect lines in Gazebo (a simulation environment):

#!/usr/bin/env python
# rospy for the subscriber
import rospy
# ROS Image message
from sensor_msgs.msg import Image
# ROS Image message -> OpenCV2 image converter
from cv_bridge import CvBridge, CvBridgeError
# OpenCV2 for saving an image
import cv2
import matplotlib.pyplot as plt
import numpy as np

def gradient(img):
    # grayscale the image
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    # gaussian blur of image with a 5x5 kernel
    gauss = cv2.GaussianBlur(gray,(5,5),0)
    # Return the canny of the image
    return cv2.Canny(gauss,20,30)

def region_of_interest(img):
    # Height of image (number of rows)
    height = img.shape[0]
    # Width of the image (number of columns)
    width = img.shape[1]
    # Create an array of polygons to use for the masking of the canny image
    polygons = np.array([
    [(200,height), (200,500), (600,500), (600,height)]
    # Create the mask image's background (black color)
    mask_bg = np.zeros_like(img)
    # Create the mask image (image with black background an white region of interest)
    mask = cv2.fillPoly(mask_bg, polygons, 255)
    # Isolate the area of interest using the bitwise operator of the mask and canny image
    masked_image = cv2.bitwise_and(img,cv2.fillPoly(mask_bg, polygons, 255))
    # Return the updated image
    return masked_image

def make_coordinates(img, line_parameters):
    # Extract the average slope and intercept of the line
    slope, intercept = line_parameters
    # Coordinate y(1) of the calculated line
    y1 = img.shape[0]
    # Coordinate y(2) of the calculated line
    y2 = int(y1*0.5)
    # Coordinate x(1) of the calculated line
    x1 = int((y1-intercept)/slope)
    # Coordinate x(2) of the calculated line
    x2 = int((y2-intercept)/slope)
    # Return the coordinates of the average line
    return np.array([x1,y1,x2,y2])

def average_slope_intercep(img,lines):
    # Create an empty list containing the coordinates of the detected line
    line_fit = []
    # Loop through all the detected lines
    for line in lines:
        # Store the coordinates of the detected lines into an 1D array of 4 elements
        x1,y1,x2,y2 = line.reshape(4)
        # Create a line y = mx+b based on the coordinates
        parameters = np.polyfit((x1,x2),(y1,y2),1)
        # Extract the slope m
        slope = parameters[0]
        # Extract the intercept b
        intercept = parameters[1]
        # Add elements on the list
        # Check slope of line
        # if slope < 0:
        #     continue
        # else:
        #     continue
    # Calculate the average of the line fit parameters list
    line_fit_average = np.average(line_fit,axis=0)
    # Extract the coordinates of the calculated line
    main_line = make_coordinates(img,line_fit_average)

    return np.array([main_line])

def display_lines(img,lines):
    # Create a mask image that will have the drawn lines
    line_image = np.zeros_like(img)
    # If no lines were detected
    if lines is not None:
        # Loop through all the lines
        for line in lines:
            # Store the coordinates of the first and last point of the lines into 1D arrays
            x1, y1, x2, y2 = line.reshape(4)
            # Draw the lines on the image with blue color and thicknes of 10
    # Return the mask image with the drawn lines
    return line_image

def image_callback(msg):
    # print("Received an image!")
    # Instantiate CvBridge
    bridge = CvBridge()
        # Convert your ROS Image message to OpenCV2
        frame = bridge.imgmsg_to_cv2(msg, "bgr8")
    except CvBridgeError, e:
        # Copy of the original frame
        frame_copy = np.copy(frame)
        # Canny of image
        canny_frame = gradient(frame_copy)
        # Apply mask in region of interest
        cropped_image = region_of_interest(canny_frame)
        # Apply Hough Transform on the region of interest
        lines = cv2.HoughLinesP(cropped_image,1,np.pi/180,30,np.array([]),minLineLength=10,maxLineGap=2)
        # Calculate the average slope of the detected lines
        averaged_lines = average_slope_intercep(frame_copy,lines)
        # Create a mask image with the drawn lines
        line_image = display_lines(frame_copy,averaged_lines)
        # Plot lines on the camera feed frame
        combo_image = cv2.addWeighted(frame_copy,0.8,line_image,1,1)
        #Show manipulated image feed
        cv2.imshow("Result feed", frame_copy)
        # plt.imshow(canny_frame)

def main():
    # Define your image topic
    image_topic = "rover/camera1/image_raw"
    # Set up your subscriber and define its callback
    rospy.Subscriber(image_topic, Image, image_callback)
    # Spin until ctrl + c

if __name__ == '__main__':

The code is integrated in ROS, so please focus your attention at the image_callback function. My issue is that the line that I want to detect is quite noisy and I cannot figure out how to detect it correctly.

To be more specific, from the following frame,

Original Frame

enter image description here

I get this image after gaussian blur and the canny algorithm,

Canny Frame

enter image description here

How could I filter the “noise” I see in the canny frame? I played a lot with the canny and gausian blur parameters but all that I have achieved is removing gradients instead of actually making it less “noisy”.


This method might help you to remove noise from the frame.

import cv2
import numpy as np
from skimage.morphology import skeletonize

def get_skeleton_iamge(threshold_image):
    skeleton = skeletonize(threshold_image / 255)
    skeleton = skeleton.astype(np.uint8)
    skeleton *= 255
    return skeleton

image = cv2.imread("road.png", 0)
image = cv2.resize(image, (300, 300))

bilateral = cv2.bilateralFilter(image, 15, 100, 100)
cv2.imshow("bilateral_image", bilateral)

canny_image = cv2.Canny(bilateral, 20, 30)
cv2.imshow("canny_image", canny_image)

kernel = np.ones((10, 10))
dilate_image = cv2.dilate(canny_image, kernel, iterations=1)
erode_image = cv2.erode(dilate_image, kernel, iterations=1)
cv2.imshow("erode_image", erode_image)

skeleton_iamge = get_skeleton_iamge(erode_image)
cv2.imshow("skeleton_iamge", skeleton_iamge)


enter image description here

Source: stackoverflow