汽车距离测量与视图模式切换技术实现
1. 汽车距离测量概述
在应用中,我们可以从两个不同的视图来测量汽车之间或汽车与相机之间的距离,分别是鸟瞰视图和水平视图。
2. 鸟瞰视图下汽车间距测量
- 相机设置 :将相机固定在八楼办公室的窗户上,使其面向地面获取画面。
-
代码实现流程
:
-
在
capture_thread.cpp文件中添加distanceBirdEye函数:
-
在
void distanceBirdEye(cv::Mat &frame, vector<cv::Rect> &cars)
{
// ...
}
该函数接收视频帧和检测到的汽车边界框向量作为参数。
2. 合并重叠的边界框:
vector<int> length_of_cars;
vector<pair<int, int>> endpoints;
vector<pair<int, int>> cars_merged;
for (auto car: cars) {
length_of_cars.push_back(car.width);
endpoints.push_back(make_pair(car.x, 1));
endpoints.push_back(make_pair(car.x + car.width, -1));
}
sort(length_of_cars.begin(), length_of_cars.end());
int length = length_of_cars[cars.size() / 2];
sort(
endpoints.begin(), endpoints.end(),
[](pair<int, int> a, pair<int, int> b) {
return a.first < b.first;
}
);
int flag = 0, start = 0;
for (auto ep: endpoints) {
flag += ep.second;
if (flag == 1 && start == 0) { // a start
start = ep.first;
} else if (flag == 0) { // an end
cars_merged.push_back(make_pair(start, ep.first));
start = 0;
}
}
3. 计算并显示距离:
for (size_t i = 1; i < cars_merged.size(); i++) {
int x1 = cars_merged[i - 1].second;
int x2 = cars_merged[i].first;
cv::line(frame, cv::Point(x1, 0), cv::Point(x1, frame.rows),
cv::Scalar(0, 255, 0), 2);
cv::line(frame, cv::Point(x2, 0), cv::Point(x2, frame.rows),
cv::Scalar(0, 0, 255), 2);
float distance = (x2 - x1) * (5.0 / length);
string label = cv::format("%.2f m", distance);
int baseLine;
cv::Size labelSize = cv::getTextSize(
label, cv::FONT_HERSHEY_SIMPLEX, 0.5, 1, &baseLine);
int label_x = (x1 + x2) / 2 - (labelSize.width / 2);
cv::putText(
frame, label, cv::Point(label_x, 20),
cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(255, 255, 255));
}
4. 在`CaptureThread::detectObjectsDNN`方法中调用`distanceBirdEye`函数:
for(size_t i = 0; i < outBoxes.size(); i ++) {
cv::rectangle(frame, outBoxes[i], cv::Scalar(0, 0, 255));
}
distanceBirdEye(frame, outBoxes);
以下是鸟瞰视图下汽车间距测量的流程图:
graph TD;
A[开始] --> B[添加distanceBirdEye函数];
B --> C[合并重叠边界框];
C --> D[计算并显示距离];
D --> E[在detectObjectsDNN方法中调用distanceBirdEye函数];
E --> F[结束];
3. 水平视图下汽车与相机距离测量
-
参考值获取
:将相机安装在固定位置,拍摄一张汽车照片,获取两个参考值:照片中汽车的宽度(以像素为单位)
W0和拍摄时相机与汽车的距离D0。示例中W0 = 150像素,D0 = 10米。 -
代码实现流程
:
-
在
capture_thread.cpp文件中添加distanceEyeLevel函数:
-
在
void distanceEyeLevel(cv::Mat &frame, vector<cv::Rect> &cars)
{
const float d0 = 1000.0f; // cm
const float w0 = 150.0f; // px
// ...
}
2. 选择目标汽车:
vector<cv::Rect> cars_in_middle;
vector<int> cars_area;
size_t target_idx = 0;
for (auto car: cars) {
if(car.x < frame.cols / 2 && (car.x + car.width) > frame.cols / 2) {
cars_in_middle.push_back(car);
int area = car.width * car.height;
cars_area.push_back(area);
if (area > cars_area[target_idx]) {
target_idx = cars_area.size() - 1;
}
}
}
if(cars_in_middle.size() <= target_idx) return;
3. 计算并显示距离:
cv::Rect car = cars_in_middle[target_idx];
float distance = (w0 / car.width) * d0; // (w0 / w1) * d0
string label = cv::format("%.2f m", distance / 100);
int baseLine;
cv::Size labelSize = cv::getTextSize(
label, cv::FONT_HERSHEY_SIMPLEX, 0.5, 1, &baseLine);
cv::putText(frame, label, cv::Point(car.x, car.y + labelSize.height),
cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0, 255, 255));
4. 在`CaptureThread::detectObjectsDNN`方法中调用`distanceEyeLevel`函数:
for(size_t i = 0; i < outBoxes.size(); i ++) {
cv::rectangle(frame, outBoxes[i], cv::Scalar(0, 0, 255));
}
distanceEyeLevel(frame, outBoxes);
以下是水平视图下汽车与相机距离测量的流程图:
graph TD;
A[开始] --> B[添加distanceEyeLevel函数];
B --> C[选择目标汽车];
C --> D[计算并显示距离];
D --> E[在detectObjectsDNN方法中调用distanceEyeLevel函数];
E --> F[结束];
4. 视图模式切换
为了让用户能够在鸟瞰视图和水平视图之间切换,我们需要对应用进行如下修改:
-
在
capture_thread.h
文件中添加相关代码
:
class CaptureThread : public QThread
{
// ...
public:
// ...
enum ViewMode { BIRDEYE, EYELEVEL, };
void setViewMode(ViewMode m) {viewMode = m; };
// ...
private:
// ...
ViewMode viewMode;
};
-
在
CaptureThread类的构造函数中初始化viewMode:
CaptureThread::CaptureThread(int camera, QMutex *lock):
running(false), cameraID(camera), videoPath(""), data_lock(lock)
{
frame_width = frame_height = 0;
taking_photo = false;
viewMode = BIRDEYE; // 初始化视图模式为鸟瞰视图
}
CaptureThread::CaptureThread(QString videoPath, QMutex *lock):
running(false), cameraID(-1), videoPath(videoPath), data_lock(lock)
{
frame_width = frame_height = 0;
taking_photo = false;
viewMode = BIRDEYE; // 初始化视图模式为鸟瞰视图
}
-
在
CaptureThread::detectObjectsDNN方法中根据viewMode调用相应函数 :
if (viewMode == BIRDEYE) {
distanceBirdEye(frame, outBoxes);
} else {
distanceEyeLevel(frame, outBoxes);
}
-
在
mainwindow.h文件中添加相关方法和字段 :
class MainWindow : public QMainWindow
{
// ...
private slots:
// ...
void changeViewMode();
private:
// ...
QMenu *viewMenu;
QAction *birdEyeAction;
QAction *eyeLevelAction;
// ...
};
-
在
MainWindow::initUI()方法中创建菜单 :
// setup menubar
fileMenu = menuBar()->addMenu("&File");
viewMenu = menuBar()->addMenu("&View");
-
在
MainWindow::createActions方法中实例化动作并添加到视图菜单 :
birdEyeAction = new QAction("Bird Eye View");
birdEyeAction->setCheckable(true);
viewMenu->addAction(birdEyeAction);
eyeLevelAction = new QAction("Eye Level View");
eyeLevelAction->setCheckable(true);
viewMenu->addAction(eyeLevelAction);
birdEyeAction->setChecked(true);
connect(birdEyeAction, SIGNAL(triggered(bool)), this, SLOT(changeViewMode()));
connect(eyeLevelAction, SIGNAL(triggered(bool)), this, SLOT(changeViewMode()));
-
实现
changeViewMode槽函数 :
void MainWindow::changeViewMode()
{
CaptureThread::ViewMode mode = CaptureThread::BIRDEYE;
QAction *active_action = qobject_cast<QAction*>(sender());
if(active_action == birdEyeAction) {
birdEyeAction->setChecked(true);
eyeLevelAction->setChecked(false);
mode = CaptureThread::BIRDEYE;
} else if (active_action == eyeLevelAction) {
eyeLevelAction->setChecked(true);
birdEyeAction->setChecked(false);
mode = CaptureThread::EYELEVEL;
}
if(capturer != nullptr) {
capturer->setViewMode(mode);
}
}
-
在
MainWindow::openCamera方法结束时重置动作状态 :
birdEyeAction->setChecked(true);
eyeLevelAction->setChecked(false);
以下是视图模式切换的操作步骤列表:
1. 在
capture_thread.h
文件中定义视图模式枚举和设置函数。
2. 在
CaptureThread
类的构造函数中初始化视图模式。
3. 在
CaptureThread::detectObjectsDNN
方法中根据视图模式调用相应函数。
4. 在
mainwindow.h
文件中添加相关方法和字段。
5. 在
MainWindow::initUI()
方法中创建视图菜单。
6. 在
MainWindow::createActions
方法中实例化动作并连接信号槽。
7. 实现
changeViewMode
槽函数。
8. 在
MainWindow::openCamera
方法结束时重置动作状态。
通过以上步骤,我们实现了汽车距离测量和视图模式切换的功能,用户可以通过视图菜单轻松切换测量模式。
汽车距离测量与视图模式切换技术实现(续)
5. 总结与效果展示
通过上述一系列的操作,我们成功实现了在不同视图模式下测量汽车距离以及视图模式的切换功能。以下是对整个实现过程的总结:
| 功能模块 | 主要操作步骤 |
|---|---|
| 鸟瞰视图下汽车间距测量 |
1. 添加
distanceBirdEye
函数;2. 合并重叠边界框;3. 计算并显示距离;4. 在
detectObjectsDNN
方法中调用
distanceBirdEye
函数
|
| 水平视图下汽车与相机距离测量 |
1. 添加
distanceEyeLevel
函数;2. 选择目标汽车;3. 计算并显示距离;4. 在
detectObjectsDNN
方法中调用
distanceEyeLevel
函数
|
| 视图模式切换 |
1. 在
capture_thread.h
文件中定义视图模式枚举和设置函数;2. 在
CaptureThread
类的构造函数中初始化视图模式;3. 在
CaptureThread::detectObjectsDNN
方法中根据视图模式调用相应函数;4. 在
mainwindow.h
文件中添加相关方法和字段;5. 在
MainWindow::initUI()
方法中创建视图菜单;6. 在
MainWindow::createActions
方法中实例化动作并连接信号槽;7. 实现
changeViewMode
槽函数;8. 在
MainWindow::openCamera
方法结束时重置动作状态
|
当我们编译并运行应用程序后,可以看到以下效果:
- 在鸟瞰视图下,视频中会出现许多绿色和红色的线对,它们表示汽车之间的距离,并且在视频顶部的两条线之间会标记出近似的距离长度。
- 在水平视图下,视频中可能会检测到多辆汽车,但只会测量中间那辆汽车与相机之间的距离,距离长度会以黄色文本标记在目标汽车边界框的左上角。
- 用户可以通过视图菜单轻松地在鸟瞰视图和水平视图之间进行切换。
6. 关键技术点分析
-
参考对象的选择
:
- 在鸟瞰视图中,由于没有合适的固定大小的参考对象,我们选择检测到的汽车长度的中位数,并假设其在现实世界中的长度为 5 米,以此作为参考对象来计算汽车之间的距离。这种方法考虑到了可能存在部分汽车进入或离开相机视野的情况,避免了使用平均值带来的不准确性。
- 在水平视图中,我们通过将相机安装在固定位置,拍摄一张汽车照片,获取照片中汽车的宽度(以像素为单位)和拍摄时相机与汽车的距离作为参考值,然后根据这些参考值计算目标汽车与相机之间的距离。
- 重叠边界框的合并 :在鸟瞰视图下测量汽车间距时,为了避免重复测量部分重叠的汽车之间的距离,我们需要合并在水平方向上重叠的边界框。具体做法是通过记录汽车的端点信息,对端点进行排序,然后根据端点的变化情况合并边界框。这种方法有效地处理了重叠汽车的距离测量问题。
-
视图模式的切换
:为了实现用户在鸟瞰视图和水平视图之间的切换,我们使用了枚举类型来表示不同的视图模式,并在
CaptureThread类中添加了相应的成员变量和设置函数。同时,在MainWindow类中创建了视图菜单和动作,并通过信号槽机制实现了视图模式的切换。这种设计使得用户可以方便地在不同视图模式下进行距离测量。
7. 未来展望
虽然我们已经实现了基本的汽车距离测量和视图模式切换功能,但在实际应用中,还有一些可以改进和扩展的地方:
-
提高测量精度
:可以通过使用更精确的参考对象或采用更复杂的算法来提高距离测量的精度。例如,可以使用已知尺寸的标志物作为参考对象,或者结合多传感器数据进行距离测量。
-
增加更多视图模式
:除了鸟瞰视图和水平视图,还可以考虑增加其他视图模式,如斜视图等,以满足不同场景下的需求。
-
优化用户界面
:可以进一步优化用户界面,使距离测量结果更加直观和易于理解。例如,可以添加更多的可视化元素,如距离图表等。
通过不断地改进和扩展,我们可以使汽车距离测量系统更加完善,为用户提供更好的使用体验。
以下是整个实现流程的 mermaid 流程图:
graph LR;
A[开始] --> B[鸟瞰视图测量];
A --> C[水平视图测量];
B --> D[合并重叠边界框];
D --> E[计算并显示距离];
C --> F[选择目标汽车];
F --> G[计算并显示距离];
B & C --> H[视图模式切换];
H --> I[创建视图菜单和动作];
I --> J[实现信号槽机制];
J --> K[用户切换视图模式];
E & G & K --> L[结束];
通过以上的实现和分析,我们掌握了如何使用相关技术实现汽车距离测量和视图模式切换的功能,并且对未来的改进方向有了一定的思考。希望这些内容对大家有所帮助。
超级会员免费看

被折叠的 条评论
为什么被折叠?



