视觉知觉使用单眼照相机
该实例说明了如何构造一个能实现车道边界和车辆检测的单目相机传感器仿真。传感器将在车辆坐标系中报告这些检测。在本
例中, 您将了解自动驱动系统 Toolbox™所使用的坐标系, 以及在设计单目相机传感器时所涉及的计算机视觉技术。
概述
包含绮特性或设计为完全自主的车辆依赖于多个传感器。这些传感器可以包括声纳、雷达、激光雷达和照相机。这个例子说明了在单眼摄像系统的设计中所涉及的一些概
念。这样的传感器可以完成许多任务, 包括:
•
•
•
车道边界检测
车辆、人员和其他物体的检测
从自我车辆到障碍物的距离估计
随后, 由单目相机传感器返回的读数可用于发出车道偏离警告、碰撞警告或设计车道保持辅助控制系统。与其他传感器一起, 它也可用于实施紧急制动系统和其他安全关键功
能。
该示例实现了在完全开发的单目相机系统上发现的功能子集。它检测车道边界和车辆的背部, 并报告他们的位置在车辆坐标系统。
定义摄像机配置
了解相机的内在和外部校准参数是关键的准确转换像素和车辆的坐标。
首先定义摄像机的内部参数。下面的参数是通过使用棋盘式校准模式的摄像机校准程序较早确定的。您可以使用cameraCalibrator应用程序为您的照相机获取它们。
focalLength = [309.4362, 344.2161]; % [fx, fy] in pixel units
principalPoint = [318.9034, 257.5352]; % [cx, cy] optical center in pixel coordinates
imageSize = [480, 640]; % [nrows, mcols]
注意, 透镜畸变系数被忽略, 因为数据中的失真很小。这些参数存储在 cameraIntrinsics 对象中。
camIntrinsics = cameraIntrinsics(focalLength, principalPoint, imageSize);
接下来, 定义有关车辆底盘的摄像机方向。您将使用此信息建立摄像机 extrinsics, 用于定义3维摄像机坐标系在车辆坐标系中的位置。
height = 2.1798; % mounting height in meters from the ground
pitch = 14; % pitch of the camera in degrees
上述数量可以从 extrinsics 函数返回的旋转和平移矩阵派生。音高指定相机从水平位置倾斜。对于本例中使用的摄像机, 传感器的滚动和偏航都是零。定义内部和
extrinsics 的整个配置存储在 monoCamera 对象中。
sensor = monoCamera(camIntrinsics, height, 'Pitch', pitch);
注意, 该对象设置了一个非常特定的车辆坐标系, 其中 X 轴指向车辆, Y 轴指向车辆左侧。monoCamera
坐标系的原点在地面上, 直接位于摄像机中心点的下方。此外, monoCamera 提供 imageToVehicle 和 vehicleToImage 方法, 用于在图像和车辆坐标系之间进行转换。
注意: 坐标系统之间的转换假定为平坦的道路。它基于建立一个应矩阵, 将成像平面上的位置映射到路面上的位置。Nonflat 路在距离计算中引入错误, 特别是在远离车辆的地
方。
加载视频帧
在处理整个视频之前, 处理单个视频帧, 以说明单目相机传感器的设计所涉及的概念。
首先创建一个打开视频文件的 VideoReader 对象。为了节省内存, 一次加载一个视频帧。VideoReader
videoName = 'caltech_cordova1.avi';
videoReader = VideoReader(videoName);
阅读一个有趣的框架, 其中包含车道标记和车辆。
timeStamp = 0.06667;
videoReader.CurrentTime = timeStamp; % point to the chosen frame
% time from the beginning of the video
frame = readFrame(videoReader); % read frame at timeStamp seconds
imshow(frame) % display frame
注意: 本示例忽略镜头失真。如果您担心镜头失真引入的距离测量错误, 此时您将使用 undistortImage 函数来删除镜头失真。
创建鸟瞰图图像
有许多方法来分割和检测车道标记。一种方法是使用鸟瞰图像变换。虽然它会招致计算成本, 但这一转换提供了一个主要优势。鸟瞰图中的车道标记具有均匀的厚度, 从而简
化了分割过程。属于同一车道的车道标记也变得平行, 从而使进一步的分析更加容易。
给定摄像机设置, birdsEyeView 对象将原始图像转换为鸟瞰图。它允许您指定要使用车辆坐标进行转换的区域。请注意, 当摄像机的安装高度在米中指定时, 车辆坐标单位是
由 monoCamera 对象建立的。例如, 如果高度以毫米为单位指定, 则模拟的其余部分将使用毫米。
% Using vehicle coordinates, define area to transform
distAheadOfSensor = 30; % in meters, as previously specified in monoCamera height input
spaceToOneSide = 6; % all other distance quantities are also in meters
bottomOffset = 3;
outView = [bottomOffset, distAheadOfSensor, spaceToOneSide, spaceToOneSide]; % [xmin, xmax, ymin, ymax]
imageSize = [NaN, 250]; % output image width in pixels; height is chosen automatically to preserve units per pixel ratio
birdsEyeConfig = birdsEyeView(sensor, outView, imageSize);
生成鸟瞰图。
birdsEyeImage = transformImage(birdsEyeConfig, frame);
figure
imshow(birdsEyeImage)
远离传感器的区域更模糊, 因为像素较少, 因此需要更大的插值量。
请注意, 您可以在不使用鸟瞰图的情况下完成后一个处理步骤, 只要可以在车辆坐标中找到车道边界候选像素。
查找车辆坐标中的车道标记
有了鸟瞰图像, 现在可以使用 segmentLaneMarkerRidge 函数将车道标记候选像素与路面分开。该技术因其简单性和相对有效性而被选择。可选的分割技术包括语义分割
(深度学习) 和可调滤波器。您可以用下面这些技术来获取下一阶段所需的二进制掩码。
下面的函数的大多数输入参数是在世界单位中指定的, 例如, 车道标记宽度送入。使用世界单位可以方便地尝试新的传感器, 即使输入图像大小发生变化。这是非常重要的, 使
设计更加稳健和灵活的方面, 改变相机的硬件和处理不同的标准, 在许多国家。segmentLaneMarkerRidge
% Convert to grayscale
birdsEyeImage = rgb2gray(birdsEyeImage);
% Lane marker segmentation ROI in world units
vehicleROI = outView [1, 2, 3, 3]; % look 3 meters to left and right, and 4 meters ahead of the sensor
approxLaneMarkerWidthVehicle = 0.25; % 25 centimeters
% Detect lane features
laneSensitivity = 0.25;
birdsEyeViewBW = segmentLaneMarkerRidge(birdsEyeImage, birdsEyeConfig, approxLaneMarkerWidthVehicle,...
'ROI', vehicleROI, 'Sensitivity', laneSensitivity);
figure
imshow(birdsEyeViewBW)
定位于摄像机传感器的车辆坐标中, 查找单独的车道标记。这个例子使用抛物线车道边界模型, ax^2 + c, 代表车道制造商。其他表示法, 如三度多项式或样条, 是可能的。转
换到车辆的坐标是必要的, 否则车道标志曲率不能正确地代表一个抛物线, 而它受透视失真的影响。
车道模型持有车道标记沿车辆的路径。车道标记横跨道路或路标在沥青被绘被拒绝。
% Obtain lane candidate points in vehicle coordinates
[imageX, imageY] = find(birdsEyeViewBW);
xyBoundaryPoints = imageToVehicle(birdsEyeConfig, [imageY, imageX]);
由于分割点包含许多不属于实际车道标记的异常值, 因此, 采用基于随机样本一致 (RANSAC) 的鲁棒曲线拟合算法。
返回 parabolicLaneBoundary 对象的数组中的边界及其抛物线参数 (a、b、c)。boundaries
maxLanes = 2; % look for maximum of two lane markers
boundaryWidth = 3*approxLaneMarkerWidthVehicle; % expand boundary width to search for double markers
[boundaries, boundaryPoints] = findParabolicLaneBoundaries(xyBoundaryPoints,boundaryWidth, ...
'MaxNumBoundaries', maxLanes, 'validateBoundaryFcn', @validateBoundaryFcn);
请注意, 采用函数句柄。此示例函数在本示例末尾列出。使用此附加输入可以根据 a、b、c 参数的值拒绝某些曲线。它也可以用来利用在一系列帧的时间信息的优势, 通过限
制未来的 a, b, c 的基础上的前视频帧值。findParabolicLaneBoundariesvalidateBoundaryFcn
确定自我车道的边界
上一步中发现的某些曲线可能仍然无效。例如, 当曲线适合于人行横道标记时。使用其他启发式来拒绝许多这样的曲线。
% Establish criteria for rejecting boundaries based on their length
maxPossibleXLength = diff(vehicleROI(1:2));
minXLength = maxPossibleXLength * 0.60; % establish a threshold
% Reject short boundaries
isOfMinLength = arrayfun(@(b)diff(b.XExtent) > minXLength, boundaries);
boundaries = boundaries(isOfMinLength);
基于 findParabolicLaneBoundaries 函数计算的强度度量, 删除其他边界。根据 ROI 和图像大小设置车道强度阈值。
% To compute the maximum strength, assume all image pixels within the ROI
% are lane candidate points
birdsImageROI = vehicleToImageROI(birdsEyeConfig, vehicleROI);
[laneImageX,laneImageY] = meshgrid(birdsImageROI(1):birdsImageROI(2),birdsImageROI(3):birdsImageROI(4));
% Convert the image points to vehicle points
vehiclePoints = imageToVehicle(birdsEyeConfig,[laneImageX(:),laneImageY(:)]);
% Find the maximum number of unique xaxis locations possible for any lane
% boundary
maxPointsInOneLane = numel(unique(vehiclePoints(:,1)));
% Set the maximum length of a lane boundary to the ROI length
maxLaneLength = diff(vehicleROI(1:2));
% Compute the maximum possible lane strength for this image size/ROI size
% specification
maxStrength = maxPointsInOneLane/maxLaneLength;
% Reject weak boundaries
isStrong = [boundaries.Strength] > 0.4*maxStrength;
boundaries = boundaries(isStrong);
在本例的底部列出的帮助函数中包含了将车道标记类型分类为单/双、实/虚的启发式算法。知道车道标记类型是关键的自动驾驶车辆。例如, 禁止通过另一辆车越过双固体车
道标记。
boundaries = classifyLaneTypes(boundaries, boundaryPoints);
% Locate two ego lanes if they are present
xOffset = 0; % 0 meters from the sensor
distanceToBoundaries = boundaries.computeBoundaryModel(xOffset);
% Find candidate ego boundaries
leftEgoBoundaryIndex = [];
rightEgoBoundaryIndex = [];
minLDistance = min(distanceToBoundaries(distanceToBoundaries>0));
minRDistance = max(distanceToBoundaries(distanceToBoundaries<=0));
if ~isempty(minLDistance)
leftEgoBoundaryIndex = distanceToBoundaries == minLDistance;
end
if ~isempty(minRDistance)
rightEgoBoundaryIndex = distanceToBoundaries == minRDistance;
end
leftEgoBoundary = boundaries(leftEgoBoundaryIndex);
rightEgoBoundary = boundaries(rightEgoBoundaryIndex);
显示检测到的车道标记在鸟瞰图象和在规则视图。
xVehiclePoints = bottomOffset:distAheadOfSensor;
birdsEyeWithEgoLane = insertLaneBoundary(birdsEyeImage, leftEgoBoundary , birdsEyeConfig, xVehiclePoints, 'Color','Red');
birdsEyeWithEgoLane = insertLaneBoundary(birdsEyeWithEgoLane, rightEgoBoundary, birdsEyeConfig, xVehiclePoints, 'Color','Green');
frameWithEgoLane = insertLaneBoundary(frame, leftEgoBoundary, sensor, xVehiclePoints, 'Color','Red');
frameWithEgoLane = insertLaneBoundary(frameWithEgoLane, rightEgoBoundary, sensor, xVehiclePoints, 'Color','Green');
figure
subplot('Position', [0, 0, 0.5, 1.0]) % [left, bottom, width, height] in normalized units
imshow(birdsEyeWithEgoLane)
subplot('Position', [0.5, 0, 0.5, 1.0])
imshow(frameWithEgoLane)
在车辆座标中定位车辆
在前碰撞警告 (联合会) 和自动紧急制动 (AEB) 系统中, 检测和跟踪车辆至关重要。
加载 pretrained 检测车辆前后部的聚合通道功能 (ACF) 检测器。这样的检测器可以处理在发出冲突警告时非常重要的情况。这是不够的, 例如, 检测车辆行驶在前面的自我
车辆的道路。
detector = vehicleDetectorACF();
% Width of a common vehicles is between 1.5 to 2.5 meters
vehicleWidth = [1.5, 2.5];
使用 configureDetectorMonoCamera 函数专门用于通用的 ACF 探测器, 以考虑典型的汽车应用的几何形状。通过这种相机的配置, 这个新的探测器只搜索道路表面沿线的
车辆, 因为没有点搜索的车辆在消失点高。这样可以节省计算时间, 减少误报数。
monoDetector = configureDetectorMonoCamera(detector, sensor, vehicleWidth);
[bboxes, scores] = detect(monoDetector, frame);
由于此示例演示如何仅处理单个帧以进行演示, 因此无法在原始检测的顶部应用跟踪。跟踪的增加使得返回车辆位置的结果更加健壮, 因为即使车辆部分被遮挡, 跟踪器仍会继
续返回车辆的位置。有关详细信息, 请参阅从照相机跟踪多个车辆示例。
然后, 将车辆检测转换为车辆坐标。该函数, 包括在本例的末尾, 计算的车辆在车辆坐标的位置给定一个边界框的检测算法返回的图像坐标。它返回边界框底部在车辆坐标中的
中心位置。由于我们使用的是单目相机传感器和简单的应, 只有沿路面的距离可以准确计算。在3维空间中任意位置的计算需要使用立体声摄像机或能够进行三角化的另一个
传感器。computeVehicleLocations
locations = computeVehicleLocations(bboxes, sensor);
% Overlay the detections on the video frame
imgOut = insertVehicleDetections(frame, locations, bboxes);
figure;
imshow(imgOut);
模拟带有视频输入的完整传感器
现在, 您已经了解了各个步骤的内部工作原理, 让我们把它们放在一起, 并将它们应用到视频序列中, 我们还可以利用时态信息。
将视频倒带到开头, 然后处理视频。下面的代码被缩短, 因为所有的关键参数都是在前面的步骤中定义的。在这里, 参数的使用没有进一步的解释。
videoReader.CurrentTime = 0;
isPlayerOpen = true;
snapshot = [];
while hasFrame(videoReader) && isPlayerOpen
% Grab a frame of video
frame = readFrame(videoReader);
% Compute birdsEyeView image
birdsEyeImage = transformImage(birdsEyeConfig, frame);
birdsEyeImage = rgb2gray(birdsEyeImage);
% Detect lane boundary features
birdsEyeViewBW = segmentLaneMarkerRidge(birdsEyeImage, birdsEyeConfig, ...
approxLaneMarkerWidthVehicle, 'ROI', vehicleROI, ...
'Sensitivity', laneSensitivity);
% Obtain lane candidate points in vehicle coordinates
[imageX, imageY] = find(birdsEyeViewBW);
xyBoundaryPoints = imageToVehicle(birdsEyeConfig, [imageY, imageX]);
% Find lane boundary candidates
[boundaries, boundaryPoints] = findParabolicLaneBoundaries(xyBoundaryPoints,boundaryWidth, ...
'MaxNumBoundaries', maxLanes, 'validateBoundaryFcn', @validateBoundaryFcn);
% Reject boundaries based on their length and strength
isOfMinLength = arrayfun(@(b)diff(b.XExtent) > minXLength, boundaries);
boundaries = boundaries(isOfMinLength);
isStrong = [boundaries.Strength] > 0.2*maxStrength;
boundaries = boundaries(isStrong);
% Classify lane marker type
boundaries = classifyLaneTypes(boundaries, boundaryPoints);
% Find ego lanes
xOffset = 0; % 0 meters from the sensor
distanceToBoundaries = boundaries.computeBoundaryModel(xOffset);
% Find candidate ego boundaries
leftEgoBoundaryIndex = [];
rightEgoBoundaryIndex = [];
minLDistance = min(distanceToBoundaries(distanceToBoundaries>0));
minRDistance = max(distanceToBoundaries(distanceToBoundaries<=0));
if ~isempty(minLDistance)
leftEgoBoundaryIndex = distanceToBoundaries == minLDistance;
end
if ~isempty(minRDistance)
rightEgoBoundaryIndex = distanceToBoundaries == minRDistance;
end
leftEgoBoundary = boundaries(leftEgoBoundaryIndex);
rightEgoBoundary = boundaries(rightEgoBoundaryIndex);
% Detect vehicles
[bboxes, scores] = detect(monoDetector, frame);
locations = computeVehicleLocations(bboxes, sensor);
% Visualize sensor outputs and intermediate results. Pack the core
% sensor outputs into a struct.
sensorOut.leftEgoBoundary = leftEgoBoundary;
sensorOut.rightEgoBoundary = rightEgoBoundary;
sensorOut.vehicleLocations = locations;
sensorOut.xVehiclePoints = bottomOffset:distAheadOfSensor;
sensorOut.vehicleBoxes = bboxes;
% Pack additional visualization data, including intermediate results
intOut.birdsEyeImage = birdsEyeImage;
intOut.birdsEyeConfig = birdsEyeConfig;
intOut.vehicleScores = scores;
intOut.vehicleROI = vehicleROI;
intOut.birdsEyeBW = birdsEyeViewBW;
closePlayers = ~hasFrame(videoReader);
isPlayerOpen = visualizeSensorResults(frame, sensor, sensorOut, ...
intOut, closePlayers);
timeStamp = 7.5333; % take snapshot for publishing at timeStamp seconds
if abs(videoReader.CurrentTime timeStamp) < 0.01
snapshot = takeSnapshot(frame, sensor, sensorOut);
end
end
显示视频帧。快照在几秒钟内拍摄。timeStamp
if ~isempty(snapshot)
figure
imshow(snapshot)
end
在不同的视频上尝试传感器设计
helperMonoSensor 类组装安装程序和所有必要的步骤, 以将单眼照相机传感器模拟到可应用于任何视频的完整包中。由于传感器设计所使用的大多数参数都是基于世界单位
的, 因此设计对摄像机参数 (包括图像大小) 的变化具有很强的鲁棒性。请注意, 类内的代码与上一节中的循环不同, 后者用于阐释基本概念。helperMonoSensor
除了提供一个新的视频, 你必须提供一个摄像头配置对应的视频。这个过程在这里显示。尝试自己的视频。
% Sensor configuration
focalLength = [309.4362, 344.2161];
principalPoint = [318.9034, 257.5352];
imageSize = [480, 640];
height = 2.1798; % mounting height in meters from the ground
pitch = 14; % pitch of the camera in degrees
camIntrinsics = cameraIntrinsics(focalLength, principalPoint, imageSize);
sensor = monoCamera(camIntrinsics, height, 'Pitch', pitch);
videoReader = VideoReader('caltech_washington1.avi');
创建对象并将其应用于视频。helperMonoSensor
monoSensor = helperMonoSensor(sensor);
monoSensor.LaneXExtentThreshold = 0.5;
% To remove false detections from shadows in this video, we only return
% vehicle detections with higher scores.
monoSensor.VehicleDetectionThreshold = 20;
isPlayerOpen = true;
snapshot = [];
while hasFrame(videoReader) && isPlayerOpen
frame = readFrame(videoReader); % get a frame
sensorOut = processFrame(monoSensor, frame);
closePlayers = ~hasFrame(videoReader);
isPlayerOpen = displaySensorOutputs(monoSensor, frame, sensorOut, closePlayers);
timeStamp = 11.1333; % take snapshot for publishing at timeStamp seconds
if abs(videoReader.CurrentTime timeStamp) < 0.01
snapshot = takeSnapshot(frame, sensor, sensorOut);
end
end
显示视频帧。快照以秒为单位。timeStamp