Add continuous TensorRT camera inference and ready-to-run Orin package
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
#include <condition_variable>
|
||||
#include <deque>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace cms {
|
||||
// FIFO with bounded ownership. Normal close drains; cancellation may discard.
|
||||
template<class T> class BoundedQueue {
|
||||
public:
|
||||
explicit BoundedQueue(std::size_t capacity):capacity_(capacity) {
|
||||
if(!capacity)throw std::invalid_argument("Queue capacity must be positive");
|
||||
}
|
||||
bool push(T item) {
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
changed_.wait(lock,[&]{return closed_||items_.size()<capacity_;});
|
||||
if(closed_)return false;
|
||||
items_.push_back(std::move(item));changed_.notify_all();return true;
|
||||
}
|
||||
std::optional<T> pop() {
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
changed_.wait(lock,[&]{return closed_||!items_.empty();});
|
||||
if(items_.empty())return {};
|
||||
T item=std::move(items_.front());items_.pop_front();changed_.notify_all();return item;
|
||||
}
|
||||
void close(bool discard=false) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
closed_=true;if(discard)items_.clear();changed_.notify_all();
|
||||
}
|
||||
private:
|
||||
const std::size_t capacity_;
|
||||
std::mutex mutex_;std::condition_variable changed_;
|
||||
std::deque<T> items_;bool closed_{};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
#include "image.hpp"
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
namespace cms {
|
||||
struct CameraConfig {
|
||||
std::string device{"/dev/video5"};
|
||||
int width{1920},height{1536},fps{30},buffers{4},timeout_ms{2000};
|
||||
};
|
||||
struct CapturedFrame {
|
||||
CaptureMetadata metadata;
|
||||
std::shared_ptr<const BgrImage> image;
|
||||
};
|
||||
// The capture thread exclusively owns read(); requestStop() is thread safe.
|
||||
class Camera {
|
||||
public:
|
||||
explicit Camera(CameraConfig config);
|
||||
~Camera();
|
||||
Camera(Camera const&)=delete;
|
||||
Camera& operator=(Camera const&)=delete;
|
||||
std::optional<CapturedFrame> read();
|
||||
void requestStop() noexcept;
|
||||
private:
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> impl_;
|
||||
};
|
||||
std::uint64_t monotonicNs();
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
#include "camera.hpp"
|
||||
#include "frame_result.hpp"
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
|
||||
namespace cms {
|
||||
struct PipelineConfig {
|
||||
CameraConfig camera;
|
||||
std::filesystem::path engines,door_mask;
|
||||
double detector_confidence{0.30};
|
||||
int max_people{3};
|
||||
double warning_relative{0.30},release_relative{0.45};
|
||||
std::size_t queue_capacity{2};
|
||||
bool door_mask_calibration_verified{false};
|
||||
};
|
||||
struct PipelineStats {
|
||||
std::uint64_t captured{},published{},driver_sequence_gaps{},discarded_error_frames{};
|
||||
double mean_capture_to_result_ms{};
|
||||
};
|
||||
// One lifecycle per instance. start() warms engines before starting capture.
|
||||
// Runs continuously until requestStop() or a worker failure.
|
||||
// latest() reads the immutable result variable without removing or acknowledging it.
|
||||
// No result queue, consumer registration or frame-count termination.
|
||||
// Stop is safe from any thread;
|
||||
// destruction closes queues, interrupts capture and joins every worker.
|
||||
class CameraPipeline {
|
||||
public:
|
||||
explicit CameraPipeline(PipelineConfig config);
|
||||
~CameraPipeline();
|
||||
CameraPipeline(CameraPipeline const&)=delete;
|
||||
CameraPipeline& operator=(CameraPipeline const&)=delete;
|
||||
void start();
|
||||
FrameResultPtr latest() const;
|
||||
bool running() const noexcept;
|
||||
void requestStop() noexcept;
|
||||
void join();
|
||||
PipelineStats stats() const;
|
||||
private:
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> impl_;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
#pragma once
|
||||
|
||||
#include <NvInfer.h>
|
||||
#include <NvInferVersion.h>
|
||||
#include <cuda_runtime.h>
|
||||
|
||||
#include <cstring>
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace fangjia {
|
||||
|
||||
inline void checkCuda(cudaError_t status, char const* expression, char const* file, int line) {
|
||||
if (status != cudaSuccess) {
|
||||
throw std::runtime_error(
|
||||
std::string("CUDA failure: ") + expression + " at " + file + ":" +
|
||||
std::to_string(line) + ": " + cudaGetErrorString(status));
|
||||
}
|
||||
}
|
||||
|
||||
#define CUDA_CHECK(expr) ::fangjia::checkCuda((expr), #expr, __FILE__, __LINE__)
|
||||
|
||||
class TrtLogger final : public nvinfer1::ILogger {
|
||||
public:
|
||||
explicit TrtLogger(Severity minimum = Severity::kWARNING) : minimum_(minimum) {}
|
||||
|
||||
void log(Severity severity, char const* message) noexcept override {
|
||||
if (severity <= minimum_) {
|
||||
std::cerr << "[TensorRT] " << message << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
Severity minimum_;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct TrtDeleter {
|
||||
void operator()(T* object) const noexcept { delete object; }
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
using TrtUniquePtr = std::unique_ptr<T, TrtDeleter<T>>;
|
||||
|
||||
inline std::vector<std::uint8_t> readBinary(std::filesystem::path const& path) {
|
||||
std::ifstream stream(path, std::ios::binary | std::ios::ate);
|
||||
if (!stream) {
|
||||
throw std::runtime_error("Cannot open " + path.string());
|
||||
}
|
||||
auto const size = stream.tellg();
|
||||
if(size<=0)throw std::runtime_error("Empty or unreadable engine: "+path.string());
|
||||
stream.seekg(0);
|
||||
std::vector<std::uint8_t> bytes(static_cast<std::size_t>(size));
|
||||
if(!stream.read(reinterpret_cast<char*>(bytes.data()), size))throw std::runtime_error("Truncated engine: "+path.string());
|
||||
return bytes;
|
||||
}
|
||||
|
||||
inline void writeBinary(std::filesystem::path const& path, void const* data, std::size_t size) {
|
||||
std::filesystem::create_directories(path.parent_path());
|
||||
std::ofstream stream(path, std::ios::binary);
|
||||
if (!stream) {
|
||||
throw std::runtime_error("Cannot write " + path.string());
|
||||
}
|
||||
stream.write(static_cast<char const*>(data), static_cast<std::streamsize>(size));
|
||||
}
|
||||
|
||||
inline std::size_t volume(nvinfer1::Dims const& dims) {
|
||||
std::size_t value = 1;
|
||||
for (int index = 0; index < dims.nbDims; ++index) {
|
||||
if (dims.d[index] < 0) {
|
||||
throw std::runtime_error("Unresolved dynamic tensor dimension");
|
||||
}
|
||||
value *= static_cast<std::size_t>(dims.d[index]);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
inline std::size_t dataTypeSize(nvinfer1::DataType type) {
|
||||
switch (type) {
|
||||
case nvinfer1::DataType::kFLOAT: return 4;
|
||||
case nvinfer1::DataType::kHALF: return 2;
|
||||
case nvinfer1::DataType::kINT8: return 1;
|
||||
case nvinfer1::DataType::kINT32: return 4;
|
||||
#if NV_TENSORRT_MAJOR >= 10
|
||||
case nvinfer1::DataType::kINT64: return 8;
|
||||
#endif
|
||||
case nvinfer1::DataType::kBOOL: return 1;
|
||||
default: throw std::runtime_error("Unsupported TensorRT data type");
|
||||
}
|
||||
}
|
||||
|
||||
class CudaEventTimer {
|
||||
public:
|
||||
CudaEventTimer() {
|
||||
CUDA_CHECK(cudaEventCreate(&start_));
|
||||
CUDA_CHECK(cudaEventCreate(&end_));
|
||||
}
|
||||
~CudaEventTimer() {
|
||||
cudaEventDestroy(start_);
|
||||
cudaEventDestroy(end_);
|
||||
}
|
||||
void start(cudaStream_t stream) { CUDA_CHECK(cudaEventRecord(start_, stream)); }
|
||||
float stop(cudaStream_t stream) {
|
||||
CUDA_CHECK(cudaEventRecord(end_, stream));
|
||||
CUDA_CHECK(cudaEventSynchronize(end_));
|
||||
float milliseconds = 0.0F;
|
||||
CUDA_CHECK(cudaEventElapsedTime(&milliseconds, start_, end_));
|
||||
return milliseconds;
|
||||
}
|
||||
|
||||
private:
|
||||
cudaEvent_t start_{};
|
||||
cudaEvent_t end_{};
|
||||
};
|
||||
|
||||
} // namespace fangjia
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
#include "camera_pipeline.hpp"
|
||||
namespace cms {
|
||||
PipelineConfig loadConfig(std::filesystem::path const& path);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
#include "pose_engine.hpp"
|
||||
#include "gpu_preprocess.hpp"
|
||||
namespace cms {
|
||||
class Detector {
|
||||
public:
|
||||
Detector(std::filesystem::path const& path, fangjia::TrtLogger& logger, float confidence, int maxPeople);
|
||||
std::vector<fangjia::Box> infer(cv::Mat const& frame,cudaStream_t stream,int filterHeight=0);
|
||||
private:
|
||||
fangjia::TrtEngine engine_;
|
||||
float confidence_;
|
||||
int maxPeople_, width_, height_;
|
||||
std::string input_,output_;
|
||||
std::unique_ptr<YoloPreprocess> preprocess_;
|
||||
std::vector<float> outputHost_;
|
||||
};
|
||||
cv::Mat maskOtherPeople(cv::Mat const& frame,std::vector<fangjia::Box> const& boxes,size_t target);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <opencv2/core.hpp>
|
||||
#include <filesystem>
|
||||
#include <map>
|
||||
#include <set>
|
||||
using Json = nlohmann::ordered_json;
|
||||
namespace cms {
|
||||
struct DoorOptions {
|
||||
std::filesystem::path constant, directory;
|
||||
std::string format{"auto"};
|
||||
std::set<std::string> labels{"door", "车门"};
|
||||
int offset{};
|
||||
bool require{};
|
||||
double warning{0.30}, release{0.45};
|
||||
};
|
||||
class DoorRisk {
|
||||
public:
|
||||
DoorRisk(DoorOptions options, cv::Size size);
|
||||
void initialize() { load(0); }
|
||||
Json evaluate(int frame, Json& record);
|
||||
cv::Mat const& mask() const { return mask_; }
|
||||
private:
|
||||
void load(int frame);
|
||||
cv::Mat rasterize(std::filesystem::path const& path) const;
|
||||
DoorOptions options_;
|
||||
cv::Size size_;
|
||||
std::string format_, status_;
|
||||
std::filesystem::path loadedPath_;
|
||||
std::map<int, std::filesystem::path> paths_;
|
||||
cv::Mat mask_, distance_;
|
||||
bool cached_{};
|
||||
};
|
||||
double rounded(double value, int digits);
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
#pragma once
|
||||
// Public downstream contract. Owns its data; no GPU, JSON or network handles.
|
||||
#include "image.hpp"
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace cms {
|
||||
struct DoorMaskResult {
|
||||
std::string status;
|
||||
int annotation_index{};
|
||||
std::optional<std::string> path;
|
||||
std::string format;
|
||||
int area_px{};
|
||||
};
|
||||
struct RegionRisk {
|
||||
std::string status;
|
||||
std::optional<double> risk_score;
|
||||
std::string risk_level;
|
||||
std::string decision;
|
||||
std::string source;
|
||||
int track_id{};
|
||||
std::string side;
|
||||
std::optional<std::string> state;
|
||||
// Unavailable evidence has no geometric measurements, matching the reference.
|
||||
std::optional<int> region_area_px, door_overlap_px;
|
||||
std::optional<double> region_overlap_ratio, min_distance_px;
|
||||
std::optional<double> region_scale_px, distance_relative;
|
||||
};
|
||||
struct RiskResult {
|
||||
std::string status;
|
||||
std::optional<double> risk_score;
|
||||
std::string risk_level;
|
||||
std::string decision;
|
||||
std::optional<std::string> source;
|
||||
std::optional<int> track_id;
|
||||
std::optional<std::string> side;
|
||||
DoorMaskResult door_mask;
|
||||
double warning_distance_relative{}, release_distance_relative{};
|
||||
std::vector<RegionRisk> regions;
|
||||
};
|
||||
struct PosePoint { double x{},y{},score{}; };
|
||||
struct HandState {
|
||||
std::string state, source;
|
||||
bool limb_consensus{}, distance_eligible{}, alarm_eligible{};
|
||||
};
|
||||
struct PersonResult {
|
||||
int track_id{};
|
||||
double confidence{};
|
||||
std::array<double,4> box{};
|
||||
std::array<PosePoint,133> selected{}, smoothed{}, temporal{};
|
||||
std::array<HandState,2> hands; // left, right
|
||||
};
|
||||
struct HandRegion {
|
||||
int person_id{},track_id{};
|
||||
std::string side;
|
||||
std::array<PosePoint,21> keypoints{};
|
||||
};
|
||||
struct StageTiming {
|
||||
double detector_ms{}, pose_ms{}, fusion_risk_ms{}, capture_to_result_ms{};
|
||||
};
|
||||
struct FrameResult {
|
||||
int frame_index{};
|
||||
std::vector<int> track_ids;
|
||||
std::vector<std::array<double,4>> boxes;
|
||||
RiskResult risk;
|
||||
CaptureMetadata capture;
|
||||
StageTiming timing;
|
||||
std::vector<PersonResult> persons;
|
||||
std::vector<HandRegion> hand_regions;
|
||||
// Both images belong to this result. The constant mask is shared across frames.
|
||||
std::shared_ptr<const BgrImage> image;
|
||||
std::shared_ptr<const BinaryMask> door_mask_pixels;
|
||||
bool door_mask_calibration_verified{};
|
||||
};
|
||||
using FrameResultPtr = std::shared_ptr<const FrameResult>;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
#include "common.hpp"
|
||||
#include <npp.h>
|
||||
#include <opencv2/core.hpp>
|
||||
namespace cms {
|
||||
inline void nppCheck(NppStatus status) {if(status<0)throw std::runtime_error("NPP error "+std::to_string(int(status)));}
|
||||
inline NppStreamContext nppContext(cudaStream_t stream) {
|
||||
NppStreamContext c{};c.hStream=stream;CUDA_CHECK(cudaGetDevice(&c.nCudaDeviceId));cudaDeviceProp p{};CUDA_CHECK(cudaGetDeviceProperties(&p,c.nCudaDeviceId));
|
||||
c.nMultiProcessorCount=p.multiProcessorCount;c.nMaxThreadsPerMultiProcessor=p.maxThreadsPerMultiProcessor;c.nMaxThreadsPerBlock=p.maxThreadsPerBlock;c.nSharedMemPerBlock=p.sharedMemPerBlock;c.nCudaDevAttrComputeCapabilityMajor=p.major;c.nCudaDevAttrComputeCapabilityMinor=p.minor;CUDA_CHECK(cudaStreamGetFlags(stream,&c.nStreamFlags));return c;
|
||||
}
|
||||
class YoloPreprocess {
|
||||
public:
|
||||
YoloPreprocess(int width,int height):width_(width),height_(height) {CUDA_CHECK(cudaMalloc(&letter_,size_t(width)*height*3));}
|
||||
~YoloPreprocess(){cudaFree(source_);cudaFree(letter_);}
|
||||
void enqueue(cv::Mat const& frame,float* destination,cudaStream_t stream);
|
||||
private:
|
||||
int width_,height_;size_t capacity_{};unsigned char *source_{},*letter_{};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace cms {
|
||||
// Immutable when published. Stride is explicit; no borrowed camera/GPU memory.
|
||||
struct BgrImage {
|
||||
int width{}, height{}, stride{};
|
||||
std::vector<std::uint8_t> pixels;
|
||||
};
|
||||
struct BinaryMask {
|
||||
int width{}, height{}, stride{};
|
||||
std::vector<std::uint8_t> pixels;
|
||||
};
|
||||
struct CaptureMetadata {
|
||||
std::uint64_t frame_index{};
|
||||
std::uint32_t driver_sequence{};
|
||||
std::uint64_t driver_timestamp_ns{};
|
||||
std::uint64_t received_monotonic_ns{};
|
||||
std::uint64_t capture_monotonic_ns{};
|
||||
bool driver_timestamp_monotonic{};
|
||||
std::uint32_t skipped_driver_frames{};
|
||||
std::uint32_t discarded_error_frames{};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
#include "frame_result.hpp"
|
||||
#include <atomic>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace cms {
|
||||
// One immutable result variable. Readers neither remove nor acknowledge results.
|
||||
class LatestResult {
|
||||
public:
|
||||
void publish(FrameResultPtr result) {
|
||||
if(!result)throw std::invalid_argument("Cannot publish a null result");
|
||||
std::atomic_store_explicit(&latest_,std::move(result),std::memory_order_release);
|
||||
}
|
||||
FrameResultPtr load() const {
|
||||
return std::atomic_load_explicit(&latest_,std::memory_order_acquire);
|
||||
}
|
||||
private:
|
||||
FrameResultPtr latest_;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace fangjia {
|
||||
|
||||
struct Box {
|
||||
float x1{};
|
||||
float y1{};
|
||||
float x2{};
|
||||
float y2{};
|
||||
float score{};
|
||||
int trackId{};
|
||||
};
|
||||
|
||||
struct Keypoint {
|
||||
float x{};
|
||||
float y{};
|
||||
float score{};
|
||||
};
|
||||
|
||||
struct PersonPose {
|
||||
Box box;
|
||||
std::array<Keypoint, 133> keypoints{};
|
||||
};
|
||||
|
||||
enum class LimbState {
|
||||
Observed,
|
||||
Provisional,
|
||||
Recovered,
|
||||
Reassigned,
|
||||
Interpolated,
|
||||
PredictedShort,
|
||||
ReachableMedium,
|
||||
ReachableLong,
|
||||
};
|
||||
|
||||
inline char const* limbStateName(LimbState state) {
|
||||
switch (state) {
|
||||
case LimbState::Observed: return "observed";
|
||||
case LimbState::Provisional: return "provisional";
|
||||
case LimbState::Recovered: return "recovered";
|
||||
case LimbState::Reassigned: return "reassigned";
|
||||
case LimbState::Interpolated: return "interpolated";
|
||||
case LimbState::PredictedShort: return "predicted_short";
|
||||
case LimbState::ReachableMedium: return "reachable_medium";
|
||||
case LimbState::ReachableLong: return "reachable_long";
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
struct LimbObservation {
|
||||
int trackId{};
|
||||
bool left{};
|
||||
LimbState state{LimbState::ReachableLong};
|
||||
Keypoint elbow{};
|
||||
Keypoint wrist{};
|
||||
std::array<Keypoint, 21> hand{};
|
||||
Box personBox{};
|
||||
bool handValid{};
|
||||
bool alarmEligible{};
|
||||
float handQuality{};
|
||||
};
|
||||
|
||||
// Exact pre-V2 hand evidence consumed by the Python risk stage. These points
|
||||
// come from raw/isolated fusion before temporal reconstruction.
|
||||
struct GlobalHand {
|
||||
int personId{};
|
||||
int trackId{};
|
||||
bool left{};
|
||||
std::string source;
|
||||
std::string state;
|
||||
bool distanceEligible{};
|
||||
bool alarmEligible{};
|
||||
float score{};
|
||||
float spanPx{};
|
||||
std::array<Keypoint, 21> keypoints{};
|
||||
std::vector<std::pair<int, bool>> aliases;
|
||||
};
|
||||
|
||||
struct FusionMeta {
|
||||
bool limbConsensus{}, handValid{}, distanceEligible{}, alarmEligible{};
|
||||
double handScore{};
|
||||
std::string handSource, handState;
|
||||
};
|
||||
|
||||
struct PoseFrameResult {
|
||||
std::vector<std::array<FusionMeta, 2>> metadata;
|
||||
std::vector<PersonPose> selectedPeople;
|
||||
std::vector<PersonPose> smoothedPeople;
|
||||
std::vector<PersonPose> v2People;
|
||||
std::vector<LimbObservation> limbs;
|
||||
std::vector<GlobalHand> globalHands;
|
||||
};
|
||||
|
||||
} // namespace fangjia
|
||||
@@ -0,0 +1,108 @@
|
||||
#pragma once
|
||||
|
||||
#include "pipeline_types.hpp"
|
||||
#include "trt_engine.hpp"
|
||||
|
||||
#include <opencv2/core.hpp>
|
||||
|
||||
#include <deque>
|
||||
#include <array>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace fangjia {
|
||||
|
||||
class BoxTracker {
|
||||
public:
|
||||
std::vector<int> update(std::vector<Box>& boxes, int frameIndex, int width, int height);
|
||||
|
||||
private:
|
||||
struct Track { Box box; int lastFrame{}; };
|
||||
int nextId_{1};
|
||||
std::unordered_map<int, Track> tracks_;
|
||||
};
|
||||
|
||||
class RtmwEngine {
|
||||
public:
|
||||
RtmwEngine(
|
||||
std::filesystem::path const& enginePath,
|
||||
TrtLogger& logger,
|
||||
int maxSourceWidth,
|
||||
int maxSourceHeight,
|
||||
int maxPeople = 3);
|
||||
~RtmwEngine();
|
||||
|
||||
RtmwEngine(RtmwEngine const&) = delete;
|
||||
RtmwEngine& operator=(RtmwEngine const&) = delete;
|
||||
|
||||
std::vector<PersonPose> infer(
|
||||
cv::Mat const& frame,
|
||||
std::vector<Box> const& boxes,
|
||||
cudaStream_t stream,
|
||||
float* preprocessMs = nullptr,
|
||||
float* inferenceMs = nullptr,
|
||||
float* postprocessMs = nullptr);
|
||||
|
||||
void warmup(cudaStream_t stream);
|
||||
|
||||
private:
|
||||
TrtEngine engine_;
|
||||
int maxSourceWidth_{};
|
||||
int maxSourceHeight_{};
|
||||
int maxPeople_{};
|
||||
std::uint8_t* sourceDevice_{};
|
||||
std::uint8_t* cropsDevice_{};
|
||||
float* networkDevice_{};
|
||||
std::vector<double> centersHost_;
|
||||
std::vector<double> scalesHost_;
|
||||
std::vector<float> simccXHost_;
|
||||
std::vector<float> simccYHost_;
|
||||
};
|
||||
|
||||
class PoseFusionTracker {
|
||||
public:
|
||||
PoseFrameResult update(
|
||||
std::vector<PersonPose> const& raw,
|
||||
std::vector<PersonPose> const& isolated,
|
||||
int frameIndex);
|
||||
|
||||
private:
|
||||
struct Vec2 { double x{}; double y{}; };
|
||||
struct SideFusion {
|
||||
std::array<Vec2, 3> limbPrevious{};
|
||||
std::array<Vec2, 3> limbVelocity{};
|
||||
std::array<Vec2, 21> handPrevious{};
|
||||
std::array<Vec2, 21> handVelocity{};
|
||||
bool hasLimbPrevious{};
|
||||
bool hasLimbVelocity{};
|
||||
bool hasHandPrevious{};
|
||||
bool hasHandVelocity{};
|
||||
std::string limbSource;
|
||||
std::string handSource;
|
||||
};
|
||||
struct Endpoint {
|
||||
bool hasWrist{};
|
||||
Vec2 wrist{};
|
||||
Vec2 velocity{};
|
||||
std::array<Vec2, 21> handShape{};
|
||||
bool hasHandShape{};
|
||||
int lastObservedFrame{-1};
|
||||
int missedFrames{};
|
||||
bool hasLastElbow{};
|
||||
Vec2 lastElbow{};
|
||||
bool hasLastDirection{};
|
||||
Vec2 lastDirection{};
|
||||
};
|
||||
struct PersonState {
|
||||
std::array<SideFusion, 2> fusion;
|
||||
std::array<Endpoint, 2> endpoints;
|
||||
std::array<std::deque<std::array<double, 2>>, 2> boneSamples;
|
||||
std::array<Vec2, 133> smoothed{};
|
||||
bool hasSmoothed{};
|
||||
int lastFrame{-1};
|
||||
};
|
||||
std::unordered_map<int, PersonState> states_;
|
||||
};
|
||||
|
||||
} // namespace fangjia
|
||||
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
#include "door_risk.hpp"
|
||||
#include "pipeline_types.hpp"
|
||||
namespace cms {
|
||||
inline Json xy(std::vector<fangjia::PersonPose> const& people, int digits=3) {
|
||||
Json a=Json::array(); for(auto const& p:people) { Json k=Json::array();for(auto const& v:p.keypoints) k.push_back({rounded(v.x,digits),rounded(v.y,digits)});a.push_back(k); } return a;
|
||||
}
|
||||
inline Json scores(std::vector<fangjia::PersonPose> const& people) {
|
||||
Json a=Json::array();for(auto const& p:people) {Json k=Json::array();for(auto const& v:p.keypoints) k.push_back(v.score);a.push_back(k);}return a;
|
||||
}
|
||||
inline Json makeRecord(int frame,std::vector<fangjia::PersonPose> const& raw,std::vector<fangjia::PersonPose> const& isolated,fangjia::PoseFrameResult const& fused, bool diagnostics=true, bool visual=true) {
|
||||
Json r={{"frame_index",frame},{"track_ids",Json::array()},{"boxes",Json::array()},{"det_confidences",Json::array()}};
|
||||
for(auto const&p:raw) {auto const&b=p.box;r["track_ids"].push_back(b.trackId);r["boxes"].push_back({rounded(b.x1,3),rounded(b.y1,3),rounded(b.x2,3),rounded(b.y2,3)});r["det_confidences"].push_back(rounded(b.score,4));}
|
||||
if(diagnostics) {
|
||||
r["raw_keypoints"]=xy(raw,9);r["raw_scores"]=scores(raw);r["isolated_keypoints"]=xy(isolated,9);r["isolated_scores"]=scores(isolated);
|
||||
r["inference_boxes"]=Json::array();for(auto const&p:raw)r["inference_boxes"].push_back({p.box.x1,p.box.y1,p.box.x2,p.box.y2});
|
||||
}
|
||||
if(visual||diagnostics) {
|
||||
r["temporal_selected_keypoints"]=xy(fused.selectedPeople);r["temporal_keypoints"]=xy(fused.smoothedPeople);r["v2_keypoints"]=xy(fused.v2People);
|
||||
r["temporal_metadata"]=Json::array();r["v2_metadata"]=Json::array();
|
||||
for(size_t p=0;p<fused.metadata.size();++p) {
|
||||
Json m=Json::object(),v=Json::object();
|
||||
for(int s=0;s<2;++s) {auto side=s==0?"left":"right";auto const&t=fused.metadata[p][s];
|
||||
m[side]={{"limb_state",t.limbConsensus?"consensus":"inferred"},{"hand_source",t.handSource},{"hand_state",t.handState},{"distance_eligible",t.distanceEligible},{"alarm_eligible",t.alarmEligible},{"selected_hand_valid",t.handValid},{"selected_hand_score",t.handScore}};
|
||||
v[side]={{"state",fangjia::limbStateName(fused.limbs[p*2+s].state)}};
|
||||
} r["temporal_metadata"].push_back(m);r["v2_metadata"].push_back(v);
|
||||
}
|
||||
}
|
||||
r["global_hands"]=Json::array();
|
||||
for(auto const& h:fused.globalHands) {
|
||||
Json points=Json::array(),aliases=Json::array();double cx=0,cy=0;
|
||||
for(auto const& k:h.keypoints) {points.push_back({rounded(k.x,3),rounded(k.y,3)});cx+=k.x;cy+=k.y;}
|
||||
for(auto const&a:h.aliases) aliases.push_back({{"person_id",a.first},{"side",a.second?"left":"right"}});
|
||||
r["global_hands"].push_back({{"person_id",h.personId},{"side",h.left?"left":"right"},{"source",h.source},{"state",h.state},{"distance_eligible",h.distanceEligible},{"alarm_eligible",h.alarmEligible},{"score",rounded(h.score,4)},{"center",{rounded(cx/21,3),rounded(cy/21,3)}},{"span_px",rounded(h.spanPx,3)},{"keypoints",points},{"aliases",aliases}});
|
||||
}
|
||||
return r;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
#include "frame_result.hpp"
|
||||
#include "door_risk.hpp"
|
||||
namespace cms {
|
||||
template<class T> inline std::optional<T> optionalField(Json const& value,char const* key) {
|
||||
auto it=value.find(key);return it==value.end()||it->is_null()?std::nullopt:std::optional<T>(it->get<T>());
|
||||
}
|
||||
inline FrameResult makeFrameResult(Json const& record,Json const& risk,
|
||||
CaptureMetadata capture) {
|
||||
FrameResult out;
|
||||
out.frame_index=record.at("frame_index").get<int>();
|
||||
out.track_ids=record.at("track_ids").get<std::vector<int>>();
|
||||
out.boxes=record.at("boxes").get<std::vector<std::array<double,4>>>();
|
||||
out.capture=capture;
|
||||
auto& r=out.risk;
|
||||
r.status=risk.at("status");r.risk_score=optionalField<double>(risk,"risk_score");
|
||||
r.risk_level=risk.at("risk_level");r.decision=risk.at("decision");
|
||||
r.source=optionalField<std::string>(risk,"source");r.track_id=optionalField<int>(risk,"track_id");r.side=optionalField<std::string>(risk,"side");
|
||||
auto const& d=risk.at("door_mask");
|
||||
r.door_mask={d.at("status"),d.at("annotation_index"),optionalField<std::string>(d,"path"),d.at("format"),d.at("area_px")};
|
||||
r.warning_distance_relative=risk.at("warning_distance_relative");r.release_distance_relative=risk.at("release_distance_relative");
|
||||
for(auto const& j:risk.at("regions")) {
|
||||
RegionRisk v;
|
||||
v.status=j.at("status");v.risk_score=optionalField<double>(j,"risk_score");
|
||||
v.risk_level=j.at("risk_level");v.decision=j.at("decision");v.source=j.at("source");
|
||||
v.track_id=j.at("track_id");v.side=j.at("side");v.state=optionalField<std::string>(j,"state");
|
||||
v.region_area_px=optionalField<int>(j,"region_area_px");v.door_overlap_px=optionalField<int>(j,"door_overlap_px");
|
||||
v.region_overlap_ratio=optionalField<double>(j,"region_overlap_ratio");v.min_distance_px=optionalField<double>(j,"min_distance_px");
|
||||
v.region_scale_px=optionalField<double>(j,"region_scale_px");v.distance_relative=optionalField<double>(j,"distance_relative");
|
||||
r.regions.push_back(std::move(v));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
#include "camera_pipeline.hpp"
|
||||
#include <atomic>
|
||||
#include <csignal>
|
||||
#include <pthread.h>
|
||||
#include <stdexcept>
|
||||
#include <thread>
|
||||
|
||||
namespace cms {
|
||||
// Construct before starting workers so all threads inherit the signal mask.
|
||||
// Stop requests run in an ordinary thread, never an asynchronous signal handler.
|
||||
class StopSignals {
|
||||
public:
|
||||
explicit StopSignals(CameraPipeline& pipeline):pipeline_(pipeline) {
|
||||
sigemptyset(&signals_);sigaddset(&signals_,SIGINT);sigaddset(&signals_,SIGTERM);
|
||||
if(pthread_sigmask(SIG_BLOCK,&signals_,&old_)!=0)throw std::runtime_error("Cannot block termination signals");
|
||||
try {
|
||||
thread_=std::thread([this]{
|
||||
while(!done_) {
|
||||
timespec timeout{0,100000000};
|
||||
int signal=sigtimedwait(&signals_,nullptr,&timeout);
|
||||
if(signal==SIGINT||signal==SIGTERM){pipeline_.requestStop();break;}
|
||||
}
|
||||
});
|
||||
}catch(...){pthread_sigmask(SIG_SETMASK,&old_,nullptr);throw;}
|
||||
}
|
||||
~StopSignals(){done_=true;if(thread_.joinable())thread_.join();pthread_sigmask(SIG_SETMASK,&old_,nullptr);}
|
||||
StopSignals(StopSignals const&)=delete;
|
||||
StopSignals& operator=(StopSignals const&)=delete;
|
||||
private:
|
||||
CameraPipeline& pipeline_;
|
||||
sigset_t signals_{},old_{};
|
||||
std::atomic<bool> done_{false};
|
||||
std::thread thread_;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
|
||||
#include "common.hpp"
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
namespace fangjia {
|
||||
|
||||
class TrtEngine {
|
||||
public:
|
||||
TrtEngine(std::filesystem::path const& enginePath, TrtLogger& logger);
|
||||
~TrtEngine();
|
||||
|
||||
TrtEngine(TrtEngine const&) = delete;
|
||||
TrtEngine& operator=(TrtEngine const&) = delete;
|
||||
|
||||
void setInputShape(std::string const& name, nvinfer1::Dims const& shape);
|
||||
void prepare();
|
||||
void bindExternal(std::string const& name, void* pointer, std::size_t bytes);
|
||||
void useOwned(std::string const& name);
|
||||
bool enqueue(cudaStream_t stream);
|
||||
|
||||
void* device(std::string const& name);
|
||||
std::size_t bytes(std::string const& name) const;
|
||||
nvinfer1::Dims shape(std::string const& name) const;
|
||||
nvinfer1::DataType dataType(std::string const& name) const;
|
||||
std::vector<std::string> inputs() const;
|
||||
std::vector<std::string> outputs() const;
|
||||
|
||||
private:
|
||||
struct Buffer {
|
||||
void* owned{};
|
||||
void* active{};
|
||||
std::size_t capacity{};
|
||||
std::size_t required{};
|
||||
bool external{};
|
||||
};
|
||||
|
||||
Buffer& buffer(std::string const& name);
|
||||
Buffer const& buffer(std::string const& name) const;
|
||||
|
||||
TrtUniquePtr<nvinfer1::IRuntime> runtime_;
|
||||
TrtUniquePtr<nvinfer1::ICudaEngine> engine_;
|
||||
TrtUniquePtr<nvinfer1::IExecutionContext> context_;
|
||||
std::filesystem::path enginePath_;
|
||||
std::unordered_map<std::string, Buffer> buffers_;
|
||||
};
|
||||
|
||||
} // namespace fangjia
|
||||
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
#include "image.hpp"
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
namespace cms {
|
||||
// GPU NPP BT.601 UYVY -> RGB followed by a CUDA BGR channel swap.
|
||||
class UyvyConverter {
|
||||
public:
|
||||
UyvyConverter(int width,int height);
|
||||
~UyvyConverter();
|
||||
UyvyConverter(UyvyConverter const&)=delete;
|
||||
UyvyConverter& operator=(UyvyConverter const&)=delete;
|
||||
std::shared_ptr<const BgrImage> convert(void const* data,std::size_t stride);
|
||||
private:
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> impl_;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
#include "frame_result.hpp"
|
||||
#include <opencv2/core.hpp>
|
||||
|
||||
namespace cms {
|
||||
class CameraPipeline;
|
||||
// Pure drawing from the immutable result. Never re-runs inference.
|
||||
cv::Mat renderResult(FrameResult const& result);
|
||||
// Optional observer: closing the window returns without stopping the pipeline.
|
||||
void runLocalVisualization(CameraPipeline const& pipeline);
|
||||
}
|
||||
Reference in New Issue
Block a user