#include "widget.h" #include "ui_widget.h" #include #include #include #include #include #include // #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; // 硬编码参数值 int file_delay = 1180; // 延迟时间(毫秒) int file_encoder = 12000; // 行频 int file_valve = 200; // 喷阀触发频率 //下位机参数 int lowmac_dp = 350; //偏振延迟时间 int lowmac_sm = 1200; //吹气量 valve/12 = 吹气时间ms int lowmac_ts = 10; //模板匹配阈值 int lowmac_sg = 65; //偏振绿色通道大小阈值 int lowmac_td = 7; //偏振红色通道差值 Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) { this->isCamRunning = false; ui->setupUi(this); // 确认 tabWidget 是 QTabWidget 类型 QTabWidget* tab = qobject_cast(ui->tabWidget); if (tab) { QTabBar* tabBar = tab->tabBar(); if (tabBar) { tabBar->hide(); // 隐藏 TabBar } else { qWarning() << "tabBar() 返回 nullptr!"; } } else { qWarning() << "ui->tabWidget 不是 QTabWidget 类型!"; } ui->camera_0_img->setScaledContents(false); ui->camera_1_img->setScaledContents(false); iniOnnx(); // iniColor(); loadConfig(getConfigDirectory()+"/color_range_config.txt"); iniLowMac(); iniCamera(); update_colorlist(); // 初始化存储工作者和线程 storageWorker = new StorageWorker(); storageWorker->moveToThread(&storageThread); connect(&storageThread, &QThread::started, storageWorker, &StorageWorker::process); connect(this, &Widget::destroyed, &storageThread, &QThread::quit); connect(&storageThread, &QThread::finished, storageWorker, &QObject::deleteLater); storageThread.start(); // 启动显示定时器,每秒检查一次 QTimer* timer = new QTimer(this); connect(timer, &QTimer::timeout, this, &Widget::refreshImage); timer->start(40); // 每50毫秒秒刷新一次界面 ui->tabWidget->setCurrentIndex(1); } Widget::~Widget() { // 停止存储线程 g_storageQueue.stop(); storageThread.quit(); storageThread.wait(); // 现有清理代码... DestoryCamera(); DestoryLowMac(); delete ui; } void Widget::refreshImage() { // refresh Image 1 and image 0 refreshSingleImage(0, this->ui->mtx_0_overlay->isChecked(), this->ui->dl_0_overlay->isChecked(), this->ui->img_0_mirror->isChecked()); refreshSingleImage(1, this->ui->mtx_1_overlay->isChecked(), this->ui->dl_1_overlay->isChecked(), this->ui->img_1_mirror->isChecked()); // refresh buttons this->ui->btn_start->setEnabled(!this->isCamRunning); this->ui->btn_stop->setEnabled(this->isCamRunning); this->ui->btn_take_photos->setEnabled(this->isCamRunning); // refresh checkouts this->ui->dl_enable_0->setEnabled(!this->isCamRunning); this->ui->dl_enable_1->setEnabled(!this->isCamRunning); this->ui->tra_enable_0->setEnabled(!this->isCamRunning); this->ui->tra_enable_1->setEnabled(!this->isCamRunning); this->ui->btn_quit->setEnabled(!this->isCamRunning); // QDateTime now = QDateTime::currentDateTime(); ui->label_currentDateTime->setText(now.toString("yyyy-MM-dd hh:mm:ss")); if (this->isCamRunning) { // 计算启动至今的秒数差 qint64 elapsedSeconds = this->startTime.secsTo(now); // 转化为小时、分钟、秒 qint64 hours = elapsedSeconds / 3600; qint64 minutes = (elapsedSeconds % 3600) / 60; qint64 seconds = elapsedSeconds % 60; // 将“总运行时间”的小时/分钟分别更新到 UI ui->label_totalHours->setText(QString::number(hours)); // e.g. "482" ui->label_totalMinutes->setText(QString::number(minutes)); // e.g. "52" // 拼接“今日运行时长”一句话 (根据实际需求,可直接使用上面 hours、minutes、seconds) QString todayRunStr = QString("今日运行时长:%1小时%2分钟%3秒") .arg(hours) .arg(minutes) .arg(seconds); ui->label_todayRunningTime->setText(todayRunStr); } else { // 如果设备没在运行,UI可以显示默认内容或保持不变 // 下面示例:将运行时长重置为 0 ui->label_totalHours->setText("0"); ui->label_totalMinutes->setText("0"); ui->label_todayRunningTime->setText("今日运行时长:0小时0分钟0秒"); } // refresh info QString info; if(this->isCamRunning) { if(SaveImg_Flag==1) { info = "存图中!!"; } else { info = "运行"; }; }else { info = "停止"; }; this->ui->lab_info->setText(info); ui->label_valve_actions->setText(QLocale(QLocale::English).toString(g_valveActionCount)); } void Widget::refreshSingleImage(int camera_id, bool overlay_traditional_result, bool overlay_dl_result, bool mirror) { // 验证摄像头ID的有效性 if (camera_id < 0 || camera_id >= 2) { // 假设只有两个摄像头 qWarning() << "Invalid Camera ID:" << camera_id; return; } // 定义每个摄像头对应的 QLabel QLabel* cameraLabels[2] = { ui->camera_0_img, ui->camera_1_img }; // 定义每个摄像头对应的变量数组 QMutex* dispPicMutexes[2] = { &gDispPicMutex0, &gDispPicMutex1 }; MIL_ID dispCurrentPicIds[2] = { gDispCurrentPicId0, gDispCurrentPicId1 }; // 获取当前摄像头的数据 QMutex* currentDispMutex = dispPicMutexes[camera_id]; MIL_ID current_id; { QMutexLocker locker(currentDispMutex); current_id = dispCurrentPicIds[camera_id]; } if (current_id == 0) return; // 将MIL图像转换为OpenCV Mat cv::Mat img = ImageUtils::mil2Mat(current_id); if (img.empty()) { qWarning() << "Failed to convert MIL image to Mat for Camera ID:" << camera_id; return; } if (current_id == 0) return; std::vector> dl_mask; std::vector> traditional_mask; { QMutexLocker locker(&g_detection_result[camera_id].mutex); dl_mask = g_detection_result[camera_id].dl_mask; traditional_mask = g_detection_result[camera_id].traditional_mask; } // 如果需要叠加结果,处理掩码 if (overlay_dl_result && g_dl_enable[camera_id]) { if (!dl_mask.empty() && !dl_mask[0].empty()) { // 将二维 vector 转换为 cv::Mat int rows = dl_mask.size(); int cols = dl_mask[0].size(); cv::Mat dl_mask_mat(rows, cols, CV_8U); for (int i = 0; i < rows; ++i) { if (dl_mask[i].size() != cols) { qWarning() << "Inconsistent mask row size for dl_mask in Camera ID:" << camera_id; dl_mask_mat.release(); break; } memcpy(dl_mask_mat.ptr(i), dl_mask[i].data(), cols); } if (!dl_mask_mat.empty()) { // 调整掩膜尺寸以匹配图像尺寸 if (dl_mask_mat.size() != img.size()) { cv::resize(dl_mask_mat, dl_mask_mat, img.size(), 0, 0, cv::INTER_NEAREST); } // 确保掩膜为二值图像 cv::threshold(dl_mask_mat, dl_mask_mat, 128, 255, cv::THRESH_BINARY); // 创建绿色掩膜 cv::Mat green_overlay(img.size(), img.type(), cv::Scalar(0, 255, 0)); green_overlay.setTo(cv::Scalar(0, 255, 0), dl_mask_mat); // 叠加掩膜到图像 cv::addWeighted(green_overlay, 0.5, img, 1.0, 0, img); } } } if (overlay_traditional_result & g_traditional_enable[camera_id]) { if (!traditional_mask.empty() && !traditional_mask[0].empty()) { // 将二维 vector 转换为 cv::Mat int rows = traditional_mask.size(); int cols = traditional_mask[0].size(); cv::Mat traditional_mask_mat(rows, cols, CV_8U); for (int i = 0; i < rows; ++i) { if (traditional_mask[i].size() != cols) { qWarning() << "Inconsistent mask row size for traditional_mask in Camera ID:" << camera_id; traditional_mask_mat.release(); break; } memcpy(traditional_mask_mat.ptr(i), traditional_mask[i].data(), cols); } if (!traditional_mask_mat.empty()) { // 调整掩膜尺寸以匹配图像尺寸 if (traditional_mask_mat.size() != img.size()) { cv::resize(traditional_mask_mat, traditional_mask_mat, img.size(), 0, 0, cv::INTER_NEAREST); } // 确保掩膜为二值图像 cv::threshold(traditional_mask_mat, traditional_mask_mat, 128, 255, cv::THRESH_BINARY); // 创建红色掩膜 cv::Mat red_overlay(img.size(), img.type(), cv::Scalar(0, 0, 255)); red_overlay.setTo(cv::Scalar(0, 0, 255), traditional_mask_mat); // 叠加掩膜到图像 cv::addWeighted(red_overlay, 0.5, img, 1.0, 0, img); } } } // 如果需要镜像处理 if (mirror) { cv::flip(img, img, 1); // 水平翻转 } // 将OpenCV Mat转换为QPixmap QPixmap pixmap = ImageUtils::mat2QPixmap(img); if (pixmap.isNull()) { qWarning() << "Failed to convert Mat to QPixmap for Camera ID:" << camera_id; return; } // 高质量缩放图像 QSize labelSize = cameraLabels[camera_id]->size(); QPixmap scaledPixmap = pixmap.scaled(labelSize); // 更新UI标签 cameraLabels[camera_id]->setPixmap(scaledPixmap); } void Widget::on_pushButton_2_clicked() { SaveImg_Flag = 1; } void Widget::on_btn_stop_clicked() { DestoryLowMac(); this->isCamRunning = false; // 恢复显示的图片 { QMutexLocker locker(&gDispPicMutex0); gDispCurrentPicId0 = 0; } ui->camera_0_img->clear(); { QMutexLocker locker(&gDispPicMutex1); gDispCurrentPicId1 = 0; } ui->camera_1_img->clear(); } void Widget::on_btn_start_clicked() { g_dl_enable[0] = !this->ui->dl_enable_0->isChecked(); g_dl_enable[1] = !this->ui->dl_enable_0->isChecked(); g_traditional_enable[0] = !this->ui->tra_enable_0->isChecked(); g_traditional_enable[1] = !this->ui->tra_enable_1->isChecked(); this->isCamRunning = true; this->startTime = QDateTime::currentDateTime(); // 从这里开始计算设备运行时间 // 热身两个工作者 for(int i = 0; i < 2; ++i) { g_runner_array[i]->warm_up(); } // 启动检测工作者线程 for(int i = 0; i < 2; ++i) { g_recognitionRunning[i]->store(true); g_recognitionThread[i] = new std::thread(detectionWorker, i); // 获取线程的本地句柄 HANDLE hThread = static_cast(g_recognitionThread[i]->native_handle()); // 设置线程优先级为最高 if(SetThreadPriority(hThread, THREAD_PRIORITY_HIGHEST)) { std::cout << "DL Thread " << i << " set highest thread priority。" << std::endl; } else { std::cerr << "SET thread " << i << " failed, error code:" << GetLastError() << std::endl; } } Start_camera(); } void Widget::on_btn_take_photos_pressed() { SaveImg_Flag = true; } void Widget::on_btn_take_photos_released() { SaveImg_Flag = false; } void Widget::on_btn_quit_clicked() { // // 停止检测工作者线程 // for(int i = 0; i < 2; ++i) // { // g_recognitionRunning[i]->store(false); // g_img_Queue[i]->stop(); // 停止队列以唤醒线程 // } // // 等待检测工作者线程结束 // for(int i = 0; i < 2; ++i) // { // if(g_recognitionThread[i] && g_recognitionThread[i]->joinable()) // { // g_recognitionThread[i]->join(); // delete g_recognitionThread[i]; // g_recognitionThread[i] = nullptr; // } // if(g_recognitionRunning[i]) // { // delete g_recognitionRunning[i]; // g_recognitionRunning[i] = nullptr; // } // } DestoryCamera(); DestoryLowMac(); qApp->quit(); } void Widget::on_btn_set_lower_clicked() { // 硬编码参数值 file_delay = ui->spinbox_delaytime->text().toInt(); // 延迟时间(毫秒) file_encoder = ui->spinbox_encoder->text().toInt(); // 编码器值++ file_valve = ui->spinbox_valve->text().toInt(); // 阀门通道 } void Widget::on_btn_set_valve_clicked() { ui->tabWidget->setCurrentIndex(2); } void Widget::on_btn_tab3_backtab2_clicked() { ui->tabWidget->setCurrentIndex(1); } void Widget::on_btn_live_clicked() { ui->tabWidget->setCurrentIndex(1); } void Widget::on_btn_tab3_backtab2_2_clicked() { ui->tabWidget->setCurrentIndex(3); } void Widget::on_btn_settings_clicked() { ui->tabWidget->setCurrentIndex(2); } bool Widget::saveConfig(const QString &filePath, const std::map& params_to_set, const std::vector& color_vector_to_set) { QFile file(filePath); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qWarning() << "无法打开配置文件进行写入:" << filePath; return false; } QTextStream out(&file); // Qt 6 默认使用 UTF-8 编码,无需设置编码 // 保存每种颜色的参数 for (const auto &color : color_vector_to_set) { std::string color_lower = color; // 将颜色名称首字母小写以匹配键名 if (!color_lower.empty()) { color_lower[0] = static_cast(std::tolower(color_lower[0])); } try { // 使用 at() 方法访问元素 out << QString::fromStdString(color_lower) << "_L_min = " << QString::number(params_to_set.at(color_lower + "_L_min")) << "\n"; out << QString::fromStdString(color_lower) << "_L_max = " << QString::number(params_to_set.at(color_lower + "_L_max")) << "\n"; out << QString::fromStdString(color_lower) << "_a_min = " << QString::number(params_to_set.at(color_lower + "_a_min")) << "\n"; out << QString::fromStdString(color_lower) << "_a_max = " << QString::number(params_to_set.at(color_lower + "_a_max")) << "\n"; out << QString::fromStdString(color_lower) << "_b_min = " << QString::number(params_to_set.at(color_lower + "_b_min")) << "\n"; out << QString::fromStdString(color_lower) << "_b_max = " << QString::number(params_to_set.at(color_lower + "_b_max")) << "\n\n"; } catch (const std::out_of_range& e) { qWarning() << "缺少参数:" << QString::fromStdString(color_lower + "_L_min") << "等在颜色" << QString::fromStdString(color_lower); // 根据需要,您可以决定如何处理缺少的参数,例如设置默认值或跳过 continue; } } // 保存其他参数(不涉及颜色参数) for (const auto ¶m : params_to_set) { std::string key = param.first; // 如果不是颜色参数,则保存 if (key.find("_L_min") == std::string::npos && key.find("_L_max") == std::string::npos && key.find("_a_min") == std::string::npos && key.find("_a_max") == std::string::npos && key.find("_b_min") == std::string::npos && key.find("_b_max") == std::string::npos) { out << QString::fromStdString(key) << " = " << QString::number(param.second) << "\n"; } } file.close(); qDebug() << "配置已成功保存到" << filePath; return true; } bool Widget::loadConfig(const QString &filePath) { QFile file(filePath); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { qWarning() << "无法打开配置文件进行读取:" << filePath; return false; } QTextStream in(&file); // Qt 6 默认使用 UTF-8 编码,无需设置编码 // 清空当前配置 colors.clear(); params.clear(); while (!in.atEnd()) { QString line = in.readLine().trimmed(); // 跳过空行和注释行 if (line.isEmpty() || line.startsWith("#")) continue; // 处理键值对 QStringList keyValue = line.split("="); if (keyValue.size() != 2) continue; // 无效行 QString key = keyValue[0].trimmed(); QString valueStr = keyValue[1].trimmed(); bool ok; int value = valueStr.toInt(&ok); if (!ok) { qWarning() << "无效的数值:" << valueStr << "在行:" << line; continue; } std::string key_std = key.toStdString(); params[key_std] = value; // 检查是否为颜色参数(格式为 color_property,例如 green_L_min) size_t underscorePos = key_std.find('_'); if (underscorePos != std::string::npos) { std::string colorName = key_std.substr(0, underscorePos); size_t second_underscore = key_std.find('_', underscorePos + 1); if (second_underscore != std::string::npos) { // 提取第二个部分,例如 "L"、"a"、"b" std::string param_type = key_std.substr(underscorePos + 1, second_underscore - underscorePos - 1); // 过滤不需要的参数类型 if ((param_type != "L") && (param_type != "a") && (param_type != "b")) continue; // 避免重复添加颜色 if (std::find(colors.begin(), colors.end(), colorName) == colors.end()) { colors.push_back(colorName); } } } } file.close(); qDebug() << "配置已成功从" << filePath << "加载"; // qDebug()<comboBox_colorlist->currentText().toLocal8Bit().constData(); if (!(std::find(colors.begin(), colors.end(), current_color) != colors.end())) return; params[current_color+"_L_min"]=ui->spinBox_L_min->value(); params[current_color+"_L_max"]=ui->spinBox_L_max->value(); params[current_color+"_a_min"]=ui->spinBox_A_min->value(); params[current_color+"_a_max"]=ui->spinBox_A_max->value(); params[current_color+"_b_min"]=ui->spinBox_B_min->value(); params[current_color+"_b_max"]=ui->spinBox_B_max->value(); saveConfig(getConfigDirectory()+"/color_range_config.txt",params,colors); } void Widget::update_colorlist() { ui->comboBox_colorlist->clear(); for(auto color :colors) { ui->comboBox_colorlist->addItem(QString::fromStdString(color)); } } void Widget::on_comboBox_colorlist_currentIndexChanged(int index) { std::string current_color=ui->comboBox_colorlist->currentText().toLocal8Bit().constData(); if (!(std::find(colors.begin(), colors.end(), current_color) != colors.end())) return; int l_min = params[current_color+"_L_min"]; int l_max = params[current_color+"_L_max"]; int a_min = params[current_color+"_a_min"]; int a_max = params[current_color+"_a_max"]; int b_min = params[current_color+"_b_min"]; int b_max = params[current_color+"_b_max"]; ui->spinBox_L_max->setValue(l_max); ui->spinBox_L_min->setValue(l_min); ui->spinBox_A_max->setValue(a_max); ui->spinBox_A_min->setValue(a_min); ui->spinBox_B_max->setValue(b_max); ui->spinBox_B_min->setValue(b_min); } void Widget::on_btn_add_color_clicked() { std::string current_color=ui->lineEdit_color->text().toLocal8Bit().constData(); if(current_color== "") return; if (!(std::find(colors.begin(), colors.end(), current_color) != colors.end())) colors.push_back(current_color); else return; params[current_color+"_L_min"]=ui->spinBox_L_min->value(); params[current_color+"_L_max"]=ui->spinBox_L_max->value(); params[current_color+"_a_min"]=ui->spinBox_A_min->value(); params[current_color+"_a_max"]=ui->spinBox_A_max->value(); params[current_color+"_b_min"]=ui->spinBox_B_min->value(); params[current_color+"_b_max"]=ui->spinBox_B_max->value(); saveConfig(getConfigDirectory()+"/color_range_config.txt",params,colors); update_colorlist(); ui->lineEdit_color->clear(); } void Widget::on_btn_del_color_clicked() { // 获取当前选中的颜色文本 QString currentColorQStr = ui->comboBox_colorlist->currentText(); if (currentColorQStr.isEmpty()) { QMessageBox::warning(this, "删除颜色", "当前没有选中的颜色。"); return; } std::string currentColor = currentColorQStr.toStdString(); std::string color_lower = currentColor; if (!color_lower.empty()) { color_lower[0] = static_cast(std::tolower(color_lower[0])); } // 确认删除 QMessageBox::StandardButton reply; reply = QMessageBox::question(this, "删除颜色", QString("确定要删除颜色 '%1' 吗?").arg(currentColorQStr), QMessageBox::Yes | QMessageBox::No); if (reply != QMessageBox::Yes) return; // 查找颜色在 colors 向量中的位置 auto it = std::find(colors.begin(), colors.end(), currentColor); if (it != colors.end()) { // 计算索引 size_t index = std::distance(colors.begin(), it); // 从 colors 向量中删除 colors.erase(it); // 从 params 映射中删除相关参数 params.erase(color_lower + "_L_min"); params.erase(color_lower + "_L_max"); params.erase(color_lower + "_a_min"); params.erase(color_lower + "_a_max"); params.erase(color_lower + "_b_min"); params.erase(color_lower + "_b_max"); // 更新配置文件 saveConfig(getConfigDirectory()+"/color_range_config.txt",params,colors); // 更新 UI update_colorlist(); // 如果还有颜色,选择被删除颜色后的第一个颜色 if (!colors.empty()) { if (index >= colors.size()) index = colors.size() - 1; ui->comboBox_colorlist->setCurrentIndex(static_cast(index)); } else { // 如果没有颜色,重置所有 spinBox ui->spinBox_L_min->setValue(0); ui->spinBox_L_max->setValue(0); ui->spinBox_A_min->setValue(0); ui->spinBox_A_max->setValue(0); ui->spinBox_B_min->setValue(0); ui->spinBox_B_max->setValue(0); } QMessageBox::information(this, "删除颜色", QString("颜色 '%1' 已成功删除。").arg(currentColorQStr)); } else { QMessageBox::warning(this, "删除颜色", "未找到选中的颜色。"); } } void Widget::on_btn_reset_color_clicked() { // 新增全部还原槽 // 确认重置 QMessageBox::StandardButton reply; reply = QMessageBox::question(this, "全部还原", "确定要将配置还原为默认设置吗?", QMessageBox::Yes | QMessageBox::No); if (reply != QMessageBox::Yes) return; // 初始化默认配置 initDefaultConfig(); saveConfig(getConfigDirectory()+"/color_range_config.txt",params,colors); // 更新 UI update_colorlist(); // 选择第一个颜色(如果有) if (!colors.empty()) { ui->comboBox_colorlist->setCurrentIndex(0); } else { // 如果没有颜色,重置所有 spinBox ui->spinBox_L_min->setValue(0); ui->spinBox_L_max->setValue(0); ui->spinBox_A_min->setValue(0); ui->spinBox_A_max->setValue(0); ui->spinBox_B_min->setValue(0); ui->spinBox_B_max->setValue(0); } QMessageBox::information(this, "全部还原", "配置已成功还原为默认设置。"); }