Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,7 @@ cargo run -r -F openvino -F ort-load-dynamic --example yolo -- --device openvino
| [DWPose](https://github.com/IDEA-Research/DWPose) | Keypoint Detection | [demo](./examples/pose-estimation) | βœ… | ❓ | βœ… | βœ… | βœ… | βœ… | βœ… |
| [RTMW](https://arxiv.org/abs/2407.08634) | Keypoint Detection | [demo](./examples/pose-estimation) | βœ… | ❓ | βœ… | βœ… | βœ… | βœ… | βœ… |
| [RTMO](https://github.com/open-mmlab/mmpose/tree/main/projects/rtmo) | Keypoint Detection | [demo](./examples/pose-estimation) | βœ… | ❓ | βœ… | βœ… | βœ… | βœ… | ❌ |
| [ECPose](https://github.com/Intellindust-AI-Lab/EdgeCrafter) | Keypoint Detection | [demo](./examples/pose-estimation) | βœ… | ❓ | βœ… | βœ… | ❌ | ❌ | ❌ |

</details>

Expand Down
5 changes: 5 additions & 0 deletions examples/pose-estimation/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ This directory contains examples for human pose estimation models.
cargo run -F cuda-full --example pose-estimation -- rtmo --dtype f16 --device cuda:0 --processor-device cuda:0
```

### ECPose
```bash
cargo run -F cuda-full --example pose-estimation -- ecpose --scale s --dtype fp16 --device cuda:0 --processor-device cuda:0
```

### RTMW
```bash
cargo run -F cuda-full --example pose-estimation -- rtmw --dtype f16 --device cuda:0 --processor-device cuda:0
Expand Down
105 changes: 105 additions & 0 deletions examples/pose-estimation/ecpose.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
use anyhow::Result;
use clap::Args;
use usls::{Config, DType, Device, Scale};

#[derive(Args, Debug)]
pub struct EcposeArgs {
/// Scale: s, m, l, x
#[arg(long, default_value = "s")]
pub scale: Scale,

/// Optional local model file path (overrides the built-in selection)
#[arg(long)]
pub model: Option<String>,

/// Dtype: fp32, fp16, q4f16, etc.
#[arg(long, default_value = "fp32")]
pub dtype: DType,

/// Device: cpu, cuda:0, mps, coreml, openvino:CPU, etc.
#[arg(long, global = true, default_value = "cpu")]
pub device: Device,

/// Processor device (for pre/post processing)
#[arg(long, global = true, default_value = "cpu")]
pub processor_device: Device,

/// Batch size
#[arg(long, global = true, default_value_t = 1)]
pub batch: usize,

/// Min batch size (TensorRT)
#[arg(long, global = true, default_value_t = 1)]
pub min_batch: usize,

/// Max batch size (TensorRT)
#[arg(long, global = true, default_value_t = 4)]
pub max_batch: usize,

/// num dry run
#[arg(long, global = true, default_value_t = 3)]
pub num_dry_run: usize,
}

pub fn config(args: &EcposeArgs) -> Result<Config> {
let mut config = match args.scale {
Scale::S => Config::ecpose_s(),
Scale::M => Config::ecpose_m(),
Scale::L => Config::ecpose_l(),
Scale::X => Config::ecpose_x(),
_ => anyhow::bail!(
"Unsupported ECPose scale: {} (expected s, m, l, or x)",
args.scale
),
};

config = if let Some(model) = &args.model {
config.with_model_file(select_local_model_path(model, args.dtype))
} else {
config.with_model_dtype(args.dtype)
};

Ok(config
.with_model_device(args.device)
.with_model_batch_size_min_opt_max(args.min_batch, args.batch, args.max_batch)
.with_model_num_dry_run(args.num_dry_run)
.with_image_processor_device(args.processor_device))
}

fn select_local_model_path(path: &str, dtype: DType) -> String {
if matches!(dtype, DType::Auto | DType::Fp32) {
return path.to_string();
}

let Some(stem) = path.strip_suffix(".onnx") else {
return path.to_string();
};

for suffix in [
"-fp16", "_fp16", ".fp16", "-q4f16", "_q4f16", ".q4f16", "-q8", "_q8", ".q8", "-bnb4",
"_bnb4", ".bnb4",
] {
if let Some(base) = stem.strip_suffix(suffix) {
return format!("{base}-{dtype}.onnx");
}
}

format!("{stem}-{dtype}.onnx")
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn local_model_path_uses_dtype_suffix_once() {
assert_eq!(
select_local_model_path("/tmp/ecpose.onnx", DType::Fp16),
"/tmp/ecpose-fp16.onnx"
);
assert_eq!(
select_local_model_path("/tmp/ecpose-fp16.onnx", DType::Fp16),
"/tmp/ecpose-fp16.onnx"
);
}
}
36 changes: 35 additions & 1 deletion examples/pose-estimation/main.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
use anyhow::Result;
use clap::{Parser, Subcommand};
use usls::{
models::{CIGPose, DWPose, HRNet, RTMPose, RTMO, YOLO},
models::{CIGPose, DWPose, ECPose, HRNet, RTMPose, RTMO, YOLO},
Annotator, Config, DataLoader, Model, Scale, Source, Y,
};

mod cigpose;
mod dwpose;
mod ecpose;
mod hrnet;
mod rtmo;
mod rtmpose;
Expand All @@ -32,6 +33,7 @@ struct Cli {
enum Commands {
Cigpose(cigpose::CigposeArgs),
Dwpose(dwpose::DwposeArgs),
Ecpose(ecpose::EcposeArgs),
Hrnet(hrnet::HrnetArgs),
Rtmo(rtmo::RtmoArgs),
Rtmpose(rtmpose::RtmposeArgs),
Expand Down Expand Up @@ -86,6 +88,38 @@ fn main() -> Result<()> {
}
Ok(())
}
Commands::Ecpose(args) => {
let config = ecpose::config(args)?.commit()?;
let mut model = ECPose::new(config)?;
let annotator = annotator.with_keypoint_style(
usls::KeypointStyle::default()
.with_radius(2)
.with_skeleton((usls::SKELETON_COCO_19, usls::SKELETON_COLOR_COCO_19).into())
.show_id(false)
.show_confidence(false)
.show_name(false),
);
let dl = DataLoader::new(&cli.source)?
.with_batch(model.batch() as _)
.with_progress_bar(true)
.stream()?;

for xs in &dl {
let ys = model.forward(&xs)?;
for (x, y) in xs.iter().zip(ys.iter()) {
if !y.is_empty() {
annotator.annotate(x, y)?.save(format!(
"{}.jpg",
usls::Dir::Current
.base_dir_with_subs(&["runs/pose-estimation", model.spec()])?
.join(usls::timestamp(None))
.display()
))?;
}
}
}
Ok(())
}
Commands::Dwpose(args) => {
let yolo_config = yolo_config
.with_model_device(args.device)
Expand Down
64 changes: 64 additions & 0 deletions src/models/vision/ecpose/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
use crate::{ResizeAlg, ResizeFilter, Task, NAMES_COCO_KEYPOINTS_17};

const ECPOSE_RELEASE: &str = "https://github.com/wep21/assets/releases/download/ecpose";

///
/// > # ECPose: EdgeCrafter Multi-Person Pose Estimation
/// >
/// > EdgeCrafter pose models exported to ONNX with DETR-style fixed-query outputs.
/// >
/// > # Model Variants
/// >
/// > - **ecpose-s**: Small multi-person COCO-17 pose model
/// > - **ecpose-m**: Medium multi-person COCO-17 pose model
/// > - **ecpose-l**: Large multi-person COCO-17 pose model
/// > - **ecpose-x**: Extra large multi-person COCO-17 pose model
/// >
/// > # Precision / File naming
/// >
/// > Assets are hosted on the `wep21/assets` GitHub releases (tag `ecpose`).
/// > FP32 weights use `ecpose-{scale}.onnx`; non-FP32 weights follow the standard
/// > automatic suffix resolution via [`crate::Config::with_model_dtype`].
///
/// Model configuration for `ECPose`
///
impl crate::Config {
/// Base configuration for ECPose models.
pub fn ecpose() -> Self {
Self::default()
.with_name("ecpose")
.with_task(Task::KeypointsDetection)
.with_model_ixx(0, 0, 1)
.with_model_ixx(0, 1, 3)
.with_model_ixx(0, 2, 640)
.with_model_ixx(0, 3, 640)
.with_model_ixx(1, 1, 2)
.with_resize_mode_type(crate::ResizeModeType::FitExact)
.with_resize_alg(ResizeAlg::Interpolation(ResizeFilter::Bilinear))
.with_image_mean([0.485, 0.456, 0.406])
.with_image_std([0.229, 0.224, 0.225])
.with_class_confs(&[0.5])
.with_nk(17)
.with_keypoint_names(&NAMES_COCO_KEYPOINTS_17)
}

/// Small multi-person COCO-17 pose model.
pub fn ecpose_s() -> Self {
Self::ecpose().with_model_file(format!("{ECPOSE_RELEASE}/ecpose-s.onnx"))
}

/// Medium multi-person COCO-17 pose model.
pub fn ecpose_m() -> Self {
Self::ecpose().with_model_file(format!("{ECPOSE_RELEASE}/ecpose-m.onnx"))
}

/// Large multi-person COCO-17 pose model.
pub fn ecpose_l() -> Self {
Self::ecpose().with_model_file(format!("{ECPOSE_RELEASE}/ecpose-l.onnx"))
}

/// Extra large multi-person COCO-17 pose model.
pub fn ecpose_x() -> Self {
Self::ecpose().with_model_file(format!("{ECPOSE_RELEASE}/ecpose-x.onnx"))
}
}
Loading
Loading