2017-12-06 18:09:41 AdamShan 阅读数 19391
  • Mask R-CNN图像实例分割实战:训练自己的数据集

    Mask R-CNN是一种基于深度学习的图像实例分割方法,可对物体进行目标检测和像素级分割。 本课程将手把手地教大家使用VIA图像标注工具制作自己的数据集,并使用Mask R-CNN训练自己的数据集,从而能开展自己的图像分割应用。 本课程有三个项目案例实践: (1) balloon实例分割 :对图像中的气球做检测和分割 (2) pothole(单类物体)实例分割:对汽车行驶场景中的路坑进行检测和分割 (3) roadscene( 多类物体)实例分割:对汽车行驶场景中的路坑、车、车道线等进行检测和分割 本课程使用Keras版本的Mask R-CNN,在Ubuntu系统上做项目演示。 本课程提供项目的数据集和python程序文件。 下面是使用Mask R-CNN对roadscene进行图像实例分割的测试结果: 下图是使用Mask R-CNN对pothole进行单类物体图像实例分割的测试结果: 下图是使用Mask R-CNN对roadscene进行多类物体图像实例分割的测试结果:

    786 人正在学习 去看看 白勇

无人驾驶汽车系统入门(七)——基于传统计算机视觉的车道线检测(2)

原创不易,转载请注明来源:http://blog.csdn.net/adamshan/article/details/78733302

接上文,在获得ROI(透视变换)以后,我们对得到的“鸟瞰图”应用色彩阈值化和梯度阈值化,以得到鸟瞰图中“可能为”车道线的像素然后再使用滑动窗口来确定车道线的多项式系数,下面我们来具体讨论这两个步骤。

边缘检测

车道线的一个主要的特征就是它可以看作图像中的边缘(Edge),因此我们可以使用一定的 边缘检测(Edge detection) 算法将之从原图像中提取出来。边缘检测的结果就是标识出了数字图像中亮度变化明显的点。有许多用于边缘检测的方法,他们大致可分为两类:基于搜索和基于零交叉。

  • 基于搜索的边缘检测方法首先计算边缘强度,通常用一阶导数表示,例如梯度模; 然后,用计算估计边缘的局部方向,通常采用梯度的方向,并利用此方向找到局部梯度模的最大值。
  • 基于零交叉的方法找到由图像得到的二阶导数的零交叉点来定位边缘.通常用拉普拉斯算子或非线性微分方程的零交叉点。

本文介绍一种基于亮度的一阶导数的方法,或者说,基于原始图像的亮度的 梯度 的算法。我们使用 索贝尔算子(Sobel operator) 来计算图像中的边缘,该算子包含两组 3×3 的矩阵,分别为 横向纵向 ,将之与图像作平面卷积,即可分别得出横向及纵向的亮度差分近似值。如果以 A 代表原始图像, GxGy 分别代表经横向及纵向边缘检测的图像,其公式如下:

Gx=121000+1+2+1A

Gy=10+120+210+1A

在OpenCV中,可以使用 cv2.Sobel() 方法来指定,我们来看一下横向和纵向的索贝尔算子检测的边缘的区别,这里我们使用拉普拉斯算子作为参照:

import cv2
import numpy as np
from matplotlib import pyplot as plt

img = cv2.imread('test_images/shudu.jpeg', 0)

laplacian = cv2.Laplacian(img, cv2.CV_64F)
sobelx = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=3)
sobely = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=3)

plt.subplot(2, 2, 1), plt.imshow(img, cmap='gray')
plt.title('Original'), plt.xticks([]), plt.yticks([])
plt.subplot(2, 2, 2), plt.imshow(laplacian, cmap='gray')
plt.title('Laplacian'), plt.xticks([]), plt.yticks([])
plt.subplot(2, 2, 3), plt.imshow(sobelx, cmap='gray')
plt.title('Sobel X'), plt.xticks([]), plt.yticks([])
plt.subplot(2, 2, 4), plt.imshow(sobely, cmap='gray')
plt.title('Sobel Y'), plt.xticks([]), plt.yticks([])

plt.show()

检测的结果:

这里写图片描述

不难看出, x 方向的索贝尔算子倾向于检测垂直方向的边缘,而 y 方向的索贝尔算子则倾向于检测水平的边缘,而在车道线检测问题中,我们关注的对象(车道线)往往是垂直方向的线,同时我们希望过滤到一些水平方向的线,所以在本实例中,我们采用 x 方向的索贝尔算子。

通过使用索贝尔算子做两个方向的卷积,我们可以得到图像的每一个像素的横向及纵向 梯度近似值 ,更近一步,我们可以结合这两个近似值计算梯度的大小:

G=G2x+G2y

然后可用以下公式计算梯度方向:

Θ=arctan(GyGx)

我们分别使用OpenCV实现以上的内容:

def abs_sobel_thresh(img, sobel_kernel=3, orient='x', thresh=(0, 255)):
    gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
    if orient == 'x':
        sobel = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=sobel_kernel)
    else:
        sobel = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=sobel_kernel)
    abs_sobel = np.absolute(sobel)
    scaled_sobel = np.uint8(255 * abs_sobel / np.max(abs_sobel))
    sxbinary = np.zeros_like(scaled_sobel)
    sxbinary[(scaled_sobel >= thresh[0]) & (scaled_sobel <= thresh[1])] = 1
    return sxbinary


def mag_thresh(img, sobel_kernel=3, mag_thresh=(0, 255)):
    # Convert to grayscale
    gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
    # Take both Sobel x and y gradients
    sobelx = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=sobel_kernel)
    sobely = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=sobel_kernel)
    # Calculate the gradient magnitude
    gradmag = np.sqrt(sobelx**2 + sobely**2)
    # Rescale to 8 bit
    scale_factor = np.max(gradmag)/255
    gradmag = (gradmag/scale_factor).astype(np.uint8)
    # Create a binary image of ones where threshold is met, zeros otherwise
    binary_output = np.zeros_like(gradmag)
    binary_output[(gradmag >= mag_thresh[0]) & (gradmag <= mag_thresh[1])] = 1

    # Return the binary image
    return binary_output


def dir_threshold(img, sobel_kernel=3, thresh=(0, np.pi/2)):
    # Grayscale
    gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
    # Calculate the x and y gradients
    sobelx = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=sobel_kernel)
    sobely = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=sobel_kernel)
    # Take the absolute value of the gradient direction,
    # apply a threshold, and create a binary image result
    absgraddir = np.arctan2(np.absolute(sobely), np.absolute(sobelx))
    binary_output =  np.zeros_like(absgraddir)
    binary_output[(absgraddir >= thresh[0]) & (absgraddir <= thresh[1])] = 1

    # Return the binary image
    return binary_output

分别对上一篇文章中的鸟瞰图进行边缘检测:

ksize = 9 # Choose a larger odd number to smooth gradient measurements

# Apply each of the thresholding functions
gradx = abs_sobel_thresh(wrap_img, orient='x', sobel_kernel=3, thresh=(20, 255))

mag_binary = mag_thresh(wrap_img, sobel_kernel=3, mag_thresh=(30, 100))
dir_binary = dir_threshold(wrap_img, sobel_kernel=15, thresh=(0.7, 1.3))

# Plot the result
f, axs = plt.subplots(2, 2, figsize=(16, 9))
f.tight_layout()
axs[0, 0].imshow(wrap_img)
axs[0, 0].set_title('Original Image', fontsize=18)
axs[0, 1].imshow(gradx, cmap='gray')
axs[0, 1].set_title('Sobel_x_filter', fontsize=18)
axs[1, 0].imshow(dir_binary, cmap='gray')
axs[1, 0].set_title('Dir_threshold', fontsize=18)
axs[1, 1].imshow(mag_binary, cmap='gray')
axs[1, 1].set_title('Mag_threshold', fontsize=18)
plt.subplots_adjust(left=0., right=1, top=0.9, bottom=0.)
plt.show()

这里写图片描述

不难看出,x方向的索贝尔算子取得了最好的效果,同时,梯度方向也为车道线像素选取提供了一定的参考。

色彩阈值化

检测车道线的另一个依据就是车道线的颜色特征,一般来说,车道线只有两种颜色:白色和黄色,所以我们可以在 RGB 色彩空间(Color Space) 对这两种颜色进行过滤从而提取出车道线的像素。

色彩空间:使用一组值(通常使用三个、四个值或者颜色成分)表示颜色方法的抽象数学模型。有利用原色相混的比例表示的色彩空间,如 RGB (Red, Green, Blue) 颜色空间; 也有利用不同的概念表示的色彩空间,如 HSV (色相 hue, 饱和度 saturation, 明度 value) 以及 HSL (色相 hue,饱和度 saturation,亮度 lightness/luminance)

在OpenCV中,RGB三通道的图像的读取 cv2.imread() 的结果是以 BGR 顺序排列的,而在使用matplotlib的 plt.imread() 时, 读取的通道排列顺序则为 RGB 。因此此处应当注意区别。

RGB阈值化处理

我们在RGB色彩空间对黄色和白色进行过滤,得到参考的像素:

def r_select(img, thresh=(200, 255)):
    R = img[:,:,0]
    binary = np.zeros_like(R)
    binary[(R > thresh[0]) & (R <= thresh[1])] = 1
    return binary

def color_mask(hsv,low,high):
    # Return mask from HSV
    mask = cv2.inRange(hsv, low, high)
    return mask

def apply_color_mask(hsv,img,low,high):
    # Apply color mask to image
    mask = cv2.inRange(hsv, low, high)
    res = cv2.bitwise_and(img,img, mask= mask)
    return res

def apply_yellow_white_mask(img):
    image_HSV = cv2.cvtColor(img,cv2.COLOR_RGB2HSV)
    yellow_hsv_low  = np.array([ 0,  100,  100])
    yellow_hsv_high = np.array([ 80, 255, 255])
    white_hsv_low  = np.array([ 0,   0,   160])
    white_hsv_high = np.array([ 255,  80, 255])
    mask_yellow = color_mask(image_HSV,yellow_hsv_low,yellow_hsv_high)
    mask_white = color_mask(image_HSV,white_hsv_low,white_hsv_high)
    mask_YW_image = cv2.bitwise_or(mask_yellow,mask_white)
    return mask_YW_image
r_binary = r_select(wrap_img, thresh=(220, 255))
yw_binary = apply_yellow_white_mask(wrap_img)
# Plot the result
f, axs = plt.subplots(1, 2, figsize=(16, 9))
f.tight_layout()
axs[0].imshow(r_binary, cmap='gray')
axs[0].set_title('R filter', fontsize=18)
axs[1].imshow(yw_binary, cmap='gray')
axs[1].set_title('Yellow white filter', fontsize=18)
plt.subplots_adjust(left=0., right=1, top=0.9, bottom=0.)
plt.show()

效果:

这里写图片描述

上面的代码中,R filter是在Red通道上设置合适的阈值,就能够对黄色和白色的导线进行过滤,而Yellow white filter则直接对三个通道设置一定的阈值,通过组合(and操作)来获得最优的过滤效果,RGB颜色阈值化虽然能够在正常光线条件下很好的过滤出道线像素,但是在复杂环境光的情况下性能并不稳定。下面我们进一步探索颜色阈值化,看看在 HLS 色彩空间的阈值化效果。

HLS 阈值化处理

下图是 HLS 色彩空间和 HSV 色彩空间的可视化比较。HLS和HSV都是一种将RGB色彩模型中的点在圆柱坐标系中的表示法。这两种表示法试图做到比RGB基于笛卡尔坐标系的几何结构更加直观。HLS即色相、亮度、饱和度。在本文中,我们重点探讨应用HLS的色彩阈值化于车道线检测。在OpenCV中,我可以直接使用 cv2.cvtColor() 方法将图像从RGB色彩空间转换到HLS色彩空间。

这里写图片描述

我们对HLS色彩空间的三个通道分别对车道线进行阈值化处理:

def hls_select(img, channel='S', thresh=(90, 255)):
    # 1) Convert to HLS color space
    # 2) Apply a threshold to the S channel
    # 3) Return a binary image of threshold result
    hls = cv2.cvtColor(img, cv2.COLOR_RGB2HLS)
    if channel == 'S':
        X = hls[:, :, 2]
    elif channel == 'H':
        X = hls[:, :, 0]
    elif channel == 'L':
        X = hls[:, :, 1]
    else:
        print('illegal channel !!!')
        return
    binary_output = np.zeros_like(X)
    binary_output[(X > thresh[0]) & (X <= thresh[1])] = 1
    return binary_output

对鸟瞰图测试阈值:

l_binary = hls_select(wrap_img, channel='L', thresh=(100, 200))
s_binary = hls_select(wrap_img, channel='S', thresh=(100, 255))
h_binary = hls_select(wrap_img, channel='H', thresh=(100, 255))
f, axs = plt.subplots(2, 2, figsize=(16, 9))
f.tight_layout()
axs[0, 0].imshow(wrap_img)
axs[0, 0].set_title('Original Image', fontsize=18)
axs[0, 1].imshow(h_binary, cmap='gray')
axs[0, 1].set_title('H channal filter', fontsize=18)
axs[1, 0].imshow(s_binary, cmap='gray')
axs[1, 0].set_title('S channal filter', fontsize=18)
axs[1, 1].imshow(l_binary, cmap='gray')
axs[1, 1].set_title('L channal filter', fontsize=18)
plt.subplots_adjust(left=0., right=1, top=0.9, bottom=0.)
plt.show()

结果如下:

这里写图片描述

粗略看来,通过组合H和S的阈值化可以获得良好的检测效果。

组合梯度和色彩过滤车道线像素

最理想的自然是通过组合以上的方法来获得对车道线最理想的提取,这种组合方法有很多,没有一个固定的模式,通过组合梯度和色彩过滤器,可以获得一个相对稳定的车道线提取方法,最理想的状态就是我们的组合过滤器能够尽可能少受到环境光,道路背景色以及其他车辆的影响,再次我们给出一个参考的组合方法:

def combine_filters(img):
    gradx = abs_sobel_thresh(img, orient='x', sobel_kernel=3, thresh=(20, 255))
    l_binary = hls_select(img, channel='L', thresh=(100, 200))
    s_binary = hls_select(img, channel='S', thresh=(100, 255))
    yw_binary = apply_yellow_white_mask(wrap_img)
    yw_binary[(yw_binary !=0)] = 1
    combined_lsx = np.zeros_like(gradx)
    combined_lsx[((l_binary == 1) & (s_binary == 1) | (gradx == 1) | (yw_binary == 1))] = 1
    return combined_lsx

binary = combine_filters(wrap_img)
f, axs = plt.subplots(1, 2, figsize=(16, 9))
f.tight_layout()
axs[0].imshow(wrap_img)
axs[0].set_title('Original', fontsize=18)
axs[1].imshow(binary, cmap='gray')
axs[1].set_title('combine filters', fontsize=18)
plt.subplots_adjust(left=0., right=1, top=0.9, bottom=0.)
plt.show()

这里写图片描述

在提取完车道线像素以后,我们用两条曲线来拟合这些像素,在做拟合这一步操作之前,我们首先得确定哪些像素是车道线的,哪些不是,这里我们使用滑动窗口的方法。

滑动窗口与多项式拟合

滑动窗口的代码如下:

def find_line_fit(img, nwindows=9, margin=100, minpix=50):
    histogram = np.sum(img[img.shape[0]//2:,:], axis=0)
    # Create an output image to draw on and  visualize the result
    out_img = np.dstack((img, img, img)) * 255
    # Find the peak of the left and right halves of the histogram
    # These will be the starting point for the left and right lines
    midpoint = np.int(histogram.shape[0]/2)
    leftx_base = np.argmax(histogram[:midpoint])
    rightx_base = np.argmax(histogram[midpoint:]) + midpoint

    # Set height of windows
    window_height = np.int(img.shape[0]/nwindows)
    # Identify the x and y positions of all nonzero pixels in the image
    nonzero = img.nonzero()
    nonzeroy = np.array(nonzero[0])
    nonzerox = np.array(nonzero[1])
    # Current positions to be updated for each window
    leftx_current = leftx_base
    rightx_current = rightx_base
    # Create empty lists to receive left and right lane pixel indices
    left_lane_inds = []
    right_lane_inds = []

    # Step through the windows one by one
    for window in range(nwindows):
        # Identify window boundaries in x and y (and right and left)
        win_y_low = img.shape[0] - (window+1)*window_height
        win_y_high = img.shape[0] - window*window_height
        win_xleft_low = leftx_current - margin
        win_xleft_high = leftx_current + margin
        win_xright_low = rightx_current - margin
        win_xright_high = rightx_current + margin
        # Draw the windows on the visualization image
        cv2.rectangle(out_img,(win_xleft_low,win_y_low),(win_xleft_high,win_y_high),
        (0,255,0), 2)
        cv2.rectangle(out_img,(win_xright_low,win_y_low),(win_xright_high,win_y_high),
        (0,255,0), 2)
        # Identify the nonzero pixels in x and y within the window
        good_left_inds = ((nonzeroy >= win_y_low) & (nonzeroy < win_y_high) &
        (nonzerox >= win_xleft_low) &  (nonzerox < win_xleft_high)).nonzero()[0]
        good_right_inds = ((nonzeroy >= win_y_low) & (nonzeroy < win_y_high) &
        (nonzerox >= win_xright_low) &  (nonzerox < win_xright_high)).nonzero()[0]
        # Append these indices to the lists
        left_lane_inds.append(good_left_inds)
        right_lane_inds.append(good_right_inds)
        # If you found > minpix pixels, recenter next window on their mean position
        if len(good_left_inds) > minpix:
            leftx_current = np.int(np.mean(nonzerox[good_left_inds]))
        if len(good_right_inds) > minpix:
            rightx_current = np.int(np.mean(nonzerox[good_right_inds]))

    # Concatenate the arrays of indices
    left_lane_inds = np.concatenate(left_lane_inds)
    right_lane_inds = np.concatenate(right_lane_inds)

    # Extract left and right line pixel positions
    leftx = nonzerox[left_lane_inds]
    lefty = nonzeroy[left_lane_inds]
    rightx = nonzerox[right_lane_inds]
    righty = nonzeroy[right_lane_inds]

    # to plot
    out_img[nonzeroy[left_lane_inds], nonzerox[left_lane_inds]] = [255, 0, 0]
    out_img[nonzeroy[right_lane_inds], nonzerox[right_lane_inds]] = [0, 0, 255]

    # Fit a second order polynomial to each
    left_fit = np.polyfit(lefty, leftx, 2)
    right_fit = np.polyfit(righty, rightx, 2)
    return left_fit, right_fit, out_img
# Generate x and y values for plotting
def get_fit_xy(img, left_fit, right_fit):
    ploty = np.linspace(0, img.shape[0]-1, img.shape[0])
    left_fitx = left_fit[0]*ploty**2 + left_fit[1]*ploty + left_fit[2]
    right_fitx = right_fit[0]*ploty**2 + right_fit[1]*ploty + right_fit[2]
    return left_fitx, right_fitx, ploty

left_fit, right_fit, out_img = find_line_fit(binary)
left_fitx, right_fitx, ploty = get_fit_xy(binary, left_fit, right_fit)

fig = plt.figure(figsize=(16, 9))
plt.imshow(out_img)
plt.plot(left_fitx, ploty, color='white', linewidth=3.0)
plt.plot(right_fitx, ploty, color='white',  linewidth=3.0)
plt.xlim(0, 1280)
plt.ylim(720, 0)
plt.show()

结果:

这里写图片描述

具体来说,为了确定哪些像素属于车道线,首先确定左右两条车道线的大致位置,这一步非常简单,只需要将图片中的像素沿y轴累加,找出图片中间点左右的峰值,即为车道线可能的区域,然后自底向上使用滑动窗口,计算窗口内的不为0的像素点,如果像素点的数量大于某个阈值,那么就以这些点的均值作为下一个滑动窗口的中心。

得到候选的像素以后,使用numpy中的 np.polyfit()方法来拟合这些点,我们使用一个二次多项式来拟合。

拟合的曲线是x关于y的多项式表述,即自变量是y,因变量是x。

还原至原视角

接着在将拟合的曲线使用透视变换还原到原视角:

def project_back(wrap_img, origin_img, left_fitx, right_fitx, ploty, M):
    warp_zero = np.zeros_like(wrap_img).astype(np.uint8)
    color_warp = np.dstack((warp_zero, warp_zero, warp_zero))

    # Recast the x and y points into usable format for cv2.fillPoly()
    pts_left = np.array([np.transpose(np.vstack([left_fitx, ploty]))])
    pts_right = np.array([np.flipud(np.transpose(np.vstack([right_fitx, ploty])))])
    pts = np.hstack((pts_left, pts_right))

    # Draw the lane onto the warped blank image
    cv2.fillPoly(color_warp, np.int_([pts]), (0, 0, 255))

    # Warp the blank back to original image space using inverse perspective matrix (Minv)
    newwarp = perspective_transform(color_warp, M)
    # Combine the result with the original image
    result = cv2.addWeighted(origin_img, 1, newwarp, 0.3, 0)
    return result

M = cv2.getPerspectiveTransform(np.float32(dst_corners), np.float32(src_corners))
result = project_back(binary, test_img, left_fitx, right_fitx, ploty, M)
fig = plt.figure(figsize=(16, 9))
plt.imshow(result)

效果:
这里写图片描述

后记:在得到鸟瞰图的拟合曲线以后,我们是可以根据该曲线来计算车道的曲率的,同时如果知道车辆和相机的相对位置,还可以计算车辆距离左右车道线的距离,这就是目前基于视觉的车道偏离预警的实现方法之一,读者可以根据需要自行实现。

完整代码见:http://download.csdn.net/download/adamshan/10148446

2017-12-04 18:52:02 AdamShan 阅读数 17143
  • Mask R-CNN图像实例分割实战:训练自己的数据集

    Mask R-CNN是一种基于深度学习的图像实例分割方法,可对物体进行目标检测和像素级分割。 本课程将手把手地教大家使用VIA图像标注工具制作自己的数据集,并使用Mask R-CNN训练自己的数据集,从而能开展自己的图像分割应用。 本课程有三个项目案例实践: (1) balloon实例分割 :对图像中的气球做检测和分割 (2) pothole(单类物体)实例分割:对汽车行驶场景中的路坑进行检测和分割 (3) roadscene( 多类物体)实例分割:对汽车行驶场景中的路坑、车、车道线等进行检测和分割 本课程使用Keras版本的Mask R-CNN,在Ubuntu系统上做项目演示。 本课程提供项目的数据集和python程序文件。 下面是使用Mask R-CNN对roadscene进行图像实例分割的测试结果: 下图是使用Mask R-CNN对pothole进行单类物体图像实例分割的测试结果: 下图是使用Mask R-CNN对roadscene进行多类物体图像实例分割的测试结果:

    786 人正在学习 去看看 白勇

无人驾驶汽车系统入门(六)——基于传统计算机视觉的车道线检测(1)

感知,作为无人驾驶汽车系统中的“眼睛”,是目前无人驾驶汽车量产和商用化的最大障碍之一(技术角度),
目前,高等级的无人驾驶汽车系统仍然非常依赖于激光雷达的测量,通过激光雷达构造周围环境的3D地图,从而为无人驾驶系统的决策和规划提供准确的环境信息和自身相对的位置信息。然而,激光雷达雷达在成本,解析度的方面都不理想,所以基于视觉的无人驾驶相关技术(主要在感知方面)近几年发展迅猛,本节我们从传统的 计算机视觉(Computer Version, CV) 出发,来了解并且实践基于传统计算机视觉算法的车道线检测技术。

原创不易,转载请注明出处:http://blog.csdn.net/adamshan/article/details/78712120

视觉感知VS激光雷达

激光雷达能够为无人驾驶汽车提供障碍物检测,高精度地图和定位等多方面的支持,在目前各个研究团队广泛使用。下图是激光雷达的简易构造:

这里写图片描述

激光雷达以一定的角速度旋转,发射出波长在600nm到1000nm之间的激光射线,同时收集来自反射点的信息,激光雷达每旋转一周,收集到的反射点的坐标构成的一个集合,我们称之为 点云(Point Cloud)。下图就是一张点云图:

这里写图片描述

激光雷达具有可靠性高,障碍物细节分辨以及精准测距等优点,同时不受光线条件的影响。但是,激光雷达的价格居高不下,例如目前在各个研究团队中广泛使用的Velodyne HDL-64E 激光雷达的售价就在10万美元左右,这使得成本成为搭载激光雷达的无人驾驶汽车的商用化的障碍。

作为比较,基于计算机视觉(Computer Version, CV)的感知成本相对低,结合视觉,IMU+GPS以及毫米波雷达的方案是目前已经商用驾驶辅助系统中采用的主流方案(例如特斯拉的Autopilot),计算机视觉是指 通过图像来感知和理解我们的世界 的科学。对于无人驾驶而言,计算机视觉可以帮助确定车道线的位置,识别车辆,行人以及其他事物,以确保无人车的形式安全,基于深度学习的技术,我们甚至可以实现端到端的自动驾驶(输入图像,输出操作)。

此外,人类驾驶员在做感知的时候只需要一双眼睛(偶尔可能需要用耳朵去听声音),而不需要精确知道距离障碍物有多少厘米,障碍物长宽高分别是多少厘米等,所以我们可以相信——无人驾驶的最终形态或许就像人类驾驶一样仅仅需要视觉感知。

本节我们将使用传统的计算机视觉技术进行车道线的检测,同时基于检测出来的车道线计算车道线的曲率和车辆偏离车道线中心线的距离。在下一届中,我们将使用TensorFlow实现基于深度学习的车道线检测。

相机标定

相机标定(Camera Calibration) 通常是做计算机视觉的第一步,首先,为什么要做相机标定呢?因为我们通过相机镜头记录下的图像往往存在一定程度的失真,这种失真往往表现为 图像畸变(Image Distortion) 。畸变分为两类:

  1. 径向畸变(radial distortion):由于透镜的特性,光线容易在相机镜头的边缘出现较小或者较大幅度的弯曲,称之为径向畸变。这种畸变在普通廉价的镜头中表现更加明显,径向畸变主要包括桶形畸变和枕形畸变两种。以下分别是枕形和桶形畸变示意图:

这里写图片描述

  1. 切向畸变(tangential distortion):是由于透镜本身与相机传感器平面(成像平面)或图像平面不平行而产生的,这种情况多是由于透镜被粘贴到镜头模组上的安装偏差导致。

畸变(distortion) 是对直线投影(rectilinear projection)的一种偏移。简单来说直线投影是场景内的一条直线投影到图片上也保持为一条直线。那畸变简单来说就是一条直线投影到图片上不能保持为一条直线了,这是一种光学畸变(optical aberration)。可能由于摄像机镜头的原因,这里不讨论,有兴趣的可以查阅光学畸变的相关的资料。

即便一般能够由五个参数来采集,我们称之为 畸变参数(distortion parameters) ,我们使用 D=(k1,k2,p1,p2,k3) 来表示,对于径向畸变,可以使用如下公式进行矫正:

xcorr=xdis(1+k1r2+k2r4+k3r6)

ycorr=ydis(1+k1r2+k2r4+k3r6)

对于切向畸变,可以用如下公式来矫正:

xcorr=xdis+[2p1xdisydis+p2(r2+2x2dis)]

ycorr=ydis+[p1(r2+2y2dis)+2p2xdisydis]

以上公式中:

  • xdisydis 表示有畸变的坐标;
  • xcorrycorr 表示修复后的坐标;
  • k1k2k3 表示径向畸变参数;
  • p1p2 表示切向畸变参数;
  • r 表示矫正以后的坐标到图片中心的距离

直接看公式难以理解,我们看如下的图像:

这里写图片描述

这就是为什么我们标定相机通常使用棋盘图像了,下面我们使用OpenCV来实现一下相机标定。

相机,棋盘图

首先从各个角度对这个棋盘拍照,得到一系列如下的照片:

这里写图片描述

一般来说,使用相机在各个角度拍摄20张左右的棋盘图即可完成后面的标定工作。

使用OpenCV找出棋盘的对角点

首先读取图像并转为灰度图:

from __future__ import print_function

import numpy as np
import cv2
import matplotlib.pyplot as plt
import pickle
%matplotlib inline

cal = plt.imread('calibration1.jpg')

fig1 = plt.figure(1, figsize=(16, 9))
cal_gray = cv2.cvtColor(cal, cv2.COLOR_RGB2GRAY)
plt.subplot(2,2,1)
plt.imshow(cal)
plt.subplot(2,2,2)
plt.imshow(cal_gray, cmap='gray')

这里写图片描述

使用OpenCV的cv2.findChessboardCorners()函数找出棋盘图中的对角(即图片中黑白相对的点的坐标),同时使用cv2.drawChessboardCorners()将之画出来:

ret, corners = cv2.findChessboardCorners(cal_gray, (9, 6),None)
if ret == True:
    cal = cv2.drawChessboardCorners(cal, (9, 6), corners, ret)
plt.imshow(cal)

这里写图片描述

在上图中我们查找出了这个棋盘图内 9×6的黑白对角在图片中的像素位置,接着我们构造这些对角点在在现实世界中的相对位置,我们将这些位置简化成整数值,比如说第二行的第1个点就表示为 [0,1,0] , 即第0列第1行。

objp = np.zeros((6*9, 3), np.float32)
objp[:, :2] = np.mgrid[0:9, 0:6].T.reshape(-1, 2)

img_points = []
obj_points = []

img_points.append(corners)
obj_points.append(objp)

对所有的的标定用的棋盘图像都进行上述操作求得对角的像素位置和实际相对位置(对于一个棋盘来说,实际相对位置其实恒定的),并将这些都添加到 img_pointsobj_points 两个列表中。最后我们使用OpenCV中的 cv2.calibrateCamera() 即可求得这个相机的畸变系数,在后面的所有图像的矫正都可以使用这一组系数来完成。

image_size = (cal.shape[1], cal.shape[0])
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(obj_points, img_points,
 image_size, None, None)

矫正一张图像我们使用 cv2.undistort() 方法来实现:

# Read in a test image
img = cv2.imread('calibration3.jpg')
undist = cv2.undistort(img, mtx, dist, None, mtx)
plt.subplot(2,2,1)
plt.imshow(img)
plt.subplot(2,2,2)
plt.imshow(undist)

矫正的结果:

这里写图片描述

我们发现经过标定以后,相机拍出来的图像更接近于真实情况,因失真造成的”扭曲的直线”也被纠正过来。

确定ROI

ROI(Region of interest) 即我们处理一个视觉任务时“感兴趣的区域”,当然不同的任务ROI是不一样的,对于车道线检测而言(如下图),ROI就是车辆的前方的车道线区域:

这里写图片描述

我们可以通过透视变换来获得一个相对更加直观的视角(比如说在天空俯视的视角),然后在新的视角来圈出ROI。 透视变换(Perspective Transformation) 是将图片投影到一个新的 视平面(Viewing Plane) ,也称作 投影映射(Projective Mapping) 。在OpenCV中,通过使用函数 cv2.getPerspectiveTransform()cv2.warpPerspective() 即可完成对一张图片的透视变换。

透视变换基于给定的映射关系,通过对原图像进行扭曲,从而得到一个近似另一个视角的图像

首先我们读取并且矫正图像,矫正后的结果:

这里写图片描述

对直道进行透视变换:

def perspective_transform(img, M):
    img_size = (img.shape[1], img.shape[0])
    warped = cv2.warpPerspective(img, M, img_size, flags=cv2.INTER_LINEAR)
    return warped


# left_top to left_bottom,
corners = [(603, 445), (677, 445), (1105, 720), (205, 720)]

wrap_offset = 150
src_corners = [(603, 445), (677, 445), (1105, 720), (205, 720)]
dst_corners = [(205 + wrap_offset, 0), (1105 - wrap_offset, 0), (1105 - wrap_offset, 720), (205 + wrap_offset, 720)]
M = cv2.getPerspectiveTransform(np.float32(src_corners), np.float32(dst_corners))
wrap_img= perspective_transform(straight_lines1, M)

subplot(1, 2, [straight_lines1, wrap_img])

结果如图:

这里写图片描述

我们解读一下代码:cv2.getPerspectiveTransform() 需要两个参数 srcdst,他们分别为原图像中能够表示一个矩形的四个点的坐标以及扭曲以后图像的边缘四角在当前图像中的坐标,这两个矩形的坐标不同的相机的数值也不同,不如说实例中相机的分辨率为 1280×720 , 那么 [(603, 445), (677, 445), (1105, 720), (205, 720)] 在图像中构成一个梯形,这个梯形在俯视图(或者说鸟瞰图)中是一个长方形,然后我们以这个梯形的高作为目标图像的高,前后各减去一个偏移(在实例中这个偏移是150个像素),就是我们的目标图像这个目标图像,也即是我们的ROI。

下面我们就可以在这个鸟瞰图上来做道路线检测了,由于篇幅的原因,道路线检测算法以及完整代码都将在下一篇博客中给出,希望大家多关注收藏!

2018-06-23 17:10:07 weixin_37762749 阅读数 5048
  • Mask R-CNN图像实例分割实战:训练自己的数据集

    Mask R-CNN是一种基于深度学习的图像实例分割方法,可对物体进行目标检测和像素级分割。 本课程将手把手地教大家使用VIA图像标注工具制作自己的数据集,并使用Mask R-CNN训练自己的数据集,从而能开展自己的图像分割应用。 本课程有三个项目案例实践: (1) balloon实例分割 :对图像中的气球做检测和分割 (2) pothole(单类物体)实例分割:对汽车行驶场景中的路坑进行检测和分割 (3) roadscene( 多类物体)实例分割:对汽车行驶场景中的路坑、车、车道线等进行检测和分割 本课程使用Keras版本的Mask R-CNN,在Ubuntu系统上做项目演示。 本课程提供项目的数据集和python程序文件。 下面是使用Mask R-CNN对roadscene进行图像实例分割的测试结果: 下图是使用Mask R-CNN对pothole进行单类物体图像实例分割的测试结果: 下图是使用Mask R-CNN对roadscene进行多类物体图像实例分割的测试结果:

    786 人正在学习 去看看 白勇

车辆检测和车道检测

NKU计算机视觉期末大作业


目录


软件要求

  • opencv3.0+
  • opencv-contrib
  • cmake
  • CLion编译器(可选)
  • opencv python版本

车辆检测

车辆检测的整体框架是结合hog-svm分类器和haar-cascade分类器对车辆进行检测,之后采用非极大值抑制,得出最终的检测框。

根据hog特征进行训练

方向梯度直方图(Histogram of Oriented Gradient, HOG)特征是一种在计算机视觉和图像处理中用来进行物体检测的特征描述子。它通过计算和统计图像局部区域的梯度方向直方图来构成特征。HOG特征提取方法就是将一个image(要检测的目标或者扫描窗口):

1)灰度化(将图像看做一个x,y,z(灰度)的三维图像);

2)采用Gamma校正法对输入图像进行颜色空间的标准化(归一化);目的是调节图像的对比度,降低图像局部的阴影和光照变化所造成的影响,同时可以抑制噪音的干扰;

3)计算图像每个像素的梯度(包括大小和方向);主要是为了捕获轮廓信息,同时进一步弱化光照的干扰。

4)将图像划分成小cells(例如6*6像素/cell);

5)统计每个cell的梯度直方图(不同梯度的个数),即可形成每个cell的descriptor;

6)将每几个cell组成一个block(例如3*3个cell/block),一个block内所有cell的特征descriptor串联起来便得到该block的HOG特征descriptor。

7)将图像image内的所有block的HOG特征descriptor串联起来就可以得到该image(你要检测的目标)的HOG特征descriptor了。这个就是最终的可供分类使用的特征向量了。

为了样本的多样性,我采用的是部分数据集一的负样本数据集二部分正样本和全部负样本作为训练数据。最终正样本与负样本的比例为1:3,一共16000张图片。

正样本大致如下:

正样本

负样本大致如下:
负样本

因为汽车大致呈现正方形,故对每张图片resize到64x64大小,然后提取hog特征。在这里我选择的相关系数为:

  • block大小:16x16
  • window大小:64x64
  • cell大小:4x4
  • block步长:x方向为8,y方向为8
  • window步长:x方向为8,y方向为8

根据如下公式可以算出整个hog特征维度为8100

dimension=9blockxcellxblockycelly(1+windowxblockxblockstridex)(1+windowyblockyblockstridey)

如果想要采用不同的步长或者块大小,可以在config.cpp可以修改这些全局变量。

在提取特征之前我们要先将训练集与测试集写到两个txt中方便读取,考虑到c++文件读写以及科学计算方面不是很方便,在这里我采用python对数据集进行的划分处理,利用numpy,cv2,sklearn.model_selection可以较为方便的完成,具体方法在/python_func/BuildImgList.py文件中。

接下来可以提取hog特征,这部分代码在GetFeature.cpp中,引入头文件opencv2/xfeature2d.hpp,我们可以调用提取hog特征的方法:

Mat GetHOGfeature(string imgname){
    Mat img = imread(imgname);
    resize(img, img, Size(Imgheight, Imgwidth));
    Ptr<HOGDescriptor> hog = new HOGDescriptor(Size(Window_y, Window_x),
                                               Size(block_y, block_x),
                                               Size(block_stride_y, block_stride_x),
                                               Size(cell_y, cell_x), 9);
    assert(hog->getDescriptorSize() == dimension);
    vector<float> descriptor;
    hog->compute(img, descriptor, Size(Window_stride_y, Window_stride_x), Size(0, 0));
    assert(descriptor.size() == dimension);
    Mat s(descriptor);
    transpose(s, s);
    return s;
}

我们对所有图片提取特征,接下来的步骤便是送进支持向量机中进行训练,这一部分的代码在train.cpp中。在对数据集进行处理时,我把正样本的label标注为1,把负样本的label标注为-1。由于这是一个二分类问题,因此在选择SVM的核类型时,选择线性核即可。为了求出最优的参数,在这里采用opencv的machine learning模块的trainAuto函数(可以自动调节超参数)而非train函数。在调用之前必须要对一些超参数赋予初始值,如下为训练方法:

void HOGSVMtrainAuto(string trainlist){
    Mat Data4Train(0, dimension, CV_32FC1), labels(0, 1, CV_32SC1);
    GetAllImgHOGfeature(Data4Train, labels, trainlist, ImgTrainPath);

    struct timeval pre, after;
    gettimeofday(&pre, NULL);

    Ptr<ml::SVM> model = ml::SVM::create();
    model->setKernel(ml::SVM::KernelTypes::LINEAR);
    model->setType(ml::SVM::C_SVC);
    model->setP(1e-2);
    model->setC(1);
    model->setGamma(1e-2);
    model->setTermCriteria(cvTermCriteria(CV_TERMCRIT_ITER, 10000, 0.000001));

    if(debug){
        cout << "height: "<<Data4Train.rows << ", width: " << Data4Train.cols << endl;
        cout << "trainingdata depth: " << Data4Train.depth() << endl;
        cout << "label depth: " << labels.depth() << endl;
        cout << "trainingdata type " << Data4Train.type() << endl;
        cout << "label type " << labels.type() << endl;
    }

    assert(Data4Train.type() == CV_32FC1);
    assert(labels.type() == CV_32SC1);

    Ptr<ml::TrainData> data = ml::TrainData::create(Data4Train, ml::ROW_SAMPLE, labels);
    cout << "start training ..." << endl;
    model->trainAuto(data, 10);
    cout << "finish training ..." << endl;
    gettimeofday(&after, NULL);
    cout << "training time: " << after.tv_sec - pre.tv_sec << "s"<< endl;
    model->save("../model/svm_hog_classifier.xml");
    cout << "model saving fininshed ..." << endl;
}

训练大概花费20~30分钟,训练完成后会生成xml文件,即训练好的模型,在测试集上测试,准确率可以达到98%,仅从测试集上看,效果还是不错的。

根据haar特征进行训练

Haar-like特征点,是一种简单的特征描述,其理论相当容易理解,就是按照下图的模式来计算白色窗口的像素总和和黑色窗口像素总和的差,如下图:

haar

利用提取到的haar特征可以训练弱分类器,通过若干个弱分类器可以组建一个强分类器,类似于一种投票的手段,只不过不同的分类器具有不同的权重,整个训练过程可以看做是一个不断调整权重大小的过程,如下:

adaboost

接下来便是级联,如下图,最终的分类器是由多个强分类器级联而成。当且仅当通过了所有分类器的判定后才能输出结果。

cascade

仅仅参考图像的hog特征可能会存在漏检测。由此,我将人脸识别中常见的级联器检测方法迁移到车辆检测中,参考博文一博文二和opencv官方文档。以数据集一的全部正样本负样本为数据源,正负样本比大概为1:3。首先制作两个标准格式的txt文件,一个是正样本txt,另一个时负样本txt。正样本txt格式大致如下(路径 图片中目标个数 xmin ymin xmax ymax):

../../../data/ProData3/17622107.jpg 1 0 0 38 38
../../../data/ProData3/112237754.BMP 1 0 0 128 96
../../../data/ProData3/12130486.BMP 1 0 0 128 96
...

负样本txt只需要图片路径即可,如下:

../../../data/ProData4/174157305.jpg
../../../data/ProData4/18635891.jpg
../../../data/ProData4/1356388.jpg
...

为了方便我用python制作了该txt文件,具体方法在/adaboost/Gen_Imglist.py中。

opencv提供了opencv_createsamples.exe建立训练所需要的参数列表,在命令行中调用该exe,输入如下命令:

opencv_createsamples -vec pos.vec -info pos_info.dat -bg neg_info.dat -num 2000 -w 24 -b 24 

这里写图片描述

根据上图的解释可知:-vec为最终生成的文件,-num为要产生的正样本的数量,-w为输出的样本高度,-h为输出的样本宽度。

接下来利用opencv提供的opencv_traincascade.exe进行训练,在命令行中调用该exe,输入如下命令:

opencv_traincascade -data ../model/adaboost -vec pos.vec -bg neg_info.dat -numPos 2000 -numNeg 7000

这里写图片描述

根据上图的解释可知:在训练过程中所有中间模型都会放在model/adaboost这个文件夹里,这里采用2000个正样本和7000个负样本。

级联器的训练很慢,大概训练了一天左右,在模型文件夹中存放着每一级的弱分类器和最终的分类器。

最终检测

结合训练好的hog-svm分类器和haar-cascade分类器。便可以检测出物体。大致pipline如下:

  • 对图像进行缩放,resize到448x448
  • 我们以64x64的滑动窗口在图像上滑动,用hog-svm分类器和haar-cascade分类器检测
  • 滑动窗口以一定比例放大,对图像进行多尺度检测,避免漏检较大的车辆
  • 对所有结果进行非极大值抑制,得出最终检测结果

部分检测代码如下:

void FinalDetect(string filename, string model_cascade, string model_hog, int dataset = 1, bool IsLine = false) {
    setUseOptimized(true);
    setNumThreads(8);

    HOGDescriptor my_hog(Size(Window_y, Window_x), Size(block_y, block_x), Size(block_stride_y, block_stride_x),
                         Size(cell_y, cell_x), 9);
    CascadeClassifier car_classifier;
    car_classifier.load(model_cascade);
    //get support vector from model
    Ptr<ml::SVM> model = ml::StatModel::load<ml::SVM>(model_hog);
    Mat sv = model->getSupportVectors();
    vector<float> hog_detector;
    const int sv_total = sv.cols;
    Mat alpha, svidx;
    double rho = model->getDecisionFunction(0, alpha, svidx);
    Mat alpha2;
    alpha.convertTo(alpha2, CV_32FC1);
    Mat result(1, sv_total, CV_32FC1);
    result = alpha2 * sv;
    for (int i = 0; i < sv_total; ++i)
        hog_detector.push_back(-1 * result.at<float>(0, i));
    hog_detector.push_back((float) rho);
    //load vector to hog detector
    my_hog.setSVMDetector(hog_detector);

    vector<Rect> detections;
    vector<double> foundWeights;
    vector<int> rejLevel;
    vector<bbox_info> dets;
    vector<bbox_info> keep;
    VideoCapture cap;
    cap.open(filename);
    while (true) {
        Mat img;
        cap >> img;
        if (!img.data)
            break;
        resize(img, img, Size(448, 448));
        cout << img.size() << endl;
        if (IsLine)
            LineDetect2(img, dataset);

        detections.clear();
        foundWeights.clear();
        rejLevel.clear();
        dets.clear();
        keep.clear();

        my_hog.detectMultiScale(img, detections, foundWeights, 0, Size(8, 8), Size(), 1.1, 2., true);
        cout << "hog detect object: " << detections.size() << endl;
        for (size_t i = 0; i < detections.size(); i++) {
            if (foundWeights[i] > 1.3) {
                bbox_info tmp_bbox(detections[i].x, detections[i].y, detections[i].br().x, detections[i].br().y,
                                   foundWeights[i]);
                dets.push_back(tmp_bbox);
            }
        }

        car_classifier.detectMultiScale(img, detections, rejLevel, foundWeights, 1.1, 3, 0, Size(), Size(), true);
        cout << "cascade detect object: " << detections.size() << endl;
        for (int i = 0; i < detections.size(); i++) {
            if (rejLevel[i] < 20 || foundWeights[i] < 1.)
                continue;
            bbox_info tmp(detections[i].x, detections[i].y, detections[i].br().x, detections[i].br().y,
                          foundWeights[i]);
            dets.push_back(tmp);
        }

        keep = nms(dets);
        for (size_t i = 0; i < keep.size(); i++) {
            Point p1(keep[i].xmin, keep[i].ymin), p2(keep[i].xmax, keep[i].ymax);
            Scalar color(0, 255, 0);
            rectangle(img, p1, p2, color, 2);
        }
        imshow("detect", img);
        waitKey(0);

    }
}

部分结果如下:

检测

检测2

直线检测

参考博文3博文4博文5,对于车道检测,主要采用如下的pipline:

  1. 对图像进行透视变换,使其变为鸟瞰图:
Point2f origin[] = {Point2f(204, 286), Point2f(71, 448), Point2f(394, 448), Point2f(243, 286)};
        Point2f dst[] = {Point2f(112, 0), Point2f(112, 448), Point2f(336, 448), Point2f(336, 0)};
        trans = getPerspectiveTransform(origin, dst);
        warpPerspective(img_o ,img, trans, img.size());

这里写图片描述

  1. 对原图像进行x-sobel滤波,并进行阈值过滤
void mag_threshold(const Mat img, Mat &out, int sobel_kernel, int min_thres, int max_thres) {

    cvtColor(img, out, CV_BGR2GRAY);
    Sobel(out, out, CV_8UC1, 1, 0, sobel_kernel);
    normalize(out, out, 0, 255, NORM_MINMAX);
    threshold(out, out, min_thres, 0, THRESH_TOZERO);
    threshold(out, out, max_thres, 255, THRESH_BINARY);
}

这里写图片描述

  1. 对原图像转换到HLS空间,保留黄色和白色(车道多为黄色和白色)
void yellow_white_threshold(Mat origin, Mat &out1) {
    int y_lower[] = {10, 0, 100};
    int y_upper[] = {40, 255, 255};
    int w_lower[] = {0, 200, 0};
    int w_upper[] = {180, 255, 255};
    Mat HLS, y_mask, w_mask, mask;
    cvtColor(origin, HLS, CV_BGR2HLS);

    vector<int> yellow_lower(y_lower, y_lower + 3);
    vector<int> yellow_upper(y_upper, y_upper + 3);
    vector<int> white_lower(w_lower, w_lower + 3);
    vector<int> white_upper(w_upper, w_upper + 3);
    inRange(HLS, yellow_lower, yellow_upper, y_mask);
    inRange(HLS, white_lower, white_upper, w_mask);

    bitwise_or(y_mask, w_mask, mask);
    bitwise_and(origin, origin, out1, mask);
    cvtColor(out1, out1, CV_HLS2BGR);
    cvtColor(out1, out1, CV_BGR2GRAY);
    threshold(out1, out1, 130, 255, THRESH_BINARY);
}

这里写图片描述

  1. 根据2,3步得到最终的二值图

这里写图片描述

  1. 利用霍夫变换找出相应的直线端点(根据直线斜率进行一定的限制)
    vector<Vec4i> lines;
    vector<Point2f> leftlines;
    vector<Point2f> rightlines;
    HoughLinesP(out1, lines, 1, CV_PI / 180, 50, 30, 10);
    cout << lines.size() << endl;
    for (size_t i = 0; i < lines.size(); i++) {
        //abandon horizontal line.
        if (lines[i][1] == lines[i][3])
            continue;
        //get left lines
        if (lines[i][0] <= 224 && lines[i][2] <=224){
            float k = 1.5;
            //if not verticle line
            if (lines[i][0] != lines[i][2])
                k = fabs(float(lines[i][3]-lines[i][1])/float(lines[i][2]-lines[i][0]));
            if (k>=1.5) {
                leftlines.push_back(Point2f(lines[i][0], lines[i][1]));
                leftlines.push_back(Point2f(lines[i][2], lines[i][3]));
            }

        }
  1. 对这些点进行线性回归
    Vec4f line_left, line_right;
    fitLine(leftlines, line_left, DIST_L1, 0, 0.01, 0.01);
    fitLine(rightlines, line_right, DIST_L1, 0, 0.01, 0.01);
  1. 画出直线围成的区域,并进行高亮,显示到原图上

这里写图片描述

整个流程图如下:
这里写图片描述

源代码详见:我的github

2012-04-16 13:53:24 abcjennifer 阅读数 5468
  • Mask R-CNN图像实例分割实战:训练自己的数据集

    Mask R-CNN是一种基于深度学习的图像实例分割方法,可对物体进行目标检测和像素级分割。 本课程将手把手地教大家使用VIA图像标注工具制作自己的数据集,并使用Mask R-CNN训练自己的数据集,从而能开展自己的图像分割应用。 本课程有三个项目案例实践: (1) balloon实例分割 :对图像中的气球做检测和分割 (2) pothole(单类物体)实例分割:对汽车行驶场景中的路坑进行检测和分割 (3) roadscene( 多类物体)实例分割:对汽车行驶场景中的路坑、车、车道线等进行检测和分割 本课程使用Keras版本的Mask R-CNN,在Ubuntu系统上做项目演示。 本课程提供项目的数据集和python程序文件。 下面是使用Mask R-CNN对roadscene进行图像实例分割的测试结果: 下图是使用Mask R-CNN对pothole进行单类物体图像实例分割的测试结果: 下图是使用Mask R-CNN对roadscene进行多类物体图像实例分割的测试结果:

    786 人正在学习 去看看 白勇

车道检测问题研究了很长时间,本博客以此为主题进行一系列探究,包括别人论文以及实现结果,希望能够和广大计算机视觉研究者共同进步!

本文主要以左右车道检测方法中基于车道侧面连续的曲线拟合方法进行研究。

通常的车道检测方法分两步:(1)特征提取(2)车道几何模型的建立与匹配

首先我们来说说特征提取部分:

特征提取的目的是最大限度地:1.保留可能是车道的pixels2.去掉可能非车道的pixels

车道检测问题探究(一)车道特征提取(上)http://blog.csdn.net/abcjennifer/article/details/7453286

中已经提到了一种特征提取方法,基于像素光强梯度进行提取,这里我们再介绍另一种方法,基于图像车道宽度进行滤波,并给出tracking算法。

===========================Feature Extraction===========================

1.基于光强梯度提取

其中I为灰度图,只有符合该式的像素点(u,v)被留下

2. 基于车道宽度提取

类似http://blog.csdn.net/abcjennifer/article/details/7453286中提到的,只有长度在[S1,S2]内的线段被留下

3.选取一个线段的重点为feature point

============================Matching============================

两条车道间夹得长度称为车道宽度(road width),记为Lhc, 这一步中寻找couples of lane;即寻找一对对的左右车道。

采用平均值估计法,该步骤目的为找到确定的一组(Uli,Vli),(Uri,Vri);

============================Tracking============================

Define:

Ali & Arias left and right accumulator respectively,一旦其超过阈值,以后就都由另一侧的车道预测左侧车道位置

Amaxas the threshold of Ali & Ari

L as slide window size

[u,v] as 第t-1帧中scan line u 上的feature point 坐标

以左车道第t帧跟踪为例,伪代码如下:

Initialize Ali=Ari=1;
Search 在第t帧中[u,v-L] 到[u,v+L]区间内是否有feature point
if(exist)
	取一个离[u,v]最近的点作为t帧的跟踪点,Ali=1;
else
	if(Ali<Amax)	
		第t帧的feature point=第t-1帧的feature point,Ali++;
	else if(Ari<Amax)
		第t帧的feature point由右车道点-车道宽度给出
	else 
		Adjust Parameters and resume from feature extraction


2012-04-13 12:04:02 abcjennifer 阅读数 9662
  • Mask R-CNN图像实例分割实战:训练自己的数据集

    Mask R-CNN是一种基于深度学习的图像实例分割方法,可对物体进行目标检测和像素级分割。 本课程将手把手地教大家使用VIA图像标注工具制作自己的数据集,并使用Mask R-CNN训练自己的数据集,从而能开展自己的图像分割应用。 本课程有三个项目案例实践: (1) balloon实例分割 :对图像中的气球做检测和分割 (2) pothole(单类物体)实例分割:对汽车行驶场景中的路坑进行检测和分割 (3) roadscene( 多类物体)实例分割:对汽车行驶场景中的路坑、车、车道线等进行检测和分割 本课程使用Keras版本的Mask R-CNN,在Ubuntu系统上做项目演示。 本课程提供项目的数据集和python程序文件。 下面是使用Mask R-CNN对roadscene进行图像实例分割的测试结果: 下图是使用Mask R-CNN对pothole进行单类物体图像实例分割的测试结果: 下图是使用Mask R-CNN对roadscene进行多类物体图像实例分割的测试结果:

    786 人正在学习 去看看 白勇

车道检测问题研究了很长时间,本文以此为主题进行一系列探究,包括别人论文以及实现结果,希望能够和广大计算机视觉研究者共同进步!

本文主要以左右车道检测方法中基于车道侧面连续的曲线拟合方法进行研究。

通常的车道检测方法分两步:(1)特征提取(2)车道几何模型的建立与匹配

车道检测问题探究(一)车道特征提取(上) http://blog.csdn.net/abcjennifer/article/details/7453286 中我们已经讲过怎样提取车道特征并滤波,下面我们来讨论下车道形状估计,即车道几何建模、车道拟合的做法。

A. 道路线性模型&双曲线模型

通常道路检测范围为[10m,40m]内,这样就可以用线性方程作为道路模型,即

(1)

式中(u,v)表示拍摄图像中的车道曲线上的像素坐标,为系数矩阵,为u的初等函数。

如X(v)={3v,1/v},A={2,3}之类的……

这样的线性拟合可以将模型估计问题变成一个匹配问题从而加快检测速度。但实际上我们更通常地使用双曲多项式来进行车道拟合:

这里用进行建模,其中A为参数矩阵(系数矩阵),X(v)为双曲多项基。

B. 含噪声的车道模型

通常道路模型的鲁棒性问题由卡尔曼预测滤波器距离预测门限结合解决。因此,我们考虑一个高斯噪声模型模拟车道。但《 Lising Robust  Estimation  Algorithms  for  tracking  Ezplicit  Curves.  -  In  Proceedings Eumpesn Conference on Computer Vision (ECCV2002)  May  2002, Part  I, pp 492-507. 》中说高斯模型在很多情况下不适于道路图像,通过看提取出特征的错误分布的对数直方图就可以得知了:如果采用高斯分布,得到的直方图会成为一条抛物线。所以实际上,噪声模型并非服从高斯分布(如下图为对数直方图)。


因此,上面那篇文章引用了一个符合实际噪声分布的两参数模型,其pdf定义如下:

(2)

其中,第一个参数α表示该pdf被减少的程度,第二个参数σ位比例参数。

C. 鲁棒的车道参数估计

首先我们介绍一下匹配算法(其实在上面那篇文献中有详细说明,包括收敛性证明),然后描述该算法怎样应用于不同类型动态模型的建立,最后用一个更好的协方差矩阵来进行优化

我们假设在第一步http://blog.csdn.net/abcjennifer/article/details/7453286中检测到的车道中心是(1)式描述的曲线的噪声测量。现在我们假定噪声b仅在u轴上符合(2)所示的概率密度。因此我们有 我们的目的为估计能使“一侧车道线上所提取出来的n个特征点(车道中心)都尽量满足该方程”的曲线参数A。这是个不断迭代,修改权值的算法,每一步都要解决一个线性问题。

道路模型匹配算法可以利用两帧相邻图像之间的时间连续性嵌入到车道跟踪算法中。著名的跟踪算法是Condensation Algorithm(浓缩算法)和Kalman Filter,其中,Kalman filter以其线性复杂度成为优势,但问题是它默认假定噪声服从高斯分布,而Condensation Algorithm无假设服从什么分布,而是采用估计的方法。从我们的实验结果中看,对于曲线参数噪声而言,这两种方法得到的曲线参数相差不多,因此我们用Kalman滤波法(因为算法复杂度低)进行车道跟踪。因此用卡尔曼滤波的主要问题就是需要在车道拟合后获得曲线参数A协防差矩阵的准确估计。

对于放置在车辆侧边的摄像头,我们采用直线拟合即可:y=kx+b;

对于放置在车辆前面的摄像头,我们采用抛物线拟合:

由于情况1是情况2的特例,因此我们只做抛物线拟合。上文已做陈述,在图像坐标系中,图像抛物线可以由描述








高级车道线检测

阅读数 410

实现车道线检测

阅读数 201

没有更多推荐了,返回首页