20、汽车距离测量与视图模式切换技术实现

汽车距离测量与视图模式切换技术实现

1. 汽车距离测量概述

在应用中,我们可以从两个不同的视图来测量汽车之间或汽车与相机之间的距离,分别是鸟瞰视图和水平视图。

2. 鸟瞰视图下汽车间距测量
  • 相机设置 :将相机固定在八楼办公室的窗户上,使其面向地面获取画面。
  • 代码实现流程
    1. 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 米。
  • 代码实现流程
    1. 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[结束];

通过以上的实现和分析,我们掌握了如何使用相关技术实现汽车距离测量和视图模式切换的功能,并且对未来的改进方向有了一定的思考。希望这些内容对大家有所帮助。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值