|
|
@@ -300,10 +300,13 @@ int main(int argc, char* argv[]) {
|
|
|
|
|
|
// ========== 本机画面输出(HDMI显示器) ==========
|
|
|
string app_name = cfg.value("app_name", "钢包工位视频监测系统");
|
|
|
- bool hdmi_output = cfg.value("hdmi_output", true);
|
|
|
+ // 两个独立显示器输出: 网格(普通通道) + 钢包位(工位左右摄像头)
|
|
|
+ bool grid_output = cfg.value("grid_output", true);
|
|
|
+ bool steel_output = cfg.value("steel_output", true);
|
|
|
int hdmi_grid_cols = cfg.value("hdmi_grid_cols", 0);
|
|
|
bool hdmi_fullscreen = cfg.value("hdmi_fullscreen", true);
|
|
|
- cout << "[Display] HDMI画面输出: " << (hdmi_output ? "启用" : "禁用")
|
|
|
+ cout << "[Display] 网格输出: " << (grid_output ? "启用" : "禁用")
|
|
|
+ << " 钢包位输出: " << (steel_output ? "启用" : "禁用")
|
|
|
<< " 网格列=" << hdmi_grid_cols
|
|
|
<< " 全屏=" << (hdmi_fullscreen ? "是" : "否") << endl;
|
|
|
|
|
|
@@ -500,8 +503,15 @@ int main(int argc, char* argv[]) {
|
|
|
mutex channels_mtx;
|
|
|
vector<shared_ptr<ChannelState>> channels;
|
|
|
|
|
|
+ // 钢包位专用通道(id=100左, 101右), 跟随工位切换, 独立于普通rtsp通道
|
|
|
+ static const int kBucketLeftId = 100;
|
|
|
+ static const int kBucketRightId = 101;
|
|
|
+ mutex bucket_channels_mtx;
|
|
|
+ vector<shared_ptr<ChannelState>> bucket_channels;
|
|
|
+
|
|
|
WebServer web;
|
|
|
- DisplayOutput displayOutput;
|
|
|
+ DisplayOutput displayGrid; // 普通通道网格(自动布局)
|
|
|
+ DisplayOutput displayBucket; // 钢包位(当前工位左右摄像头)
|
|
|
web.setPort(web_port);
|
|
|
|
|
|
// 登录凭据(默认admin/admin), 持久化于config.json的web_auth
|
|
|
@@ -562,42 +572,50 @@ int main(int argc, char* argv[]) {
|
|
|
cout << "[Main] 视频通道: " << channels.size() << "/"
|
|
|
<< kMaxChannels << " 路" << endl;
|
|
|
|
|
|
+ // 钢包位专用通道(占位创建, 工位激活时替换为工位左右摄像头)
|
|
|
+ {
|
|
|
+ auto chL = makeChannel(kBucketLeftId, "钢包位左", "");
|
|
|
+ auto chR = makeChannel(kBucketRightId, "钢包位右", "");
|
|
|
+ lock_guard<mutex> lk(bucket_channels_mtx);
|
|
|
+ bucket_channels.push_back(chL);
|
|
|
+ bucket_channels.push_back(chR);
|
|
|
+ }
|
|
|
+ cout << "[Main] 钢包位专用通道: 左CH" << kBucketLeftId
|
|
|
+ << " 右CH" << kBucketRightId << endl;
|
|
|
+
|
|
|
// ========== 工位切换回调 ==========
|
|
|
- // 工位切换时,始终使用 channel 0 (左) 和 channel 1 (右)
|
|
|
+ // 钢包位专用通道跟随工位切换(左右摄像头), 普通rtsp通道不受影响
|
|
|
bucketManager.setActivateCallback([&](const BucketInfo* old, const BucketInfo* cur) {
|
|
|
cerr << "[Bucket] 回调触发: old=" << (old ? old->description : "null")
|
|
|
<< " cur=" << (cur ? cur->description : "null") << endl;
|
|
|
- // 停止旧工位摄像头
|
|
|
+ // 停止旧工位钢包位摄像头
|
|
|
if (old && old->id >= 0) {
|
|
|
- lock_guard<mutex> lk(channels_mtx);
|
|
|
- for (auto& ch : channels) {
|
|
|
- if (ch->id == 0 || ch->id == 1) {
|
|
|
- ch->capture->stop();
|
|
|
- }
|
|
|
- }
|
|
|
- cerr << "[Bucket] 停止工位 " << old->description << " 摄像头" << endl;
|
|
|
+ lock_guard<mutex> lk(bucket_channels_mtx);
|
|
|
+ for (auto& ch : bucket_channels) ch->capture->stop();
|
|
|
+ cerr << "[Bucket] 停止钢包位摄像头" << endl;
|
|
|
}
|
|
|
- // 启动新工位摄像头
|
|
|
+ // 启动新工位钢包位摄像头
|
|
|
if (cur && cur->id >= 0) {
|
|
|
- lock_guard<mutex> lk(channels_mtx);
|
|
|
- cerr << "[Bucket] channels数量: " << channels.size() << endl;
|
|
|
- for (auto& ch : channels) {
|
|
|
- cerr << "[Bucket] 检查channel id=" << ch->id << endl;
|
|
|
- if (ch->id == 0 && !cur->leftCamera.url.empty()) {
|
|
|
+ lock_guard<mutex> lk(bucket_channels_mtx);
|
|
|
+ cerr << "[Bucket] 激活工位: " << cur->description << endl;
|
|
|
+ for (auto& ch : bucket_channels) {
|
|
|
+ if (ch->id == kBucketLeftId && !cur->leftCamera.url.empty()) {
|
|
|
ch->capture->stop();
|
|
|
- cerr << "[Bucket] 创建左摄像头 capture url=" << cur->leftCamera.url << endl;
|
|
|
- ch->capture = make_shared<RtspCapture>(0, cur->leftCamera.url, cur->description + "_左");
|
|
|
+ ch->capture = make_shared<RtspCapture>(kBucketLeftId,
|
|
|
+ cur->leftCamera.url, cur->description + "_左");
|
|
|
ch->capture->start();
|
|
|
- cerr << "[Bucket] 启动 " << cur->description << " 左摄像头 CH0" << endl;
|
|
|
+ cerr << "[Bucket] 钢包位左摄像头: " << cur->leftCamera.url << endl;
|
|
|
}
|
|
|
- if (ch->id == 1 && !cur->rightCamera.url.empty()) {
|
|
|
+ if (ch->id == kBucketRightId && !cur->rightCamera.url.empty()) {
|
|
|
ch->capture->stop();
|
|
|
- cerr << "[Bucket] 创建右摄像头 capture url=" << cur->rightCamera.url << endl;
|
|
|
- ch->capture = make_shared<RtspCapture>(1, cur->rightCamera.url, cur->description + "_右");
|
|
|
+ ch->capture = make_shared<RtspCapture>(kBucketRightId,
|
|
|
+ cur->rightCamera.url, cur->description + "_右");
|
|
|
ch->capture->start();
|
|
|
- cerr << "[Bucket] 启动 " << cur->description << " 右摄像头 CH1" << endl;
|
|
|
+ cerr << "[Bucket] 钢包位右摄像头: " << cur->rightCamera.url << endl;
|
|
|
}
|
|
|
}
|
|
|
+ } else {
|
|
|
+ cerr << "[Bucket] 工位取消激活" << endl;
|
|
|
}
|
|
|
idleDetector.reset();
|
|
|
cerr << "[Bucket] 回调完成" << endl;
|
|
|
@@ -619,6 +637,8 @@ int main(int argc, char* argv[]) {
|
|
|
s["fall_thresh"] = fall_thresh;
|
|
|
s["fall_frames"] = fall_frames;
|
|
|
s["fall_relays"] = fall_relays;
|
|
|
+ s["grid_output"] = grid_output;
|
|
|
+ s["steel_output"] = steel_output;
|
|
|
json arr = json::array();
|
|
|
{
|
|
|
lock_guard<mutex> lk(channels_mtx);
|
|
|
@@ -1261,6 +1281,27 @@ int main(int argc, char* argv[]) {
|
|
|
}
|
|
|
if (j.contains("conf_thresh"))
|
|
|
full["conf_thresh"] = j["conf_thresh"];
|
|
|
+ if (j.contains("pose_enabled")) {
|
|
|
+ bool en = j["pose_enabled"].get<bool>();
|
|
|
+ full["pose_enabled"] = en;
|
|
|
+ pose_enabled = en;
|
|
|
+ cout << "[Pose] 姿态识别: " << (en ? "启用" : "禁用")
|
|
|
+ << " (重启后生效)" << endl;
|
|
|
+ }
|
|
|
+ if (j.contains("grid_output")) {
|
|
|
+ bool v = j["grid_output"].get<bool>();
|
|
|
+ full["grid_output"] = v;
|
|
|
+ grid_output = v;
|
|
|
+ cout << "[Display] 网格输出: " << (v ? "启用" : "禁用")
|
|
|
+ << " (重启后生效)" << endl;
|
|
|
+ }
|
|
|
+ if (j.contains("steel_output")) {
|
|
|
+ bool v = j["steel_output"].get<bool>();
|
|
|
+ full["steel_output"] = v;
|
|
|
+ steel_output = v;
|
|
|
+ cout << "[Display] 钢包位输出: " << (v ? "启用" : "禁用")
|
|
|
+ << " (重启后生效)" << endl;
|
|
|
+ }
|
|
|
if (j.contains("alarm_retention_days")) {
|
|
|
int v = j["alarm_retention_days"].get<int>();
|
|
|
if (v < 1) v = 1;
|
|
|
@@ -1394,6 +1435,7 @@ int main(int argc, char* argv[]) {
|
|
|
web.setGetHandler("/api/pose/status", [&]() -> string {
|
|
|
json r;
|
|
|
r["pose_enabled"] = pose_ok;
|
|
|
+ r["pose_cfg_enabled"] = pose_enabled;
|
|
|
r["model"] = pose_model_path;
|
|
|
r["fall_thresh"] = fall_thresh;
|
|
|
r["fall_frames"] = fall_frames;
|
|
|
@@ -1462,7 +1504,8 @@ int main(int argc, char* argv[]) {
|
|
|
});
|
|
|
|
|
|
web.start();
|
|
|
- displayOutput.start(hdmi_output, hdmi_grid_cols, app_name, hdmi_fullscreen);
|
|
|
+ displayGrid.start(grid_output, hdmi_grid_cols, app_name + " - 通道网格", hdmi_fullscreen);
|
|
|
+ displayBucket.start(steel_output, 2, app_name + " - 钢包位", hdmi_fullscreen);
|
|
|
cout << "[Main] Ctrl+C 退出" << endl;
|
|
|
|
|
|
// 自动激活第一个工位(如果有配置)
|
|
|
@@ -1595,6 +1638,12 @@ int main(int argc, char* argv[]) {
|
|
|
lock_guard<mutex> lk(channels_mtx);
|
|
|
ch_snap = channels;
|
|
|
}
|
|
|
+ // 钢包位专用通道(跟随工位切换)
|
|
|
+ {
|
|
|
+ lock_guard<mutex> lk(bucket_channels_mtx);
|
|
|
+ for (auto& bc : bucket_channels)
|
|
|
+ ch_snap.push_back(bc);
|
|
|
+ }
|
|
|
for (auto& ch : ch_snap) {
|
|
|
Nv12Frame nv12;
|
|
|
if (!ch->capture->grabNv12(nv12)) {
|
|
|
@@ -1885,11 +1934,17 @@ int main(int argc, char* argv[]) {
|
|
|
ch->disp_seq.fetch_add(1, std::memory_order_relaxed);
|
|
|
}
|
|
|
// 输出到本机显示器(HDMI)
|
|
|
- if (displayOutput.isRunning()) {
|
|
|
+ if (displayGrid.isRunning() && ch->id < 100) {
|
|
|
char dlbl[64];
|
|
|
snprintf(dlbl, sizeof(dlbl), "CH%d %s",
|
|
|
ch->id, ch->capture->name().c_str());
|
|
|
- displayOutput.setFrame(ch->id, display, dlbl);
|
|
|
+ displayGrid.setFrame(ch->id, display, dlbl);
|
|
|
+ }
|
|
|
+ if (displayBucket.isRunning() && ch->id >= 100) {
|
|
|
+ char dlbl[64];
|
|
|
+ snprintf(dlbl, sizeof(dlbl), "%s",
|
|
|
+ ch->capture->name().c_str());
|
|
|
+ displayBucket.setFrame(ch->id, display, dlbl);
|
|
|
}
|
|
|
}
|
|
|
}
|