OpenCV学习笔记--以车道线检测入门

news2024/9/25 3:21:24

本笔记gitee源代码:
https://gitee.com/hongtao-jiang/opencv_lanedetect.git

2023.8.5

文章目录

    • 1、OpenCV安装
    • 2、图片的读入、保存
    • 3、Canny算法边缘检测
    • 4、ROI mask
    • 5、霍夫变换
    • 6、离群值过滤
    • 7、最小二乘拟合
    • 8、直线绘制
    • 9、视频流读写

1、OpenCV安装

conda管理虚拟环境与否看自己
pip install opencv

import cv2
print(cv2.__version__)

查看该库是否安装成功

2、图片的读入、保存

import cv2

img=cv2.imread('img.jpg',cv2.IMREAD_GRAYSCALE) #此为以灰度图格式读入,彩色读入则shape是3通道
print(type(img)) #<class 'numpy.ndarray'>
print(img.shape) #(368, 640)

cv2.imshow('image',img)#一闪而逝
#cv2.waitKey(0)#延时阻塞 0一直阻直到键入  单位毫秒
#k=cv2.waitKey(0)
#print(k)  #输出键入的ASCII值

cv2.imwrite('img_gray.jpg',img)#灰度图重组到文件里,图片要指定后缀,cv以此确定压缩格式

3、Canny算法边缘检测

在这里插入图片描述
求取每个像素点周边梯度,对比变化,以确定是否为边缘
对于B,C点,不处于边缘,求梯度无明显变化,但是处在边缘的A来说,梯度变化会很大,就可能是边缘点
以上是理想例子,沿着边缘法向求梯度求,实际上情况更复杂,例如:
在这里插入图片描述
1是左右边缘,3上下左右都有,360°计算运算成本高,Canny算法则是选取4个梯度方向(有正负所以说4个),
(1)应用高斯滤波器,平滑图像,滤除噪声。
边缘检测易受噪声影响,所以平滑图像,降低噪声点
(2)计算图像每个像素点的梯度大小和方向。
在这里插入图片描述
在这里插入图片描述
(3)非极大值抑制,消除边缘检测带来的不利影响
遍历图像中所有的像素点,判断当前像素点是否是周围像素点中具有相同方向梯度的最大值
在这里插入图片描述
保留黄色背景像素点,其他的归0

(4)应用双阈值检测确定真实和潜在的边缘
在这里插入图片描述
在这里插入图片描述先设置高、低两个阈值(一般高阈值是低阈值的2~3倍),遍历整个灰度矩阵,若某点的梯度高于高阈值,则在结果中置1,若该点的梯度值低于低阈值,则在结果中置0,若该点的梯度值介于高低阈值之间,则需要进行如下判断:检查该点(将其视为中心点)的8邻域点,看是否存在梯度值高于高阈值的点,若存在,则说明该中心点和确定的边缘点相连接,故在结果中置1,否则置0。

(5)抑制孤立的弱边缘完成边缘检测

双阈值检测确定真是和潜在的边缘

原图:在这里插入图片描述
边缘轮廓:
在这里插入图片描述
阈值:edge_img=cv2.Canny(img,50,100)
在这里插入图片描述
阈值:edge_img=cv2.Canny(img,70,120)
相比前面面的图,边缘线少了

import cv2

img=cv2.imread('img.jpg',cv2.IMREAD_GRAYSCALE)

edge_img=cv2.Canny(img,70,120)

cv2.imshow('edges',edge_img)
cv2.waitKey(0)
cv2.imwrite('edge_img.jpg',edge_img)

附一段可动态调节阈值的实时检测效果代码:

import cv2

cv2.namedWindow('edge_detection')
cv2.createTrackbar('minThreshold','edge_detection',50,1000,lambda x: x)
cv2.createTrackbar('maxThreshold','edge_detection',100,1000,lambda x: x)

img=cv2.imread('img.jpg',cv2.IMREAD_GRAYSCALE)
while True:
    minThreshold=cv2.getTrackbarPos('minThreshold', 'edge_detection')
    maxThreshold = cv2.getTrackbarPos('maxThreshold', 'edge_detection')
    edges = cv2.Canny(img, minThreshold, maxThreshold)
    cv2.imshow('edge_detection', edges)
    cv2.waitKey(10)

效果如下:
在这里插入图片描述
用以选择合适阈值

4、ROI mask

region of interest 感兴趣区域

· 数组切片
· 布尔运算(AND与运算)

在这里插入图片描述
原图以矩阵np.array形式存储在内存
zeros_like生成一个大小一致的全0矩阵
fillpoly填充掩码部分,留作与操作

cv2.fillPoly(mask,np.array([[[0,368],[240,210],[300,210],[640,368]]]),color=255)

这部分是将四个顶点包住的梯形填充,255为灰度值,纯白色

import cv2
import numpy as np

edge_img=cv2.imread('edge_img.jpg',cv2.IMREAD_GRAYSCALE)

mask=np.zeros_like(edge_img)
cv2.fillPoly(mask,np.array([[[0,368],[240,210],[300,210],[640,368]]]),color=255)
cv2.imshow('mask',mask)
cv2.waitKey(0)

在这里插入图片描述

import cv2
import numpy as np

edge_img=cv2.imread('edge_img.jpg',cv2.IMREAD_GRAYSCALE)

mask=np.zeros_like(edge_img)
mask=cv2.fillPoly(mask,np.array([[[0,368],[240,210],[300,210],[640,368]]]),color=255)
#cv2.imshow('mask',mask)
#cv2.waitKey(0)
mask_edge_img=cv2.bitwise_and(edge_img,mask)
cv2.imshow('mask_edge_img.jpg',mask_edge_img)
#cv2.imwrite('mask_edge_img.jpg',mask_edge_img)
cv2.waitKey(0)

在这里插入图片描述
有点偏,多了杂点,往右挪一挪

np.array([[[0, 368], [300, 210], [340, 210], [640, 368]]]),
                    color=255)

在这里插入图片描述

5、霍夫变换

找直线用
在这里插入图片描述
笛卡尔坐标系中的一条线,在霍夫空间中是个点;
霍夫空间中的一条线,代表笛卡尔系中所有经过某点的直线;

在这里插入图片描述

在这里插入图片描述
!!!
所以,经过这样一个对应变换的思路,霍夫空间中的一个点作为参数可以确定笛卡尔空间中多数点确定的一条直线
在这里插入图片描述
在这里插入图片描述
右图是极坐标系对应霍夫空间交线图

在这里插入图片描述
在这里插入图片描述

如图,两条直线,右图两个两点,说明相交次数多,根据这两个点的坐标参数,确定直线

api:
返回值是一个列表,代表线段起点和终点的坐标值
在这里插入图片描述
opencv中霍夫变换要在灰度图中进行,读取图片必须注意格式
在这里插入图片描述

在控制台逐行输入

import cv2

import numpy as np

img=cv2.imread('lines.jpg',cv2.IMREAD_GRAYSCALE)

lines = cv2.HoughLinesP(img, 1, np.pi / 180, 15, minLineLength=40,
                        maxLineGap=20)

lines
len(lines)

我们直观感受到的2条线,发现输出会有32条直线
在这里插入图片描述
在这里插入图片描述
其实是因为我们所处理的原图,直线有宽度,粗了
看参数,其中有很多线起点终点坐标相差不大,就是一条线段

为了先验获得两条,我们得进行分类,按斜率分为两组

import cv2
import numpy as np


def calculate_slope(line):
    """
    计算线段line的斜率
    :param line: np.array([[x_1, y_1, x_2, y_2]])
    :return:
    """
    x_1, y_1, x_2, y_2 = line[0]
    return (y_2 - y_1) / (x_2 - x_1)


edge_img = cv2.imread('mask_edge_img.jpg', cv2.IMREAD_GRAYSCALE)
# 获取所有线段
lines = cv2.HoughLinesP(edge_img, 1, np.pi / 180, 15, minLineLength=40,
                        maxLineGap=20)
# 按照斜率正负分成车道线
left_lines = [line for line in lines if calculate_slope(line) > 0]
right_lines = [line for line in lines if calculate_slope(line) < 0]

注意,opencv中,坐标原点在左上角

在这里插入图片描述
在这里插入图片描述

6、离群值过滤

上节根据斜率分为左右两组线,但实际因为误差,噪点,会误识别为车道线,我们要进一步剔除一部分,我们知道,落在车道线上的线占大多数,与之斜率差别大的我们过滤掉

import cv2
import numpy as np

def calculate_slope(line):
    """
    计算线段line的斜率
    :param line: np.array([[x_1, y_1, x_2, y_2]])
    :return:
    """
    x_1, y_1, x_2, y_2 = line[0]
    return (y_2 - y_1) / (x_2 - x_1)

edge_img = cv2.imread('mask_edge_img.jpg', cv2.IMREAD_GRAYSCALE)
# 获取所有线段
lines = cv2.HoughLinesP(edge_img, 1, np.pi / 180, 15, minLineLength=40, maxLineGap=20)
# 按照斜率分成车道线
left_lines = [line for line in lines if calculate_slope(line) > 0]
right_lines = [line for line in lines if calculate_slope(line) < 0]

def reject_abnormal_lines(lines, threshold):
    """
    剔除斜率不一致的线段
    :param lines: 线段集合, [np.array([[x_1, y_1, x_2, y_2]]),np.array([[x_1, y_1, x_2, y_2]]),...,np.array([[x_1, y_1, x_2, y_2]])]
    """
    slopes = [calculate_slope(line) for line in lines]#建个斜率列表
    while len(lines) > 0:
        mean = np.mean(slopes)#计算斜率平均值
        diff = [abs(s - mean) for s in slopes]#计算每个斜率和平均值的差值
        idx = np.argmax(diff)# 获取array中数值最大的 找差值最大线段的索引
        if diff[idx] > threshold:#和阈值比一下,差得多不,超标就滚蛋
            slopes.pop(idx)#斜率列表中删掉
            lines.pop(idx)#从线段列表中删掉
        else:
            break
    return lines


print('before filter:')
print('left lines number=')
print(len(left_lines))
print('right lines number=')
print(len(right_lines))

reject_abnormal_lines(left_lines, threshold=0.2)
reject_abnormal_lines(right_lines, threshold=0.2)


print('after filter:')
print('left lines number=')
print(len(left_lines))
print('right lines number=')
print(len(right_lines))

在这里插入图片描述

7、最小二乘拟合

将所有认为是左或右车道的线拟合为一条

np.ravel :将高维数组拉成一维数组
np.polyfit: 多项式拟合
在这里插入图片描述

np.polyval:多项式求值,第一个参数是多项式系数,第二参数是任意X

def least_squares_fit(lines):
    """
    将lines中的线段拟合成一条线段
    :param lines: 线段集合, [np.array([[x_1, y_1, x_2, y_2]]),np.array([[x_1, y_1, x_2, y_2]]),...,np.array([[x_1, y_1, x_2, y_2]])]
    :return: 线段上的两点,np.array([[xmin, ymin], [xmax, ymax]])
    """
    # 1. 取出所有坐标点
    x_coords = np.ravel([[line[0][0], line[0][2]] for line in lines])
    y_coords = np.ravel([[line[0][1], line[0][3]] for line in lines])
    # 2. 进行直线拟合.得到多项式系数
    poly = np.polyfit(x_coords, y_coords, deg=1)
    # 3. 根据多项式系数,计算两个直线上的点,用于唯一确定这条直线
    point_min = (np.min(x_coords), np.polyval(poly, np.min(x_coords)))
    point_max = (np.max(x_coords), np.polyval(poly, np.max(x_coords)))
    return np.array([point_min, point_max], dtype=int)


print("left lane")
print(least_squares_fit(left_lines))
print("right lane")
print(least_squares_fit(right_lines))

在上节代码后加入这段处理
在这里插入图片描述
两点练成一条线

8、直线绘制

cv2.line
添加如下代码

left_line = least_squares_fit(left_lines)
right_line = least_squares_fit(right_lines)

img = cv2.imread('img.jpg', cv2.IMREAD_COLOR)
cv2.line(img, tuple(left_line[0]), tuple(left_line[1]), color=(0, 255, 255), thickness=5)
cv2.line(img, tuple(right_line[0]), tuple(right_line[1]), color=(0, 255, 255), thickness=5)

cv2.imshow('lane', img)
cv2.waitKey(0)

在这里插入图片描述
为什么左侧车道线肉眼可见的偏移大?
因为在Canny边缘检测阈值调整,和前面ROI mask处掩码区域没有设置好,如下图,左车道线处有车辆的边缘,我没有细心调节阈值,后面掩码区域也没有掩盖掉
在这里插入图片描述
将梯形块右移,得到新的图取参与绘图:
在这里插入图片描述
所以调整需要看情况

9、视频流读写

在这里插入图片描述

capture = cv2.VideoCapture('video.mp4')
while True:
    ret, frame = capture.read()#ret视频流状况,是否关闭
    frame = show_lane(frame)
    cv2.imshow('frame', frame)
    cv2.waitKey(100)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/838986.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何用限制立方样条(RCS)做生存分析?

一、引言 在医学和统计学领域&#xff0c;生存分析是一种分析个体生命长度和生存时间的重要方法。了解人们生存的期限和影响因素&#xff0c;对于制定健康政策、优化医疗资源的分配以及个体护理方案的制定都至关重要。传统的生存分析方法如Kaplan-Meier曲线和Cox比例风险模型已…

XXL-JOB定时任务框架(Oracle定制版)

特点 xxl-job是一个轻量级、易扩展的分布式任务调度平台&#xff0c;能够快速开发和简单学习。开放源代码并被多家公司线上产品使用&#xff0c;开箱即用。尽管其确实非常好用&#xff0c;但我在工作中使用的是Oracle数据库&#xff0c;因为xxl-job是针对MySQL设计的&#xff…

北邮22信通:第五章 图 最短路径 Dijkstra算法

北邮22信通一枚~ 跟随课程进度每周更新数据结构与算法的代码和文章 持续关注作者 解锁更多邮苑信通专属代码~ 获取更多文章 请访问专栏&#xff1a; 北邮22信通_青山如墨雨如画的博客-CSDN博客 一. 算法核心思想 Dijkstra算法是用来求取图中两个结点之间最短路径的算…

mysql大表的深度分页慢sql案例(跳页分页)

1 背景 有一张表&#xff0c;内容是 redis缓存中的key信息&#xff0c;数据量约1000万级&#xff0c; expiry列上有一个普通B树索引。 -- test.top definitionCREATE TABLE top (database int(11) DEFAULT NULL,type varchar(50) DEFAULT NULL,key varchar(500) DEFAULT NUL…

java+ssm校园高校足球联赛管理系统tdl2g

随着计算机技术发展&#xff0c;计算机系统的应用已延伸到社会的各个领域&#xff0c;大量基于网络的广泛应用给生活带来了十分的便利。所以把足球联赛管理与现在网络相结合&#xff0c;利用计算机搭建足球联赛管理系统&#xff0c;实现足球联赛管理系统的信息化。则对于进一步…

直线导轨在视觉检测设备中的应用

随着科技的不断发展&#xff0c;视觉检测设备已经逐渐代替了传统的人工品检&#xff0c;成为了工业生产中的一部分&#xff0c;在五金配件、塑胶件、橡胶件、电子配件等检测工业零部件表面外观缺陷尺寸方面应用&#xff0c;视觉检测设备具有优势。 直线导轨作为视觉检测设备中重…

EVE-NG MPLS L2VPN static lsp

目录 1 拓扑 2 配置步骤 2.1 配置接口IP 和路由协议 2.2 配置MPLS LDP 2.3 配置L2VPN PW 2.4 验证L2VPN 1 拓扑 2 配置步骤 2.1 配置接口IP 和路由协议 PE1 interface LoopBack 0ip address 1.1.1.9 32 quitinterface GigabitEthernet1/0ip address 10.1.1.1 255.255…

【数理知识】求刚体旋转矩阵和平移矩阵,已知 N>=3 个点在前后时刻的坐标,且这 N>=3 点间距离始终不变代表一个刚体

序号内容1【数理知识】自由度 degree of freedom 及自由度的计算方法2【数理知识】刚体 rigid body 及刚体的运动3【数理知识】刚体基本运动&#xff0c;平动&#xff0c;转动4【数理知识】向量数乘&#xff0c;内积&#xff0c;外积&#xff0c;matlab代码实现5【数理知识】协…

【深度学习_TensorFlow】感知机、全连接层、神经网络

写在前面 感知机、全连接层、神经网络是什么意思&#xff1f; 感知机&#xff1a; 是最简单的神经网络结构&#xff0c;可以对线性可分的数据进行分类。 全连接层&#xff1a; 是神经网络中的一种层结构&#xff0c;每个神经元与上一层的所有神经元相连接,实现全连接。 神经…

kibana-7.17.3版本安装及汉化

1、官网下载地址&#xff1a;https://www.elastic.co/cn/downloads/kibana 选择安装系统类型和历史版本kibana安装版本要和es版本对应 2、上传安装包然后解压 tar -zxf kibana-7.17.3-linux-x86_64.tar.gz 3、更改目录属主 chown elk. kibana-7.17.3-linux-x86_64 -R …

C语言笔试训练【第三天】

大家好&#xff0c;我是纪宁。 今天是C语言笔试训练的第三天&#xff0c;大家加油&#xff01; 第一题 1、已知函数的原型是&#xff1a; int fun(char b[10], int *a) &#xff0c;设定义&#xff1a; char c[10];int d; &#xff0c;正确的调用语句是&#xff08; &#xf…

基于Mediapipe的姿势识别并同步到Unity人体模型中

如题&#xff0c;由于是商业项目&#xff0c;无法公开源码&#xff0c;这里主要说一下实现此功能的思路。 人体关节点识别 基于Mediapipe Unity插件进行开发&#xff0c;性能比较低的CPU主机&#xff0c;无法流畅地运行Mediapipe&#xff0c;这个要注意一下。 Mediapipe33个人体…

STM32F103——基础篇

目录 1、寄存器基础知识 2、STM32F103系统架构 2.1 Cortex M3 内核&芯片 2.2 STM32F103系统架构 3、存储器映射 4、寄存器映射 4.1 寄存器描述解读 4.2 寄存器映射举例 4.3 寄存器地址计算 4.4 stm32f103xe.h 寄存器映射 1、寄存器基础知识 概念&#xff1a;寄存…

【C语言进阶】指针的高级应用(上)

本专栏介绍&#xff1a;免费专栏&#xff0c;并且会持续更新C语言基础知识&#xff0c;欢迎各位订阅关注。 关注我&#xff0c;带你了解更多关于机器人、嵌入式、人工智能等方面的优质文章&#xff0c;坚持更新&#xff01; 大家的支持才是更新的最强动力&#xff01; 文章目录…

详解PHP反射API

PHP中的反射API就像Java中的java.lang.reflect包一样。它由一系列可以分析属性、方法和类的内置类组成。它在某些方面和对象函数相似&#xff0c;比如get_class_vars()&#xff0c;但是更加灵活&#xff0c;而且可以提供更多信息。反射API也可与PHP最新的面向对象特性一起工作&…

掌握 JVM 的参数及配置

点击下方关注我&#xff0c;然后右上角点击...“设为星标”&#xff0c;就能第一时间收到更新推送啦~~~ JVM&#xff08;Java虚拟机&#xff09;是Java编程语言的核心组件之一&#xff0c;它负责执行Java程序&#xff0c;并提供一系列参数和配置选项&#xff0c;可以调整Java程…

探秘企业DevOps一体化平台建设终极形态丨IDCF

笔者从事为企业提供研发效能改进解决方案相关工作十几年&#xff0c;为国内上百家企业提供过DevOps咨询及解决方案落地解决方案&#xff0c;涉及行业包括&#xff1a;金融、通信、制造、互联网、快销等多种行业。 DevOps的核心是研发效能改进&#xff0c;效能的提升离不开强大…

Linux基本开发工具(一)

文章目录 Linux基本开发工具&#xff08;一&#xff09;Linux安装和卸载软件Linux 软件包管理器 yum关于sudo命令关于yum源的换源问题 vim编辑器的使用vim三种模式&#xff08;常见&#xff09;vim的基本操作vim配置 Linux基本开发工具&#xff08;一&#xff09; Linux安装和…

Dubbo中使用netty

技术主题 netty在Dubbo中的使用,主要集中在网络通信上, 技术原理 Dubbo是什么 高性能、轻量级的开源java的RPC框架,提供三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。 Dubbo的传输结构 1、魔数标识符(四个字节),用于区分不同…

机器人开发--兴颂雷达介绍

机器人开发--兴颂雷达介绍 1 介绍2 使用手册参考 1 介绍 佛山市兴颂机器人科技有限公司&#xff08;Hinson&#xff09;是一家集研发、设计、生产、销售机器人(AGV)导航核心零部件、并提供整体运动控制方案的自主创新型国家高新技术企业。 2 使用手册 兴颂激光雷达使…