精华内容
下载资源
问答
  • 车道线检测

    2014-04-23 14:14:27
    车道线检测
  • 车道线 检测

    热门讨论 2012-10-24 10:47:18
    利用matlab 对车道线进行检测 主函数为:lane.m
  • 针对车道线检测的任务,我们需要弄清楚几个问题:车道线的表示形式?输出类型:掩码/点集/矢量线条实例化:每个车道线是否形成实例分类:是否对车道线进行了分类(单白、双黄等)提前定义的参数:是否只能检测固定...

    针对车道线检测的任务,我们需要弄清楚几个问题:

    1. 车道线的表示形式?
    • 输出类型:掩码/点集/矢量线条
    • 实例化:每个车道线是否形成实例
    • 分类:是否对车道线进行了分类(单白、双黄等)
    • 提前定义的参数:是否只能检测固定数量的车道线
    • 车道标记:是否对车道上的行车标记也做了检测

    这会影响到数据的标注和网络的输出形式,而且最终需要的是车道线在世界坐标系下的方程。而神经网络更适合提取图像层面的特征,直接回归方程参数有较多限制。所以,网络推理输出后需要相对复杂的后处理去解决真实坐标的问题。

    2. 神经网络推理到哪一步?

    是逐像素的车道线标识本身 or 表征出抽象的车道分隔边界线

    • 图像分割方案倾向于像素点的分类,该像素点是否属于车道线标识并对标识类别进行判断;
    • 图像检测方案倾向于抽象出分割边界线,在设定的一系列anchor中判断是否存在车道线,以及回归车道线的位置参数。

    数据集

    1. 需要各场景类别的数据平衡,比如高速公路,辅路,盘山路,夜晚,雨天等数据
    2. 检查筛选出质量较好的图片,比如高速公路夜间的数据和雨中驾驶的视频较为模糊
    3. 相近的图片抽帧标注,可以每10张抽取1张,比如低速的多张图片相似会造成准确率虚高
    4. 增广小类别的图片,比如查看车道线系数直方图,再小幅度旋转使每个系数分布更为合理
    5. 缩放并归一化数据,加速收敛

    开源车道线数据集包括:

    Caltech:一共约1.2k张图片,场景比较简单,且视角较好;图片大小:640x480,如下图

    aa0e7de220f75d1cdc778479aaa24ec0.png

    VPGNet:一共20k张图片,包含白天(非雨天、雨天、大雨天)、夜晚的数据,同时包含了各种车道线类型,以及其他不同类型的车道标识(左转箭头、直行箭头、斑马线等等);

    TuSimple:一共72k张图片,位于高速路,天气晴朗,车道线清晰,特点是车道线以点来标注;图片大小:1280x720,如下图

    5af4f0c2e98222933d03269a930d0f94.png

    CULane:一共98k张图片,包含拥挤,黑夜,无线,暗影等八种难以检测的情况,最多标记4条车道线;图片大小:1640x590,如下图

    9637a125a4d23e63b87121de020b95b9.png

    BDD100k:120M张图片, 包含美国4个区的白天、黑夜各种天气状况,二维8类别车道线;图片大小:1280x720,如下图

    f23c88014f2e5f19924d83beb75307e0.png

    ApolloScape:140k张图片,特点是车道线以掩码的形式标注,包含2/3维28个类别;图片大小:3384x2710,如下图

    1774b5f1d1d38dda3b3d740fa3583edc.png

    CurveLanes:华为弯道检测数据集 135k张图片, 采用三次样条曲线手动标注所有车道线,包括很多较为复杂的场景,如S路、Y车道,还有夜间和多车道的场景。分为训练集10万张,验证集2万张,测试级3万张;图片大小:2650x1440,如下图

    75c623087158bc4e72e3a86c925d5f43.png

    传统图像方法

    通过边缘检测滤波等方式分割出车道线区域,然后结合霍夫变换、RANSAC等算法进行车道线检测。这类算法需要人工手动去调滤波算子,根据算法所针对的街道场景特点手动调节参数,工作量大且鲁棒性较差,当行车环境出现明显变化时,车道线的检测效果不佳。主流方式如下:

    1. 基于霍夫变换的车道线检测;
    2. 基于LSD直线的车道线检测;
    3. 基于俯视图变换的车道线检测;
    4. 基于拟合的车道线检测;
    5. 基于平行透视灭点的车道线检测;

    难点:

    1. 应用场景受限;霍夫直线检测方法准确但不能做弯道检测,拟合方法可以检测弯道但不稳定,仿射变换可以做多车道检测但在遮挡等情况下干扰严重。
    2. 透视变换操作会对相机有一些具体的要求,在变换前需要调正图像,而且摄像机的安装和道路本身的倾斜都会影响变换效果。

    c6cedce2a9bb6ece8b1562ec4d6f0853.png

    深度学习方法

    车道线检测的应用场景具有时序信息特性,为了利用时序特征通常会引入RNN模块,加上Encoder-Decoder的形式已经成为CNN特征提取的标配,所以一般的做法是对Encoder提取的Features进行进一步加工,提取连续帧带来的历史信息。或者借助一些额外的相关业务更好的引导车道线的回归。主流深度学习的车道线检测方法:包括二值语义分割产生掩码图部分和掩码图的线拟合部分。

    二值语义分割主要采用CNN方法并引入一些方式提高语义分割精度,在线的拟合阶段可以采用学习到的转换矩阵先将分割结果转换为鸟瞰图视角,然后,采用均匀取点+最小二乘法拟合,拟合方程可选三次方程。

    1.《Robust Lane Detection from Continuous Driving Scenes Using Deep Neural Networks》:采用CNN+RNN的方式,在Encoder和Decoder之间插入LSTM模块,对连续帧的输入预测二值分割图。

    2. 《Unifying Lane-Sensitive Architecture Search and Adaptive Point Blending》:采用CNN的方式,通过多尺度融合和输出的方式提高定位精度,最后采用一种类似于NMS方法,将低层输出中位置精度回归较高的点逐步向高层输出替换,得到最后融合优化的车道线点输出。

    3. 《Heatmap-based Vanishing Point boosts Lane Detection》:采用Encoder-Decoder结构,在车道线的预测Head以外,增加了一个Head用于消失点的预测;这种结构将特征提取阶段的输出和车道线预测的输出进行信息融合,再经过卷积层的处理后,输出消失点的预测结果。(类似VPGNet)

    4. 《Lane Detection Model Based on Spatio-Temporal Network with Double ConvGRUs》:采用Encoder+RNN+Decoder的方式,在Encoder和Decoder之间插入GRU模块,同样对连续帧的输入预测一张二值图。

    5.《RESA: Recurrent Feature-Shift Aggregator for Lane Detection》:采用Encoder-Decoder的方式,在Encoder和Decoder部分之间,插入RESA模块,增强空间结构信息在全局的传播能力。(类似SCNN)

    6. 《Real-Time LaneDtection Networks for Autonomous Driving》:采用Encoder-LSTM的方式,分割出车道线后采用聚类算法将不同的车道线进行区分,然后再通过一个HNet变换到鸟瞰视角去检测车道线。

    7. 《Key Points Estimation and Point Instance Segmentation Approach for Lane Detection》:PiNet算法将车道线用点表示转换成点的回归问题,然后使用聚类算法区分不同车道线上的点和去掉一部分多余的点。

    8. 《Ultra Fast Structure-aware Deep Lane Detection》:将车道线检测定义为寻找车道线在图像中某些行的位置的集合,即基于行方向上的位置选择、分类。

    区别于上述车道线检测和曲线拟合分开两步去做的方式,还有一种端到端的车道线拟合,输入图片,输出车道线曲线模型参数。比如:《End-to-end Lane Detection through Differentiable Least-Squares Fitting》

    性能指标

    在评判True or False时,主要有两种方式:

    • end point,通过判断线的端点间的距离及其包围面积是否超过阈值
    • IOU,直接计算IOU的重叠面积

    e0a89bf84a662b08f9ea3533d5ace5bd.png

    面临挑战

    (1)车道线这种细长的形态结构,需要更加强大的高低层次特征融合,来同时获取全局的空间结构关系,和细节处的定位精度。

    (2)车道线的形态有很多不确定性,比如被遮挡,磨损,以及道路变化时本身的不连续性。需要网络针对这些情况有较强的推测能力。

    (3)车辆的偏离或换道过程会产生自车所在车道的切换,车道线也会发生左/右线的切换。一些提前给车道线赋值固定序号的方法,在换道过程中会产生歧义的情况。

    展开全文
  • 前言第一次接触式车道线检测时尝试的实现,整理上传下思路1、提取原图边缘,可以看出车辆前方的车道线在整个图像下方的梯形区域,提取这个roi2、在roi区域进行轮廓查找,按照轮廓周长和面积过滤掉干扰项,最后应该...

    74998b0555c9cf21468c80be4c2eb240.png

    前言

    第一次接触式车道线检测时尝试的实现,整理上传下

    思路

    1、提取原图边缘,可以看出车辆前方的车道线在整个图像下方的梯形区域,提取这个roi

    2、在roi区域进行轮廓查找,按照轮廓周长和面积过滤掉干扰项,最后应该剩下两条分布在图像中线左右两侧的两条车道线

    3、对左右两条车道线点集做直线拟合,最后得出两条车道线的直线方程,进而可以在图上画出车道线的位置

    代码

    # !usr/bin/env python
    

    总结

    此方法对路面环境要求比较苛刻,并不实用,但是作为学习图像处理的常规思想还是大有裨益的,可以类推到一些简单的缺陷检测应用场景

    展开全文
  • Xavier车道线检测算法小试牛刀上次的模型经过tensorRT加速之后,速度提升非常明显,然后再将python实现的功能转为C++实现,又进一步提升帧率。后台有同学问我实现细节,所以这里我简单介绍一下这个车道线检测模型的...

    上个星期,拿到xavier之后,直接用python部署了pytorch模型小试了一把。出来的检测效果还凑合,但是速度贼慢,下面链接是上次发的视频。

        Xavier车道线检测算法小试牛刀

    上次的模型经过tensorRT加速之后,速度提升非常明显,然后再将python实现的功能转为C++实现,又进一步提升帧率。

    后台有同学问我实现细节,所以这里我简单介绍一下这个车道线检测模型的开发过程。

    一、车道线算法选型

    要求:保证检测精度的同时,在xavier平台上满足实时性

    可能满足要求的车道线检测算法

    1、LaneNet[1] 《Real-Time LaneDtection Networks for Autonomous Driving》 github链接[2]

    2、 SAD[3] 《Learning Lightweight Lane Detection CNNs by Self Atrention Distillation》 github链接[4]

    3、[PiNet]https://arxiv.org/abs/2002.06604()《Key Points Estimation and Point Instance Segmentation Approach for Lane Detection》 github链接[5]

    1、LaneNet的尝试

    9ddc81d3066d3bf9459f4938fd69aa0e.png

    LaneNet算法主要是分为两个阶段。第一个阶段是使用CNN做语义分割,得到分割图像。第二个阶段是在分割图像上做车道线检测。

    尝试过使用TuSimple的数据集训练LaneNet,在分割输出图像上,效果不错。

    但是在拿到分割图像之后,还需要进一步的去做车道线检测,原论文中给出的方法是先使用一个聚类算法将不同的车道线进行区分,然后再通过一个HNet变换到up-view视角去检测车道线。

    我的实验只尝试到分割网络这里就暂停了,因为到分割网络

    2bc48fc9ea1226d400811a0837914ee1.png

    这里的inference耗时已经不能满足实时性要求。后续内容还比较多。

    2、SAD

    主干网络使用ERFNet[6]使用分割方法做车道线检测。速度确实非常快,但是实际效果不是很好,程序运行起来之后,车道线会比较飘。所以就放弃了这个方法。

    3、PiNet

    PiNet算法的主要思想和LaneNe的分割思想不同。其主要是将车道线用点表示,很自然地将车道线检测问题转换为了点回归问题。一般而言,land

    9abc27c8df020d39918e69347361e1c6.png

    mark回归算法复杂度是要比segmentation算法复杂度要低的。

    PiNet算法也可以被分为两个阶段。第一个阶段是landmark回归,得到一系列的点。第二个阶段使用聚类算法区分不同车道线上的点和去掉一部分多余的点。

    255831e7aad08ed8844ea81da3bbccf2.png

    从论文中的数据来看,作者在TuSimple test数据集上进行测试,其精度是要比LaneNet精度高一些的。

    4656a367bd4f720c96a8166f3799af91.png

    但是在论文中,作者已经说了

    The test hardward is NVIDIA RTX 2080ti

    The proposed method can run about 30 frames per second without the post processing, and if the post processing isapplied, whole module works about 10 frames per second

    也就是说在2080Ti上运行cnn的inference,在没有后处理的情况下只能达到30FPS。在有后处理的情况下只能达到10FPS。后处理的时间比inference的时间还要多。

    通过阅读原作者的代码,所有的代码全部使用python完成,后处理的python代码只有功能实现,没有做计算优化处理。

    尝试直接在xavier运行python代码

    bfa36a41b84a1b2dece29dc986a50eeb.png

    得到结果只有2FPS

    通过打印每个阶段耗时和资源占用量发现。inference耗时150ms左右,后处理耗时300ms左右,然后GPU计算资源使用率不到15%

    尝试将pytorch模型转为libtorch模型使用C++部署

    在xavier编译pytorch源码,得到libtorch的动态库

    将模型通过tracing转为 Torch Script

    import torchimport torchvision# 模型实例model = PiNet()# forward的数据example = imgInput()# 通过tracing转换模型traced_script_module = torch.jit.trace(model, example)#保存转换之后的模型traced_script_module.save("traced_resnet_model.pt")

    转换完模型之后,使用c++代码直接部署

    测试耗时发现,inference耗时130ms左右,并没有明显的改善

    但是后处理部分使用c++重写python部分的代码之后,耗时可以降低到20ms以内

    所以这个时候的耗时瓶颈完全存在于forward上面了

    尝试使用pytorch转onnx再转TRT

    pytorch和版本和onnx的版本是绑定在一起的,固定版本的pytorch只能转换为固定版本的onnx。然后onnx版本和TRT版本也是有一定对应关系的。

    3ed9b52d601e637db9a7aef2502bcc3d.png

    由于xavier上TRT版本是5.0.6.3,所以在训练使用pytorch的版本选择上选择了pytorch1.0

    下图是pytorch模型输入

    bb40e9376bab7e8d669dd6523e8f5915.png

    下图是pytorch模型输出

    d2a45d14edfa6b4462031a4f3de529f7.png

    pytorch转onnx

    import torchimport cv2lane_agent.evaluate_mode()test_image = cv2.imread(test_image_file)test_image = cv2.resize(test_image, (512, 256)) / 255.0test_image = np.rollaxis(test_image, axis=2, start=0)test_image = np.array([test_image])result = lane_agent.predict_lanes_test(test_image)inputs = torch.from_numpy(test_image).float() inputs = Variable(inputs).cuda()onnx_file = 'net_onnx/model_pi.onnx'torch.onnx.export(lane_agent.lane_detection_network, inputs, onnx_file)

    onnx转tensorRT

    engine = ONNX_build_engine(onnx_file)with open(engine_file_path, "wb") as f:         f.write(engine.serialize())

    tensorRT转换测试

    # 通过onnx文件,构建TensorRT运行引擎context = engine.create_execution_context()#分配内存zeor_image = np.empty((1 , 3 , 256 , 512), dtype=np.float32)ravel = zeor_image.ravel().astype(np.float32)bindings = []it_size = ravel.dtype.itemsizeprint(it_size)d_input = cuda.mem_alloc(1 * ravel.size * ravel.dtype.itemsize)bindings.append(int(d_input))d_output_1431 = cuda.mem_alloc( 1 * 1 * 32 * 64 * ravel.dtype.itemsize )bindings.append(int(d_output_1431))d_output_1438 = cuda.mem_alloc( 1 * 2 * 32 * 64 * ravel.dtype.itemsize )bindings.append(int(d_output_1438))d_output_1445 = cuda.mem_alloc( 1 * 4 * 32 * 64 * ravel.dtype.itemsize )bindings.append(int(d_output_1445))d_output_1679 = cuda.mem_alloc( 1 * 1 * 32 * 64 * ravel.dtype.itemsize )bindings.append(int(d_output_1679))d_output_1686 = cuda.mem_alloc( 1 * 2 * 32 * 64 * ravel.dtype.itemsize )bindings.append(int(d_output_1686))d_output_1693 = cuda.mem_alloc( 1 * 4 * 32 * 64 * ravel.dtype.itemsize )bindings.append(int(d_output_1693))#读图片draw_image = cv2.resize(frame, (512, 256))#预处理test_image = cv2.resize(frame, (512, 256)) / 255.0test_image = np.rollaxis(test_image, axis=2, start=0)test_image = np.array([test_image])ravel = test_image.ravel().astype(np.float32)# pycuda操作缓冲区stream = cuda.Stream()# 将输入数据放入devicecuda.memcpy_htod_async(d_input, ravel, stream)# 执行模型context.execute_async(1, bindings, stream.handle, None)# 将预测结果从从缓冲区取出cuda.memcpy_dtoh_async(output_1431, d_output_1431, stream)cuda.memcpy_dtoh_async(output_1438, d_output_1438, stream)cuda.memcpy_dtoh_async(output_1445, d_output_1445, stream)cuda.memcpy_dtoh_async(output_1679, d_output_1679, stream)cuda.memcpy_dtoh_async(output_1686, d_output_1686, stream)cuda.memcpy_dtoh_async(output_1693, d_output_1693, stream)# 线程同步stream.synchronize()

    从pytorch转换完onnx之后,输入层和输出层的名字全部都是被数字所替代

    如下是onnx模型输入

    26f1e6c5b77f55b6b3795b368019cb15.png

    如下是onnx的模型输出,输出节点全部是使用数字代替

    5818aa598447aa5bad2b38c3fa9671f5.png

    最后使用转换的tensorRT模型部署工程

    模型inference时间大约在15ms左右

    eb7c7e76a75fa553a746c7a50f875749.png

    gpu资源占用从50%-75%之间浮动

    64cbd8a0539adf8c3ecf9187cb166dc4.png

    总结

    PiNet网路模型虽然小,但是直接部署的话,其inference时间却是很大的使用c++的API部署pytorch模型,解决不了根本性的时间问题xavier上使用tensorRT加速,效果非常明显,并且没有明显的精度下降pytorch模型转onnx,onnx转tensorRT,需要注意版本之间的关系

    References

    [1] LaneNet: *https://arxiv.org/abs/1802.05591*[2] github链接: *https://github.com/MaybeShewill-CV/lanenet-lane-detection*[3] SAD: [https://arxiv.org/pdf/1908.00821.pdf](https://links.jianshu.com/go?to=https%3A%2F%2Farxiv.org%2Fpdf%2F1908.00821.pdf)[4] github链接: https://github.com/cardwing/Codes-for-Lane-Detection[5] github链接: https://github.com/koyeongmin/PINet[6] ERFNet: https://github.com/cardwing/Codes-for-Lane-Detection/tree/master/ERFNet-CULane-PyTorch

    展开全文
  • 车道线检测-源码

    2021-02-11 04:17:10
    高级车道线检测
  • matlab车道线检测.zip

    2021-04-08 17:13:01
    matlab车道线检测.zipmatlab车道线检测.zip
  • 车道线检测 ····这里是车道线检测第二篇,睿智小编,在线码字。 ····车道线检测,从易到难:单直线车道检测–>单弯道车道检测–>多直线车道检测–>多弯道车道检测 ····我更希望用例子去说明,...

    车道线检测

    ····这里是车道线检测第二篇,睿智小编,在线码字。
    ····车道线检测,从易到难:单直线车道检测–>单弯道车道检测–>多直线车道检测–>多弯道车道检测
    ····我更希望用例子去说明,多按图说话。本篇分三个内容:

    ···· 1.讲解Udacity的CarND-LaneLines-P1-master项目
    ···· 2.讲解Udacity的CarND-Advanced-Lane-Lines-master项目
    ···· 3.讲解我在这基础上改进的multi-lane-lines-detection项目

    CarND-Advanced-Lane-Lines-master

    ····透视变换,在鸟瞰视角拟合车道线,效果很好但实时性较差的单车道线检测方法。
    详细代码见 https://github.com/wisdom-bob/CarND-Advanced-Lane-Lines-master
    在这里插入图片描述
    ····如图所示,为项目结果图,边缘拟合度高,并且车道两边几乎对称,符合弯道车道线检测结果,效果不错,但实时性较差,在无gpu下10.2帧/s,不过还是来看看这是怎么做的,如何改进,或者有哪些值得借鉴的地方。
    ····主要思路:对输入图像通过灰度化、去畸变、sobel算子在s-channel,gray图像上进行边缘检测、图像二值化;再框选出感兴趣区域,对感兴趣区域进行透视变换,基于滑动窗口法和统计直方图法得到车道线节点,拟合车道线节点,得到车道线,得到稳定输出结果。

    摄像头标定

    ····透视变换是图像数据处理的一个大招了,想想最让你头疼的不就是图像是正视吗,你硬是要基于二维图像去考虑三维空间的事情,透视变换一弄-------->二维下考虑二维的事情,应该会轻松很多吧!
    ····但是要使用透视变换,也不是那么容易的,我们需要摄像头内参才可以,这里就来说一下如何标定摄像头拿到摄像头内参。
    ····说到摄像头标定,就一定要提到张正友老师了,这里对于张正友标定法并不详细展开说,简而言之,基于实际点坐标和对标像素点位置,基于极大似然估计计算图像的内参,数据点越多结果越准。这里推荐几篇比较好的标定blog,见转载12,在1中简洁明了的对张正友方法进行推导说明,2中有一点错误,仔细读下来也能帮助你理解。

    在这里插入图片描述
    如上图所示为标定的图片,通过以下代码,基于cv2.findChessboardCorners找到对标像素点,这里的像素坐标精确到小数点后3位(ps,事实上这里并不是简单的图像捕捉点,也是统计得到的结果,精确度算不得准,只是当单张表格点较多时结果更可信),对标点对应的ground_truth point,即objp点集,相当于标定板不动,相机移动,由于标定板间隔相同,这里直接简单设定间距为单位1.

    # prepare object points, like (0,0,0), (1,0,0), (2,0,0) ....,(6,5,0)
    objp = np.zeros((6 * 9, 3), np.float32)
    objp[:,:2] = np.mgrid[0:9, 0:6].T.reshape(-1, 2)
    
    # Arrays to store object points and image points from all the images.
    objpoints = [] # 3d points in real world space
    imgpoints = [] # 2d points in image plane.
    img_with_corners = []
    # Make a list of calibration images
    images = glob.glob('./camera_cal/calibration*.jpg')
    
    # Step through the list and search for chessboard corners
    for i in range(len(images)):
        img = cv2.imread(images[i])
        gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)  
        ret, corners = cv2.findChessboardCorners(gray, (9,6), None)
        
        # If found, add object points, image points
        if ret == True:
            objpoints.append(objp)
            imgpoints.append(corners)
    
            # Draw and display the corners
            img = cv2.drawChessboardCorners(img, (9,6), corners, ret)
            img_with_corners.append(img)
    

    在这里插入图片描述
    如果算法能够准确检测到所有对标图像点,每张图像应都能画上如右图所示若干标记点,直线代表点位检测顺序。基于对标点集,就可以运用cv2.calibrateCamera计算摄像头内参矩阵和畸变矩阵等,当然也可以得到摄像头的外参(rvecs和tvecs),旋转向量和平移向量。

    # mtx is Camera Matrix, dist is the distort arameter Vector
    global mtx,dist
    
    # calculate the mtx and dist by cv2.calibrateCamera
    img = cv2.imread('camera_cal/calibration1.jpg')
    gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
    img_size = gray.shape[::-1]
    ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, img_size, None, None)
    

    /##################那些天趟过的坑##################/
    这里想说明一下标定的一些坑点,关于cv2.findChessboardCorners和cv2.calibrateCamera,与整体内容无关,不感兴趣的可以直接跳过偶。
    cv2.findchessboard,经过多次测试,对于仰视视角的图像检测结果较差如上图4,边界不完整检测效果也有影响如图5;这里的标定板是10x7,单张图片的标定点数54(9x6)个,最好不少于12个点,否则对标点精度会有影响;函数对于块格的间隔也有明确要求,检测点间最小距离>25像素;另外,如果明确感觉对标点精度不行,可以自行修改对标像素坐标,再跑cv2.calibrateCamera标定内参。
    cv2.calibrateCamera,基于objpoints和imgpoints计算结果,当然数据是越多越好,它会逐渐趋向稳定,这里推荐cv2.projectPoints配合筛选元图像数据。另外说一下cv2.calibrateCamera最末尾参数 criteria,用于设定迭代终止条件,算法依据最小二乘法,利用输入的对应点集进行循环计算,不断修缮结果,但偶尔也会碰到无法计算的情况,即使findchessboard没有剔除图像,但是对于标定计算的过程中,也有一些图像缺陷会被暴露出来需要剔除,这些都需要在调试中慢慢进行。
    总之,数据集是越大越好,至少有60张图像去筛选,最后可能得到20-30张有效图片,另外记得拍摄图像时要尽可能概括相机视野的每个地方,不要过于集中,防止权重失衡,导致内参标定差异。
    /##################那些天趟过的坑##################/

    以上只是个人见解,不喜勿喷,感谢

    基于标定内参,对图像进行畸变矫正,如下图所示。

    def cal_undistort(img):
        # convert image into gray scale
        undist = cv2.undistort(img, mtx, dist, None, mtx)
        return undist
    
    undist = cal_undistort(img)
        
    # Visualize undistortion
    f, (ax1, ax2) = plt.subplots(1, 2, figsize=(20,10))
    ax1.imshow(img)
    ax1.set_title('Original Image', fontsize=30)
    ax2.imshow(undist)
    ax2.set_title('Undistorted Image', fontsize=30)
    plt.subplots_adjust(left=0., right=1, top=0.9, bottom=0.)
    

    在这里插入图片描述
    到这里我们就完成了摄像头的标定,以上我们只用了摄像头畸变矫正,那要如何实现透视变换呢?让我们往下走吧!
    在这里插入图片描述

    思路讲解

    ····我们再次通过一帧图片来说明处理过程。
    在这里插入图片描述
    ····在畸变矫正后,为了减少处理的数据量和提高算法鲁棒性,二值化处理是少不了的。
    ····如上图所示为图像的RGB,HLS通道成像及在各自方向上做二值化特征提取的结果图,二值化的目的是希望尽可能保留道路特征,而其他干扰特征尽可能少,那么对比以上各托我们发现[X sobel on hls],[Y sobel on hls],[X sobel]都是不错的结果,于是这里考虑取[X sobel on hls]和[X sobel]交集,再与[R_channel]作为背景进行取交,得到一个更稳定的结果,从而完成二值化,这里使用的是cv2.Sobel,详细可见上篇文章中的转载[4],本文使用sobel算子(ps.canny算子中也同样通到了sobel算子)。结果如图所示。

    def rgb_select(img, thresh=(0, 255)):
    	#	get the binary image of r-channel
        R = img[:,:,0]
        binary = np.zeros_like(R)
        binary[(R > thresh[0]) & (R <= thresh[1])] = 1
        return binary
        
    def abs_sobel_thresh(img, orient='x', thresh=(0, 255)):
    	#	calculate the binary image by sobel operator with orient and thresh from grayscale
        gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
        if orient == 'x':
            sobel = cv2.Sobel(gray, cv2.CV_64F, 1, 0)
        else:
            sobel = cv2.Sobel(gray, cv2.CV_64F, 0, 1)
        abs_sobel = np.absolute(sobel)
        scaled_sobel = np.uint8(255 * abs_sobel / np.max(abs_sobel))
        binary_output = np.zeros_like(scaled_sobel)
        binary_output[(scaled_sobel > thresh[0]) & (scaled_sobel <= thresh[1])] = 1
        return binary_output
    
    def abs_sobel_thresh_hls(img, orient='x', thresh=(0, 255)):
    	#	calculate the binary image by sobel operator with orient and thresh from s-channel
        hls = cv2.cvtColor(img, cv2.COLOR_RGB2HLS)[:,:,2]
        if orient == 'x':
            sobel = cv2.Sobel(hls, cv2.CV_64F, 1, 0)
        else:
            sobel = cv2.Sobel(hls, cv2.CV_64F, 0, 1)
        abs_sobel = np.absolute(sobel)
        scaled_sobel = np.uint8(255 * abs_sobel / np.max(abs_sobel))
        binary_output = np.zeros_like(scaled_sobel)
        binary_output[(scaled_sobel > thresh[0]) & (scaled_sobel <= thresh[1])] = 1
        return binary_output
        
    def make_binary(img):
        # Threshold color channel
        r_binary = rgb_select(img, (220, 255))
        
        # Threshold based on sobel edge detection  
        sobel = abs_sobel_thresh(img, 'x', (40, 255))
        
        # Complex threshold
        c_binary = abs_sobel_thresh_hls(img, 'x', (50, 255))
        
        # Stack each channel
        color_binary = np.dstack((r_binary, sobel, c_binary)) * 255
        
        # Combine the two binary thresholds
        combined_binary = np.zeros_like(sobel)
        combined_binary[(c_binary == 1) | (sobel == 1) | (r_binary == 1)] = 1
        
        return (combined_binary, color_binary)
    
    undist = cal_undistort(img)
    (comb_bin, col_bin) = make_binary(undist)
    

    在这里插入图片描述
    在得到二值化图像后,此时图像特征明显,可以考虑进行透视变换了。
    在这里插入图片描述
    A3x3即我们需要求的变换矩阵M,这里我们需要把图像从透视视角转化为鸟瞰视角,首先需要计算出透视变换矩阵,那么可以直观的认为**(x,y,w)对应于(u,v,1)**,即w为单位1,那么我们只需要知道若干个对应点,即可求出A3x3,见下方代码,src为实际图像中的车道线选框点dst为对标矩形框坐标点,结果如图所示,关于透视变换的知识可见转载3,里面详细讲解的矩阵变换细节。

    def perspective_transform(img, M):  
    	# calculate the wrap image by perspective matrix
        warped = cv2.warpPerspective(img,M,(img_size),flags=cv2.INTER_LINEAR)
        return warped
    
    src = np.float32([[185, img_size[1]],[580, 460], [705, 460], [1200, img_size[1]]])
    dst = np.float32([[280, img_size[1]], [280, 0], [1000, 0], [1000, img_size[1]]])
    
    # calculate the perspective matrix
    M = cv2.getPerspectiveTransform(src, dst)
    M_inv = cv2.getPerspectiveTransform(dst, src)
    warped = perspective_transform(comb_bin,M)
    
    # Plot the result
    colored_comb_bin = np.dstack((comb_bin, comb_bin, comb_bin)) * 255
    cv2.polylines(colored_comb_bin, [np.array(src,dtype=np.int32).reshape((-1, 1, 2))], True, (255,255,0), thickness = 2)
    
    colored_warped = np.dstack((warped, warped, warped)) * 255
    cv2.polylines(colored_warped, [np.array(dst, dtype=np.int32).reshape((-1, 1, 2))], True, (255,255,0), thickness=2)
    

    在这里插入图片描述
    在此基础之上,通过直方图统计和滑动窗口法,采集车道线节点,再对节点进行拟合,得到平滑车道线。在图[warped]中,我们可以清晰看到,车道线特征为白色(色值1),其他为黑色(色值0),我们知道图像也是数据矩阵,大小为(720x1280x1),如果把关于横轴(0~1280)统计,那么我们可以得到一个(1x1280)数组向量,如下图所示,那么就可以确定车道线起点大致位置。
    在这里插入图片描述
    再基于滑动窗口法,每个窗口聚合所有(色值1)有效点,通过取平均值,得到特征点作为该窗口的车道线节点,再基于节点坐标向上滑移窗口,…,得到两条车道线的若干节点,基于节点拟合车道线如图所示。到这里,完成车道线检测。
    在这里插入图片描述

    def find_lane_pixels(binary_warped):
        # Take a histogram of the bottom half of the image
        histogram = np.sum(binary_warped[binary_warped.shape[0]//2:,:], axis=0)
        
        # Create an output image to draw on and visualize the result
        out_img = np.dstack((binary_warped, binary_warped, binary_warped))
    
        # 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
    
        # HYPERPARAMETERS
        nwindows = 10 # Choose the number of sliding windows
        margin = 80 # Set the width of the windows +/- margin
        minpix = 40 # Set minimum number of pixels found to recenter window
    
        # Set height of windows - based on nwindows above and image shape
        window_height = np.int(binary_warped.shape[0]//nwindows)
        # Identify the x and y positions of all nonzero pixels in the image
        nonzero = binary_warped.nonzero()
        nonzeroy = np.array(nonzero[0])
        nonzerox = np.array(nonzero[1])
     
        # Current positions to be updated later for each window in nwindows
        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 = binary_warped.shape[0] - ( window + 1) * window_height
            win_y_high = binary_warped.shape[0] - window * window_height
            
            ##Find the four below boundaries of the window
            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 found > minpix pixels, recenter next window
            # (`right` or `leftx_current`) 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 (previously was a list of lists of pixels)
        try:
            left_lane_inds = np.concatenate(left_lane_inds)
            right_lane_inds = np.concatenate(right_lane_inds)
        except ValueError:
            # Avoids an error if the above is not implemented fully
            pass
    
        # 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]
        
        return leftx, lefty, rightx, righty, out_img, left_lane_inds, right_lane_inds
    def fit_polynomial(binary_warped, vis, chose=1):
        # Find our lane pixels first
        leftx, lefty, rightx, righty, out_img, left_lane_inds, right_lane_inds = find_lane_pixels(binary_warped)
    
        left_fit, right_fit = (None, None)
        # Fit a second order polynomial to each
        if len(leftx) != 0:
            left_fit = np.polyfit(lefty, leftx, 2)
        if len(rightx) != 0:
            right_fit = np.polyfit(righty, rightx, 2)
    
        # Generate x and y values for plotting
        ploty = np.linspace(0, binary_warped.shape[0]-1, binary_warped.shape[0] )
        try:
            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]
        except TypeError:
            # Avoids an error if `left` and `right_fit` are still none or incorrect
            left_fitx = 1*ploty**2 + 1*ploty
            right_fitx = 1*ploty**2 + 1*ploty
    
        ## Visualization ##
        if vis:
            # Colors in the left and right lane regions
            out_img[lefty, leftx] = [255, 0, 0]
            out_img[righty, rightx] = [0, 0, 255]
    
            # Plots the left and right polynomials on the lane lines
            plt.figure(figsize=(15, 15))
            plt.plot(left_fitx, ploty, color='yellow')
            plt.plot(right_fitx, ploty, color='yellow')
            plt.imshow(out_img)
    
        if chose == 1:
            return (ploty, left_fit, right_fit, left_fitx, right_fitx)
        else:
            return left_fit, right_fit, left_lane_inds, right_lane_inds
    
    (ploty, left_fit, right_fit, left_fitx, right_fitx) = fit_polynomial(warped, True)
    

    总结和拓展

    ····该方法的泛化能力更强,可以针对直道、弯道检测,若不作为实时车道线检测,那么用于作为辅助车道偏移,巡航都是不错的选择。
    ····从优化角度考虑,效果还不够稳定,偶尔由于光影影响,容易出现错帧,可考虑加上平滑器,效果可以提高一些;此外图像上可考虑加上黑白阶调节,减少光影影响,此外可考虑左右两车道线相互联系,再与平滑器比对,提高车道线检测稳定性,但关键缺陷还是在帧率上。
    ····通过测试,在时间占比上:二值化41%,去畸变22.7%,绘制19.7%,拟合车道线13%,透视变换5%…。可以考虑去掉r-channel,只考虑[X sobel on hls]和[X sobel],或者换种二值化方法;此外绘制也可以简化,从而提高帧率。
    ····快去试试吧~~

    如有侵权,请私戳~~感谢。


    1. https://blog.csdn.net/u010128736/article/details/52860364 ↩︎

    2. https://blog.csdn.net/lxy_2011/article/details/80675803 ↩︎

    3. https://blog.csdn.net/xiaowei_cqu/article/details/26471527 ↩︎

    展开全文
  • 车道线检测 ····在参加udacity的课程后,对于车道线有了一些直观的认识,后来在实验室学习和外出实习过程中,也接触过不少车道线方面的检测工作,这边将对这些日子里的工作做一个总结。 ····这也是我写的第...
  • matlab车道线检测

    2010-06-30 23:38:27
    车道线检测车道线检测车道线检测车道线检测车道线检测车道线检测车道线检测车道线检测车道线检测车道线检测车道线检测车道线检测车道线检测车道线检测车道线检测车道线检测车道线检测车道线检测车道线检测车道线检测...
  • 车道线检测国外现状

    2018-06-23 22:20:26
    车道线检测深度学习车道线检测深度学习深度学习车道线间测
  • opencv车道线检测

    2020-11-23 10:35:01
    基于Visual Studio 2015,并进行Qt配置Opencv,实现对视频中基于道路特征的 车道线检测方法。

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,329
精华内容 531
关键字:

车道线检测