Compare commits
10 Commits
34eaf9348a
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
59a3fddc0b | ||
|
|
eb9451aad8 | ||
|
|
c6b3f5279f | ||
|
|
a419a5ac4a | ||
|
|
a340552257 | ||
|
|
aaf34ef74e | ||
|
|
ac8f1d01b4 | ||
|
|
4256c0af74 | ||
|
|
3eec262076 | ||
|
|
c758fd8d41 |
157
Cargo.lock
generated
157
Cargo.lock
generated
@@ -1271,20 +1271,22 @@ dependencies = [
|
|||||||
"image 0.25.6",
|
"image 0.25.6",
|
||||||
"imageproc",
|
"imageproc",
|
||||||
"itertools 0.14.0",
|
"itertools 0.14.0",
|
||||||
|
"linfa",
|
||||||
|
"linfa-clustering",
|
||||||
"mnn",
|
"mnn",
|
||||||
"mnn-bridge",
|
"mnn-bridge",
|
||||||
"mnn-sync",
|
"mnn-sync",
|
||||||
"nalgebra 0.34.0",
|
"nalgebra 0.34.0",
|
||||||
"ndarray",
|
"ndarray",
|
||||||
"ndarray-image",
|
"ndarray-image",
|
||||||
"ndarray-math 0.1.0 (git+https://git.darksailor.dev/servius/ndarray-math)",
|
"ndarray-math",
|
||||||
"ndarray-resize",
|
"ndarray-resize",
|
||||||
"ndarray-safetensors",
|
"ndarray-safetensors",
|
||||||
"ordered-float",
|
"ordered-float",
|
||||||
"ort",
|
"ort",
|
||||||
"rfd",
|
"rfd",
|
||||||
"rusqlite",
|
"rusqlite",
|
||||||
"sqlite3-safetensor-cosine",
|
"sqlite3-ndarray-math",
|
||||||
"tap",
|
"tap",
|
||||||
"thiserror 2.0.15",
|
"thiserror 2.0.15",
|
||||||
"tokio",
|
"tokio",
|
||||||
@@ -2808,6 +2810,16 @@ dependencies = [
|
|||||||
"mutate_once",
|
"mutate_once",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "kdtree"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0f0a0e9f770b65bac9aad00f97a67ab5c5319effed07f6da385da3c2115e47ba"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
"thiserror 1.0.69",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "khronos-egl"
|
name = "khronos-egl"
|
||||||
version = "6.0.0"
|
version = "6.0.0"
|
||||||
@@ -2916,6 +2928,63 @@ dependencies = [
|
|||||||
"vcpkg",
|
"vcpkg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "linfa"
|
||||||
|
version = "0.7.1"
|
||||||
|
source = "git+https://github.com/relf/linfa?branch=upgrade-ndarray-0.16#c1fbee7c54e806de3f5fb2c5240ce163d000f1ba"
|
||||||
|
dependencies = [
|
||||||
|
"approx",
|
||||||
|
"ndarray",
|
||||||
|
"num-traits",
|
||||||
|
"rand 0.8.5",
|
||||||
|
"sprs",
|
||||||
|
"thiserror 2.0.15",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "linfa-clustering"
|
||||||
|
version = "0.7.1"
|
||||||
|
source = "git+https://github.com/relf/linfa?branch=upgrade-ndarray-0.16#c1fbee7c54e806de3f5fb2c5240ce163d000f1ba"
|
||||||
|
dependencies = [
|
||||||
|
"linfa",
|
||||||
|
"linfa-linalg",
|
||||||
|
"linfa-nn",
|
||||||
|
"ndarray",
|
||||||
|
"ndarray-rand",
|
||||||
|
"ndarray-stats",
|
||||||
|
"noisy_float",
|
||||||
|
"num-traits",
|
||||||
|
"rand_xoshiro",
|
||||||
|
"space",
|
||||||
|
"thiserror 2.0.15",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "linfa-linalg"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "02a834c0ec063937688a0d13573aa515ab8c425bd8de3154b908dd3b9c197dc4"
|
||||||
|
dependencies = [
|
||||||
|
"ndarray",
|
||||||
|
"num-traits",
|
||||||
|
"thiserror 1.0.69",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "linfa-nn"
|
||||||
|
version = "0.7.2"
|
||||||
|
source = "git+https://github.com/relf/linfa?branch=upgrade-ndarray-0.16#c1fbee7c54e806de3f5fb2c5240ce163d000f1ba"
|
||||||
|
dependencies = [
|
||||||
|
"kdtree",
|
||||||
|
"linfa",
|
||||||
|
"ndarray",
|
||||||
|
"ndarray-stats",
|
||||||
|
"noisy_float",
|
||||||
|
"num-traits",
|
||||||
|
"order-stat",
|
||||||
|
"thiserror 2.0.15",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linux-raw-sys"
|
name = "linux-raw-sys"
|
||||||
version = "0.4.15"
|
version = "0.4.15"
|
||||||
@@ -3223,6 +3292,7 @@ version = "0.16.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "882ed72dce9365842bf196bdeedf5055305f11fc8c03dee7bb0194a6cad34841"
|
checksum = "882ed72dce9365842bf196bdeedf5055305f11fc8c03dee7bb0194a6cad34841"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"approx",
|
||||||
"matrixmultiply",
|
"matrixmultiply",
|
||||||
"num-complex",
|
"num-complex",
|
||||||
"num-integer",
|
"num-integer",
|
||||||
@@ -3244,16 +3314,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "ndarray-math"
|
name = "ndarray-math"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
source = "git+https://git.darksailor.dev/servius/ndarray-math#df17c36193df60e070e4e120c9feebe68ff3f517"
|
||||||
"ndarray",
|
|
||||||
"num",
|
|
||||||
"thiserror 2.0.15",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ndarray-math"
|
|
||||||
version = "0.1.0"
|
|
||||||
source = "git+https://git.darksailor.dev/servius/ndarray-math#f047966f20835267f20e5839272b9ab36c445796"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ndarray",
|
"ndarray",
|
||||||
"num",
|
"num",
|
||||||
@@ -3274,6 +3335,17 @@ dependencies = [
|
|||||||
"zip",
|
"zip",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ndarray-rand"
|
||||||
|
version = "0.15.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f093b3db6fd194718dcdeea6bd8c829417deae904e3fcc7732dabcd4416d25d8"
|
||||||
|
dependencies = [
|
||||||
|
"ndarray",
|
||||||
|
"rand 0.8.5",
|
||||||
|
"rand_distr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ndarray-resize"
|
name = "ndarray-resize"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -3297,6 +3369,21 @@ dependencies = [
|
|||||||
"thiserror 2.0.15",
|
"thiserror 2.0.15",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ndarray-stats"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "17ebbe97acce52d06aebed4cd4a87c0941f4b2519b59b82b4feb5bd0ce003dfd"
|
||||||
|
dependencies = [
|
||||||
|
"indexmap 2.10.0",
|
||||||
|
"itertools 0.13.0",
|
||||||
|
"ndarray",
|
||||||
|
"noisy_float",
|
||||||
|
"num-integer",
|
||||||
|
"num-traits",
|
||||||
|
"rand 0.8.5",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ndcv-bridge"
|
name = "ndcv-bridge"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -3389,6 +3476,15 @@ dependencies = [
|
|||||||
"memoffset",
|
"memoffset",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "noisy_float"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "978fe6e6ebc0bf53de533cd456ca2d9de13de13856eda1518a285d7705a213af"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nom"
|
name = "nom"
|
||||||
version = "7.1.3"
|
version = "7.1.3"
|
||||||
@@ -3883,6 +3979,12 @@ dependencies = [
|
|||||||
"libredox",
|
"libredox",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "order-stat"
|
||||||
|
version = "0.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "efa535d5117d3661134dbf1719b6f0ffe06f2375843b13935db186cd094105eb"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ordered-float"
|
name = "ordered-float"
|
||||||
version = "5.0.0"
|
version = "5.0.0"
|
||||||
@@ -4431,6 +4533,15 @@ dependencies = [
|
|||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_xoshiro"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa"
|
||||||
|
dependencies = [
|
||||||
|
"rand_core 0.6.4",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "range-alloc"
|
name = "range-alloc"
|
||||||
version = "0.1.4"
|
version = "0.1.4"
|
||||||
@@ -5102,6 +5213,12 @@ dependencies = [
|
|||||||
"x11rb",
|
"x11rb",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "space"
|
||||||
|
version = "0.12.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e990cc6cb89a82d70fe722cd7811dbce48a72bbfaebd623e58f142b6db28428f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "spin"
|
name = "spin"
|
||||||
version = "0.9.8"
|
version = "0.9.8"
|
||||||
@@ -5120,6 +5237,18 @@ dependencies = [
|
|||||||
"bitflags 2.9.2",
|
"bitflags 2.9.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sprs"
|
||||||
|
version = "0.11.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8bff8419009a08f6cb7519a602c5590241fbff1446bcc823c07af15386eb801b"
|
||||||
|
dependencies = [
|
||||||
|
"ndarray",
|
||||||
|
"num-complex",
|
||||||
|
"num-traits",
|
||||||
|
"smallvec 1.15.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sqlite-loadable"
|
name = "sqlite-loadable"
|
||||||
version = "0.0.5"
|
version = "0.0.5"
|
||||||
@@ -5145,11 +5274,11 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sqlite3-safetensor-cosine"
|
name = "sqlite3-ndarray-math"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ndarray",
|
"ndarray",
|
||||||
"ndarray-math 0.1.0",
|
"ndarray-math",
|
||||||
"ndarray-safetensors",
|
"ndarray-safetensors",
|
||||||
"sqlite-loadable",
|
"sqlite-loadable",
|
||||||
]
|
]
|
||||||
|
|||||||
49
Cargo.toml
49
Cargo.toml
@@ -1,43 +1,43 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"ndarray-image",
|
"ndarray-image",
|
||||||
"ndarray-resize",
|
"ndarray-resize",
|
||||||
".",
|
".",
|
||||||
"bounding-box",
|
"bounding-box",
|
||||||
"ndarray-safetensors",
|
"ndarray-safetensors",
|
||||||
"sqlite3-safetensor-cosine",
|
"sqlite3-ndarray-math",
|
||||||
"ndcv-bridge",
|
"ndcv-bridge",
|
||||||
"bbox",
|
"bbox",
|
||||||
]
|
]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
|
[patch.crates-io]
|
||||||
|
linfa = { git = "https://github.com/relf/linfa", branch = "upgrade-ndarray-0.16" }
|
||||||
|
linfa-clustering = { git = "https://github.com/relf/linfa", branch = "upgrade-ndarray-0.16" }
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
bbox = { path = "bbox" }
|
|
||||||
divan = { version = "0.1.21" }
|
divan = { version = "0.1.21" }
|
||||||
ndarray-npy = "0.9.1"
|
ndarray-npy = "0.9.1"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
ndarray-image = { path = "ndarray-image" }
|
ndarray-image = { path = "ndarray-image" }
|
||||||
ndarray-resize = { path = "ndarray-resize" }
|
ndarray-resize = { path = "ndarray-resize" }
|
||||||
mnn = { git = "https://github.com/uttarayan21/mnn-rs", version = "0.2.0", features = [
|
mnn = { git = "https://github.com/uttarayan21/mnn-rs", version = "0.2.0", features = [
|
||||||
# "metal",
|
# "metal",
|
||||||
# "coreml",
|
# "coreml",
|
||||||
"tracing",
|
"tracing",
|
||||||
], branch = "restructure-tensor-type" }
|
], branch = "restructure-tensor-type" }
|
||||||
mnn-bridge = { git = "https://github.com/uttarayan21/mnn-rs", version = "0.1.0", features = [
|
mnn-bridge = { git = "https://github.com/uttarayan21/mnn-rs", version = "0.1.0", features = [
|
||||||
"ndarray",
|
"ndarray",
|
||||||
], branch = "restructure-tensor-type" }
|
], branch = "restructure-tensor-type" }
|
||||||
mnn-sync = { git = "https://github.com/uttarayan21/mnn-rs", version = "0.1.0", features = [
|
mnn-sync = { git = "https://github.com/uttarayan21/mnn-rs", version = "0.1.0", features = [
|
||||||
"tracing",
|
"tracing",
|
||||||
], branch = "restructure-tensor-type" }
|
], branch = "restructure-tensor-type" }
|
||||||
nalgebra = { version = "0.34.0", default-features = false, features = ["std"] }
|
nalgebra = { version = "0.34.0", default-features = false, features = ["std"] }
|
||||||
opencv = { version = "0.95.1" }
|
opencv = { version = "0.95.1" }
|
||||||
bounding-box = { path = "bounding-box" }
|
bounding-box = { path = "bounding-box" }
|
||||||
ndarray-safetensors = { path = "ndarray-safetensors" }
|
|
||||||
wide = "0.7.33"
|
|
||||||
rayon = "1.11.0"
|
|
||||||
bytemuck = "1.23.2"
|
bytemuck = "1.23.2"
|
||||||
error-stack = "0.5.0"
|
error-stack = "0.5.0"
|
||||||
thiserror = "2.0"
|
thiserror = "2.0"
|
||||||
@@ -76,25 +76,28 @@ color = "0.3.1"
|
|||||||
itertools = "0.14.0"
|
itertools = "0.14.0"
|
||||||
ordered-float = "5.0.0"
|
ordered-float = "5.0.0"
|
||||||
ort = { version = "2.0.0-rc.10", default-features = false, features = [
|
ort = { version = "2.0.0-rc.10", default-features = false, features = [
|
||||||
"std",
|
"std",
|
||||||
"tracing",
|
"tracing",
|
||||||
"ndarray",
|
"ndarray",
|
||||||
|
"cuda",
|
||||||
] }
|
] }
|
||||||
ndarray-math = { git = "https://git.darksailor.dev/servius/ndarray-math", version = "0.1.0" }
|
ndarray-math = { git = "https://git.darksailor.dev/servius/ndarray-math", version = "0.1.0" }
|
||||||
ndarray-safetensors = { version = "0.1.0", path = "ndarray-safetensors" }
|
ndarray-safetensors = { version = "0.1.0", path = "ndarray-safetensors" }
|
||||||
sqlite3-safetensor-cosine = { version = "0.1.0", path = "sqlite3-safetensor-cosine" }
|
sqlite3-ndarray-math = { version = "0.1.0", path = "sqlite3-ndarray-math" }
|
||||||
|
|
||||||
# GUI dependencies
|
# GUI dependencies
|
||||||
iced = { version = "0.13", features = ["tokio", "image"] }
|
iced = { version = "0.13", features = ["tokio", "image"] }
|
||||||
rfd = "0.15"
|
rfd = "0.15"
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
imageproc = "0.25"
|
imageproc = "0.25"
|
||||||
|
linfa = "0.7.1"
|
||||||
|
linfa-clustering = "0.7.1"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
debug = true
|
debug = true
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
ort-cuda = ["ort/cuda"]
|
ort-cuda = []
|
||||||
ort-coreml = ["ort/coreml"]
|
ort-coreml = ["ort/coreml"]
|
||||||
ort-tensorrt = ["ort/tensorrt"]
|
ort-tensorrt = ["ort/tensorrt"]
|
||||||
ort-tvm = ["ort/tvm"]
|
ort-tvm = ["ort/tvm"]
|
||||||
@@ -103,7 +106,7 @@ ort-directml = ["ort/directml"]
|
|||||||
mnn-metal = ["mnn/metal"]
|
mnn-metal = ["mnn/metal"]
|
||||||
mnn-coreml = ["mnn/coreml"]
|
mnn-coreml = ["mnn/coreml"]
|
||||||
|
|
||||||
default = ["mnn-metal", "mnn-coreml"]
|
default = ["ort-cuda"]
|
||||||
|
|
||||||
[[test]]
|
[[test]]
|
||||||
name = "test_bbox_replacement"
|
name = "test_bbox_replacement"
|
||||||
|
|||||||
202
GUI_DEMO.md
202
GUI_DEMO.md
@@ -1,202 +0,0 @@
|
|||||||
# Face Detector GUI - Demo Documentation
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
This document demonstrates the successful creation of a modern GUI with full image rendering capabilities for the face-detector project using iced.rs, a cross-platform GUI framework for Rust.
|
|
||||||
|
|
||||||
## What Was Built
|
|
||||||
|
|
||||||
### 🎯 Core Features Implemented
|
|
||||||
|
|
||||||
1. **Modern Tabbed Interface**
|
|
||||||
- Detection tab for single image face detection with visual results
|
|
||||||
- Comparison tab for face similarity comparison with side-by-side images
|
|
||||||
- Settings tab for model and parameter configuration
|
|
||||||
|
|
||||||
2. **Full Image Rendering System**
|
|
||||||
- Real-time image preview for selected input images
|
|
||||||
- Processed image display with bounding boxes drawn around detected faces
|
|
||||||
- Side-by-side comparison view for face matching
|
|
||||||
- Automatic image scaling and fitting within UI containers
|
|
||||||
- Support for displaying results from both MNN and ONNX backends
|
|
||||||
|
|
||||||
3. **File Management**
|
|
||||||
- Image file selection dialogs
|
|
||||||
- Output path selection for processed images
|
|
||||||
- Support for multiple image formats (jpg, jpeg, png, bmp, tiff, webp)
|
|
||||||
- Automatic image loading and display upon selection
|
|
||||||
|
|
||||||
4. **Real-time Parameter Control**
|
|
||||||
- Adjustable detection threshold (0.1-1.0)
|
|
||||||
- Adjustable NMS threshold (0.1-1.0)
|
|
||||||
- Model type selection (RetinaFace, YOLO)
|
|
||||||
- Execution backend selection (MNN CPU/Metal/CoreML, ONNX CPU)
|
|
||||||
|
|
||||||
5. **Progress Tracking**
|
|
||||||
- Status bar with current operation display
|
|
||||||
- Progress bar for long-running operations
|
|
||||||
- Processing time reporting
|
|
||||||
|
|
||||||
6. **Visual Results Display**
|
|
||||||
- Face count reporting with visual confirmation
|
|
||||||
- Processed images with red bounding boxes around detected faces
|
|
||||||
- Similarity scores with interpretation and color coding
|
|
||||||
- Error handling and display
|
|
||||||
- Before/after image comparison
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
### 🏗️ Project Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
src/
|
|
||||||
├── gui/
|
|
||||||
│ ├── mod.rs # Module declarations
|
|
||||||
│ ├── app.rs # Main application logic
|
|
||||||
│ └── bridge.rs # Integration with face detection backend
|
|
||||||
├── bin/
|
|
||||||
│ └── gui.rs # GUI executable entry point
|
|
||||||
└── ... # Existing face detection modules
|
|
||||||
```
|
|
||||||
|
|
||||||
### 🔌 Integration Points
|
|
||||||
|
|
||||||
The GUI seamlessly integrates with your existing face detection infrastructure:
|
|
||||||
|
|
||||||
- **Backend Support**: Both MNN and ONNX Runtime backends
|
|
||||||
- **Model Support**: RetinaFace and YOLO models
|
|
||||||
- **Hardware Acceleration**: Metal, CoreML, and CPU execution
|
|
||||||
- **Database Integration**: Ready for face database operations
|
|
||||||
|
|
||||||
## Technical Highlights
|
|
||||||
|
|
||||||
### ⚡ Performance Features
|
|
||||||
|
|
||||||
1. **Asynchronous Operations**: All face detection operations run asynchronously to keep the UI responsive
|
|
||||||
2. **Memory Efficient**: Proper resource management for image processing
|
|
||||||
3. **Hardware Accelerated**: Full support for Metal and CoreML on macOS
|
|
||||||
|
|
||||||
### 🎨 User Experience
|
|
||||||
|
|
||||||
1. **Intuitive Design**: Clean, modern interface with logical tab organization
|
|
||||||
2. **Real-time Feedback**: Immediate visual feedback for all operations
|
|
||||||
3. **Error Handling**: User-friendly error messages and recovery
|
|
||||||
4. **Accessibility**: Proper contrast and sizing for readability
|
|
||||||
|
|
||||||
## Usage Examples
|
|
||||||
|
|
||||||
### Running the GUI
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Build and run the GUI
|
|
||||||
cargo run --bin gui
|
|
||||||
|
|
||||||
# Or build the binary
|
|
||||||
cargo build --bin gui --release
|
|
||||||
./target/release/gui
|
|
||||||
```
|
|
||||||
|
|
||||||
### Face Detection Workflow
|
|
||||||
|
|
||||||
1. **Select Image**: Click "Select Image" to choose an input image
|
|
||||||
- Image immediately appears in the "Original Image" preview
|
|
||||||
2. **Adjust Parameters**: Use sliders to fine-tune detection thresholds
|
|
||||||
3. **Choose Backend**: Select MNN or ONNX execution backend
|
|
||||||
4. **Run Detection**: Click "Detect Faces" to process the image
|
|
||||||
5. **View Visual Results**:
|
|
||||||
- Original image displayed on the left
|
|
||||||
- Processed image with red bounding boxes on the right
|
|
||||||
- Face count, processing time, and status information below
|
|
||||||
|
|
||||||
### Face Comparison Workflow
|
|
||||||
|
|
||||||
1. **Select Images**: Choose two images for comparison
|
|
||||||
- Both images appear side-by-side in the comparison view
|
|
||||||
- "First Image" and "Second Image" clearly labeled
|
|
||||||
2. **Configure Settings**: Adjust detection and comparison parameters
|
|
||||||
3. **Run Comparison**: Click "Compare Faces" to analyze similarity
|
|
||||||
4. **View Visual Results**:
|
|
||||||
- Both original images displayed side-by-side for easy comparison
|
|
||||||
- Similarity scores with automatic interpretation and color coding:
|
|
||||||
- **> 0.8**: Very likely the same person (green text)
|
|
||||||
- **0.6-0.8**: Possibly the same person (yellow text)
|
|
||||||
- **0.4-0.6**: Unlikely to be the same person (orange text)
|
|
||||||
- **< 0.4**: Very unlikely to be the same person (red text)
|
|
||||||
|
|
||||||
## Current Status
|
|
||||||
|
|
||||||
### ✅ Successfully Implemented
|
|
||||||
|
|
||||||
- [x] Complete GUI framework integration
|
|
||||||
- [x] Tabbed interface with three main sections
|
|
||||||
- [x] File dialogs for image selection
|
|
||||||
- [x] **Full image rendering and display system**
|
|
||||||
- [x] **Real-time image preview for selected inputs**
|
|
||||||
- [x] **Processed image display with bounding boxes**
|
|
||||||
- [x] **Side-by-side image comparison view**
|
|
||||||
- [x] Parameter controls with real-time updates
|
|
||||||
- [x] Asynchronous operation handling
|
|
||||||
- [x] Progress tracking and status reporting
|
|
||||||
- [x] Integration with existing face detection backend
|
|
||||||
- [x] Support for both MNN and ONNX backends
|
|
||||||
- [x] Error handling and user feedback
|
|
||||||
- [x] Cross-platform compatibility (tested on macOS)
|
|
||||||
|
|
||||||
### 🔧 Known Issues
|
|
||||||
|
|
||||||
1. **Array Bounds Error**: There's a runtime error in the RetinaFace implementation that needs debugging:
|
|
||||||
```
|
|
||||||
thread 'tokio-runtime-worker' panicked at src/facedet/retinaface.rs:178:22:
|
|
||||||
ndarray: index 43008 is out of bounds for array of shape [43008]
|
|
||||||
```
|
|
||||||
This appears to be related to the original face detection logic, not the GUI code.
|
|
||||||
|
|
||||||
### 🚀 Future Enhancements
|
|
||||||
|
|
||||||
1. ~~**Image Display**: Add image preview and result visualization~~ ✅ **COMPLETED**
|
|
||||||
2. **Batch Processing**: Support for processing multiple images
|
|
||||||
3. **Database Integration**: GUI for face database operations
|
|
||||||
4. **Export Features**: Save results in various formats
|
|
||||||
5. **Configuration Persistence**: Remember user settings
|
|
||||||
6. **Drag & Drop**: Direct image dropping support
|
|
||||||
7. **Zoom and Pan**: Advanced image viewing capabilities
|
|
||||||
8. **Landmark Visualization**: Display facial landmarks on detected faces
|
|
||||||
|
|
||||||
## Technical Dependencies
|
|
||||||
|
|
||||||
### New Dependencies Added
|
|
||||||
|
|
||||||
```toml
|
|
||||||
# GUI dependencies
|
|
||||||
iced = { version = "0.13", features = ["tokio", "image"] }
|
|
||||||
rfd = "0.15" # File dialogs
|
|
||||||
futures = "0.3" # Async utilities
|
|
||||||
imageproc = "0.25" # Image processing utilities
|
|
||||||
```
|
|
||||||
|
|
||||||
### Integration Approach
|
|
||||||
|
|
||||||
The GUI was designed as a thin layer over your existing face detection engine:
|
|
||||||
|
|
||||||
- **Minimal Changes**: Only added new modules, no modifications to existing detection logic
|
|
||||||
- **Clean Separation**: GUI logic is completely separate from core detection algorithms
|
|
||||||
- **Reusable Components**: Bridge pattern allows easy extension to new backends
|
|
||||||
- **Maintainable Code**: Clear module boundaries and consistent error handling
|
|
||||||
|
|
||||||
## Compilation and Testing
|
|
||||||
|
|
||||||
The GUI compiles successfully with only minor warnings and has been tested on macOS with Apple Silicon. The interface is responsive and all UI components work as expected.
|
|
||||||
|
|
||||||
### Build Output
|
|
||||||
```
|
|
||||||
Finished `dev` profile [unoptimized + debuginfo] target(s) in 1m 05s
|
|
||||||
Running `/target/debug/gui`
|
|
||||||
```
|
|
||||||
|
|
||||||
The application launches properly, displays the GUI interface, and responds to user interactions. The only runtime issue is in the underlying face detection algorithm, which is separate from the GUI implementation.
|
|
||||||
|
|
||||||
## Conclusion
|
|
||||||
|
|
||||||
The GUI implementation successfully provides a modern, user-friendly interface for your face detection system. It maintains the full power and flexibility of your existing CLI tool while making it accessible to non-technical users through an intuitive graphical interface.
|
|
||||||
|
|
||||||
The architecture is extensible and maintainable, making it easy to add new features and functionality as your face detection system evolves.
|
|
||||||
BIN
KD4_7131.CR2
BIN
KD4_7131.CR2
Binary file not shown.
@@ -1,3 +1,7 @@
|
|||||||
|
[tasks.convert]
|
||||||
|
dependencies = ["convert_facenet", "convert_retinaface"]
|
||||||
|
workspace = false
|
||||||
|
|
||||||
[tasks.convert_facenet]
|
[tasks.convert_facenet]
|
||||||
command = "MNNConvert"
|
command = "MNNConvert"
|
||||||
args = [
|
args = [
|
||||||
@@ -11,6 +15,7 @@ args = [
|
|||||||
"--bizCode",
|
"--bizCode",
|
||||||
"MNN",
|
"MNN",
|
||||||
]
|
]
|
||||||
|
workspace = false
|
||||||
|
|
||||||
[tasks.convert_retinaface]
|
[tasks.convert_retinaface]
|
||||||
command = "MNNConvert"
|
command = "MNNConvert"
|
||||||
@@ -25,3 +30,9 @@ args = [
|
|||||||
"--bizCode",
|
"--bizCode",
|
||||||
"MNN",
|
"MNN",
|
||||||
]
|
]
|
||||||
|
workspace = false
|
||||||
|
|
||||||
|
[tasks.gui]
|
||||||
|
command = "cargo"
|
||||||
|
args = ["run", "--release", "--bin", "gui"]
|
||||||
|
workspace = false
|
||||||
|
|||||||
@@ -163,6 +163,21 @@ impl<T: Num, const D: usize> AxisAlignedBoundingBox<T, D> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn scale_uniform(self, scalar: T) -> Self
|
||||||
|
where
|
||||||
|
T: core::ops::MulAssign,
|
||||||
|
T: core::ops::DivAssign,
|
||||||
|
T: core::ops::SubAssign,
|
||||||
|
{
|
||||||
|
let two = T::one() + T::one();
|
||||||
|
let new_size = self.size * scalar;
|
||||||
|
let new_point = self.point.coords - (new_size - self.size) / two;
|
||||||
|
Self {
|
||||||
|
point: Point::from(new_point),
|
||||||
|
size: new_size,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn contains_bbox(&self, other: &Self) -> bool
|
pub fn contains_bbox(&self, other: &Self) -> bool
|
||||||
where
|
where
|
||||||
T: core::ops::AddAssign,
|
T: core::ops::AddAssign,
|
||||||
@@ -270,15 +285,17 @@ impl<T: Num, const D: usize> AxisAlignedBoundingBox<T, D> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// pub fn as_<T2>(&self) -> Option<Aabb<T2, D>>
|
pub fn as_<T2>(&self) -> Aabb<T2, D>
|
||||||
// where
|
where
|
||||||
// T2: Num + simba::scalar::SubsetOf<T>,
|
T2: Num,
|
||||||
// {
|
T: num::cast::AsPrimitive<T2>,
|
||||||
// Some(Aabb {
|
{
|
||||||
// point: Point::from(self.point.coords.as_()),
|
Aabb {
|
||||||
// size: self.size.as_(),
|
point: Point::from(self.point.coords.map(|x| x.as_())),
|
||||||
// })
|
size: self.size.map(|x| x.as_()),
|
||||||
// }
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn measure(&self) -> T
|
pub fn measure(&self) -> T
|
||||||
where
|
where
|
||||||
T: core::ops::MulAssign,
|
T: core::ops::MulAssign,
|
||||||
|
|||||||
62
cr2.xmp
62
cr2.xmp
@@ -1,62 +0,0 @@
|
|||||||
<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?><x:xmpmeta xmlns:x="adobe:ns:meta/"><rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><rdf:Description rdf:about="" xmlns:xmp="http://ns.adobe.com/xap/1.0/"><xmp:Rating>0</xmp:Rating></rdf:Description></rdf:RDF></x:xmpmeta>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<?xpacket end='w'?>
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
.load /Users/fs0c131y/.cache/cargo/target/release/libsqlite3_safetensor_cosine.dylib
|
|
||||||
|
|
||||||
SELECT
|
|
||||||
cosine_similarity(e1.embedding, e2.embedding) AS similarity
|
|
||||||
FROM
|
|
||||||
embeddings AS e1
|
|
||||||
CROSS JOIN embeddings AS e2
|
|
||||||
WHERE
|
|
||||||
e1.id = e2.id;
|
|
||||||
38
flake.nix
38
flake.nix
@@ -43,6 +43,8 @@
|
|||||||
system: let
|
system: let
|
||||||
pkgs = import nixpkgs {
|
pkgs = import nixpkgs {
|
||||||
inherit system;
|
inherit system;
|
||||||
|
config.allowUnfree = true;
|
||||||
|
config.cudaSupport = pkgs.stdenv.isLinux;
|
||||||
overlays = [
|
overlays = [
|
||||||
rust-overlay.overlays.default
|
rust-overlay.overlays.default
|
||||||
(final: prev: {
|
(final: prev: {
|
||||||
@@ -75,7 +77,7 @@
|
|||||||
craneLib = (crane.mkLib pkgs).overrideToolchain stableToolchain;
|
craneLib = (crane.mkLib pkgs).overrideToolchain stableToolchain;
|
||||||
craneLibLLvmTools = (crane.mkLib pkgs).overrideToolchain stableToolchainWithLLvmTools;
|
craneLibLLvmTools = (crane.mkLib pkgs).overrideToolchain stableToolchainWithLLvmTools;
|
||||||
|
|
||||||
ort_static = pkgs.onnxruntime.overrideAttrs (old: {
|
ort_static = (pkgs.onnxruntime.overide {cudaSupport = true;}).overrideAttrs (old: {
|
||||||
cmakeFlags =
|
cmakeFlags =
|
||||||
old.cmakeFlags
|
old.cmakeFlags
|
||||||
++ [
|
++ [
|
||||||
@@ -198,8 +200,9 @@
|
|||||||
devShells = {
|
devShells = {
|
||||||
default = pkgs.mkShell.override {stdenv = pkgs.clangStdenv;} (
|
default = pkgs.mkShell.override {stdenv = pkgs.clangStdenv;} (
|
||||||
commonArgs
|
commonArgs
|
||||||
// {
|
// rec {
|
||||||
LLDB_DEBUGSERVER_PATH = "/Applications/Xcode.app/Contents/SharedFrameworks/LLDB.framework/Versions/A/Resources/debugserver";
|
LLDB_DEBUGSERVER_PATH = "/Applications/Xcode.app/Contents/SharedFrameworks/LLDB.framework/Versions/A/Resources/debugserver";
|
||||||
|
LD_LIBRARY_PATH = "$LD_LIBRARY_PATH:${builtins.toString (pkgs.lib.makeLibraryPath packages)}";
|
||||||
packages = with pkgs;
|
packages = with pkgs;
|
||||||
[
|
[
|
||||||
stableToolchainWithRustAnalyzer
|
stableToolchainWithRustAnalyzer
|
||||||
@@ -212,9 +215,40 @@
|
|||||||
cargo-make
|
cargo-make
|
||||||
hyperfine
|
hyperfine
|
||||||
opencv
|
opencv
|
||||||
|
uv
|
||||||
|
# (python312.withPackages (ps:
|
||||||
|
# with ps; [
|
||||||
|
# numpy
|
||||||
|
# matplotlib
|
||||||
|
# scikit-learn
|
||||||
|
# opencv-python
|
||||||
|
# seaborn
|
||||||
|
# torch
|
||||||
|
# torchvision
|
||||||
|
# tensorflow-lite
|
||||||
|
# retinaface
|
||||||
|
# facenet-pytorch
|
||||||
|
# tqdm
|
||||||
|
# pillow
|
||||||
|
# orjson
|
||||||
|
# huggingface-hub
|
||||||
|
# # insightface
|
||||||
|
# ]))
|
||||||
]
|
]
|
||||||
++ (lib.optionals pkgs.stdenv.isDarwin [
|
++ (lib.optionals pkgs.stdenv.isDarwin [
|
||||||
apple-sdk_13
|
apple-sdk_13
|
||||||
|
])
|
||||||
|
++ (lib.optionals pkgs.stdenv.isLinux [
|
||||||
|
xorg.libX11
|
||||||
|
xorg.libXcursor
|
||||||
|
xorg.libXrandr
|
||||||
|
xorg.libXi
|
||||||
|
xorg.libxcb
|
||||||
|
libxkbcommon
|
||||||
|
vulkan-loader
|
||||||
|
wayland
|
||||||
|
zenity
|
||||||
|
cudatoolkit
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
BIN
models/facenet.mnn
LFS
BIN
models/facenet.mnn
LFS
Binary file not shown.
BIN
models/facenet.onnx
LFS
BIN
models/facenet.onnx
LFS
Binary file not shown.
BIN
models/retinaface.mnn
LFS
BIN
models/retinaface.mnn
LFS
Binary file not shown.
BIN
models/retinaface.onnx
LFS
BIN
models/retinaface.onnx
LFS
Binary file not shown.
1
rfcs
1
rfcs
Submodule rfcs deleted from c973203daf
@@ -1,5 +1,5 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "sqlite3-safetensor-cosine"
|
name = "sqlite3-ndarray-math"
|
||||||
version.workspace = true
|
version.workspace = true
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
|
|
||||||
@@ -8,7 +8,7 @@ crate-type = ["cdylib", "staticlib"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
ndarray = "0.16.1"
|
ndarray = "0.16.1"
|
||||||
# ndarray-math = { git = "https://git.darksailor.dev/servius/ndarray-math", version = "0.1.0" }
|
ndarray-math = { git = "https://git.darksailor.dev/servius/ndarray-math", version = "0.1.0" }
|
||||||
ndarray-math = { path = "/Users/fs0c131y/Projects/ndarray-math", version = "0.1.0" }
|
# ndarray-math = { path = "/Users/fs0c131y/Projects/ndarray-math", version = "0.1.0" }
|
||||||
ndarray-safetensors = { version = "0.1.0", path = "../ndarray-safetensors" }
|
ndarray-safetensors = { version = "0.1.0", path = "../ndarray-safetensors" }
|
||||||
sqlite-loadable = "0.0.5"
|
sqlite-loadable = "0.0.5"
|
||||||
@@ -21,6 +21,8 @@ pub enum SubCommand {
|
|||||||
Stats(Stats),
|
Stats(Stats),
|
||||||
#[clap(name = "compare")]
|
#[clap(name = "compare")]
|
||||||
Compare(Compare),
|
Compare(Compare),
|
||||||
|
#[clap(name = "cluster")]
|
||||||
|
Cluster(Cluster),
|
||||||
#[clap(name = "gui")]
|
#[clap(name = "gui")]
|
||||||
Gui,
|
Gui,
|
||||||
#[clap(name = "completions")]
|
#[clap(name = "completions")]
|
||||||
@@ -187,6 +189,22 @@ pub struct Compare {
|
|||||||
pub image2: PathBuf,
|
pub image2: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, clap::Args)]
|
||||||
|
pub struct Cluster {
|
||||||
|
#[clap(short = 'd', long, default_value = "face_detections.db")]
|
||||||
|
pub database: PathBuf,
|
||||||
|
#[clap(short, long, default_value_t = 5)]
|
||||||
|
pub clusters: usize,
|
||||||
|
#[clap(short, long, default_value_t = 100)]
|
||||||
|
pub max_iterations: u64,
|
||||||
|
#[clap(short, long, default_value_t = 1e-4)]
|
||||||
|
pub tolerance: f64,
|
||||||
|
#[clap(long, default_value = "facenet")]
|
||||||
|
pub model_name: String,
|
||||||
|
#[clap(short, long)]
|
||||||
|
pub output: Option<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
impl Cli {
|
impl Cli {
|
||||||
pub fn completions(shell: clap_complete::Shell) {
|
pub fn completions(shell: clap_complete::Shell) {
|
||||||
let mut command = <Cli as clap::CommandFactory>::command();
|
let mut command = <Cli as clap::CommandFactory>::command();
|
||||||
|
|||||||
@@ -2,9 +2,11 @@ mod cli;
|
|||||||
use bounding_box::roi::MultiRoi;
|
use bounding_box::roi::MultiRoi;
|
||||||
use detector::*;
|
use detector::*;
|
||||||
use detector::{database::FaceDatabase, facedet, facedet::FaceDetectionConfig, faceembed};
|
use detector::{database::FaceDatabase, facedet, facedet::FaceDetectionConfig, faceembed};
|
||||||
use errors::*;
|
|
||||||
use fast_image_resize::ResizeOptions;
|
use fast_image_resize::ResizeOptions;
|
||||||
|
|
||||||
|
use linfa::prelude::*;
|
||||||
|
use linfa_clustering::KMeans;
|
||||||
use ndarray::*;
|
use ndarray::*;
|
||||||
use ndarray_image::*;
|
use ndarray_image::*;
|
||||||
use ndarray_resize::NdFir;
|
use ndarray_resize::NdFir;
|
||||||
@@ -22,10 +24,12 @@ const FACENET_MODEL_ONNX: &[u8] =
|
|||||||
include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/models/facenet.onnx"));
|
include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/models/facenet.onnx"));
|
||||||
pub fn main() -> Result<()> {
|
pub fn main() -> Result<()> {
|
||||||
tracing_subscriber::fmt()
|
tracing_subscriber::fmt()
|
||||||
.with_env_filter("info")
|
.with_env_filter("info,ort=warn")
|
||||||
.with_thread_ids(true)
|
// .with_thread_ids(true)
|
||||||
.with_thread_names(true)
|
// .with_thread_names(true)
|
||||||
.with_target(false)
|
.with_file(true)
|
||||||
|
.with_line_number(true)
|
||||||
|
.with_target(true)
|
||||||
.init();
|
.init();
|
||||||
let args = <cli::Cli as clap::Parser>::parse();
|
let args = <cli::Cli as clap::Parser>::parse();
|
||||||
match args.cmd {
|
match args.cmd {
|
||||||
@@ -207,6 +211,9 @@ pub fn main() -> Result<()> {
|
|||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
cli::SubCommand::Cluster(cluster) => {
|
||||||
|
run_clustering(cluster)?;
|
||||||
|
}
|
||||||
cli::SubCommand::Completions { shell } => {
|
cli::SubCommand::Completions { shell } => {
|
||||||
cli::Cli::completions(shell);
|
cli::Cli::completions(shell);
|
||||||
}
|
}
|
||||||
@@ -934,3 +941,123 @@ fn run_stats(stats: cli::Stats) -> Result<()> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn run_clustering(cluster: cli::Cluster) -> Result<()> {
|
||||||
|
let db = FaceDatabase::new(&cluster.database).change_context(Error)?;
|
||||||
|
|
||||||
|
// Get all embeddings for the specified model
|
||||||
|
let embeddings = db
|
||||||
|
.get_all_embeddings(Some(&cluster.model_name))
|
||||||
|
.change_context(Error)?;
|
||||||
|
|
||||||
|
if embeddings.is_empty() {
|
||||||
|
println!("No embeddings found for model '{}'", cluster.model_name);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"Found {} embeddings for model '{}'",
|
||||||
|
embeddings.len(),
|
||||||
|
cluster.model_name
|
||||||
|
);
|
||||||
|
|
||||||
|
if embeddings.len() < cluster.clusters {
|
||||||
|
println!(
|
||||||
|
"Warning: Number of embeddings ({}) is less than requested clusters ({})",
|
||||||
|
embeddings.len(),
|
||||||
|
cluster.clusters
|
||||||
|
);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert embeddings to a 2D array for clustering
|
||||||
|
let embedding_dim = embeddings[0].embedding.len();
|
||||||
|
let mut data = Array2::<f64>::zeros((embeddings.len(), embedding_dim));
|
||||||
|
|
||||||
|
for (i, embedding_record) in embeddings.iter().enumerate() {
|
||||||
|
for (j, &val) in embedding_record.embedding.iter().enumerate() {
|
||||||
|
data[[i, j]] = val as f64;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"Running K-means clustering with {} clusters...",
|
||||||
|
cluster.clusters
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create dataset
|
||||||
|
let dataset = linfa::Dataset::new(data, Array1::<usize>::zeros(embeddings.len()));
|
||||||
|
|
||||||
|
// Configure and run K-means
|
||||||
|
let model = KMeans::params(cluster.clusters)
|
||||||
|
.max_n_iterations(cluster.max_iterations)
|
||||||
|
.tolerance(cluster.tolerance)
|
||||||
|
.fit(&dataset)
|
||||||
|
.change_context(Error)
|
||||||
|
.attach_printable("Failed to run K-means clustering")?;
|
||||||
|
|
||||||
|
// Get cluster assignments
|
||||||
|
let predictions = model.predict(&dataset);
|
||||||
|
|
||||||
|
// Group results by cluster
|
||||||
|
let mut clusters: std::collections::HashMap<usize, Vec<(i64, String)>> =
|
||||||
|
std::collections::HashMap::new();
|
||||||
|
|
||||||
|
for (i, &cluster_id) in predictions.iter().enumerate() {
|
||||||
|
let face_id = embeddings[i].face_id;
|
||||||
|
|
||||||
|
// Get image path for this face
|
||||||
|
let image_info = db.get_image_for_face(face_id).change_context(Error)?;
|
||||||
|
let image_path = image_info
|
||||||
|
.map(|info| info.file_path)
|
||||||
|
.unwrap_or_else(|| "Unknown".to_string());
|
||||||
|
|
||||||
|
clusters
|
||||||
|
.entry(cluster_id)
|
||||||
|
.or_insert_with(Vec::new)
|
||||||
|
.push((face_id, image_path));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print results
|
||||||
|
println!("\nClustering Results:");
|
||||||
|
for cluster_id in 0..cluster.clusters {
|
||||||
|
if let Some(faces) = clusters.get(&cluster_id) {
|
||||||
|
println!("\nCluster {}: {} faces", cluster_id, faces.len());
|
||||||
|
for (face_id, image_path) in faces {
|
||||||
|
println!(" Face ID: {}, Image: {}", face_id, image_path);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
println!("\nCluster {}: 0 faces", cluster_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optionally save results to file
|
||||||
|
if let Some(output_path) = &cluster.output {
|
||||||
|
use std::io::Write;
|
||||||
|
let mut file = std::fs::File::create(output_path)
|
||||||
|
.change_context(Error)
|
||||||
|
.attach_printable("Failed to create output file")?;
|
||||||
|
|
||||||
|
writeln!(file, "K-means Clustering Results").change_context(Error)?;
|
||||||
|
writeln!(file, "Model: {}", cluster.model_name).change_context(Error)?;
|
||||||
|
writeln!(file, "Total embeddings: {}", embeddings.len()).change_context(Error)?;
|
||||||
|
writeln!(file, "Number of clusters: {}", cluster.clusters).change_context(Error)?;
|
||||||
|
writeln!(file, "").change_context(Error)?;
|
||||||
|
|
||||||
|
for cluster_id in 0..cluster.clusters {
|
||||||
|
if let Some(faces) = clusters.get(&cluster_id) {
|
||||||
|
writeln!(file, "Cluster {}: {} faces", cluster_id, faces.len())
|
||||||
|
.change_context(Error)?;
|
||||||
|
for (face_id, image_path) in faces {
|
||||||
|
writeln!(file, " Face ID: {}, Image: {}", face_id, image_path)
|
||||||
|
.change_context(Error)?;
|
||||||
|
}
|
||||||
|
writeln!(file, "").change_context(Error)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("\nResults saved to: {:?}", output_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ use detector::errors::*;
|
|||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
// Initialize logging
|
// Initialize logging
|
||||||
tracing_subscriber::fmt()
|
tracing_subscriber::fmt()
|
||||||
.with_env_filter("info")
|
.with_env_filter("warn,ort=warn")
|
||||||
// .with_thread_ids(true)
|
.with_file(true)
|
||||||
// .with_file(true)
|
.with_line_number(true)
|
||||||
// .with_thread_names(true)
|
// .with_thread_names(true)
|
||||||
.with_target(true)
|
.with_target(true)
|
||||||
.init();
|
.init();
|
||||||
|
|||||||
@@ -65,14 +65,15 @@ impl FaceDatabase {
|
|||||||
/// Create a new database connection and initialize tables
|
/// Create a new database connection and initialize tables
|
||||||
pub fn new<P: AsRef<Path>>(db_path: P) -> Result<Self> {
|
pub fn new<P: AsRef<Path>>(db_path: P) -> Result<Self> {
|
||||||
let conn = Connection::open(db_path).change_context(Error)?;
|
let conn = Connection::open(db_path).change_context(Error)?;
|
||||||
unsafe {
|
// Temporarily disable extension loading for clustering demo
|
||||||
let _guard = rusqlite::LoadExtensionGuard::new(&conn).change_context(Error)?;
|
// unsafe {
|
||||||
conn.load_extension(
|
// let _guard = rusqlite::LoadExtensionGuard::new(&conn).change_context(Error)?;
|
||||||
"/Users/fs0c131y/.cache/cargo/target/release/libsqlite3_safetensor_cosine.dylib",
|
// conn.load_extension(
|
||||||
None::<&str>,
|
// "/Users/fs0c131y/.cache/cargo/target/release/libsqlite3_safetensor_cosine.dylib",
|
||||||
)
|
// None::<&str>,
|
||||||
.change_context(Error)?;
|
// )
|
||||||
}
|
// .change_context(Error)?;
|
||||||
|
// }
|
||||||
let db = Self { conn };
|
let db = Self { conn };
|
||||||
db.create_tables()?;
|
db.create_tables()?;
|
||||||
Ok(db)
|
Ok(db)
|
||||||
@@ -594,6 +595,76 @@ impl FaceDatabase {
|
|||||||
println!("{:?}", result);
|
println!("{:?}", result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get all embeddings for a specific model
|
||||||
|
pub fn get_all_embeddings(&self, model_name: Option<&str>) -> Result<Vec<EmbeddingRecord>> {
|
||||||
|
let mut embeddings = Vec::new();
|
||||||
|
|
||||||
|
if let Some(model) = model_name {
|
||||||
|
let mut stmt = self.conn.prepare(
|
||||||
|
"SELECT id, face_id, embedding, model_name, created_at FROM embeddings WHERE model_name = ?1"
|
||||||
|
).change_context(Error)?;
|
||||||
|
|
||||||
|
let embedding_iter = stmt
|
||||||
|
.query_map(params![model], |row| {
|
||||||
|
let embedding_bytes: Vec<u8> = row.get(2)?;
|
||||||
|
let embedding: ndarray::Array1<f32> = {
|
||||||
|
let sf = ndarray_safetensors::SafeArraysView::from_bytes(&embedding_bytes)
|
||||||
|
.change_context(Error)
|
||||||
|
.unwrap();
|
||||||
|
sf.tensor::<f32, ndarray::Ix1>("embedding")
|
||||||
|
.unwrap()
|
||||||
|
.to_owned()
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(EmbeddingRecord {
|
||||||
|
id: row.get(0)?,
|
||||||
|
face_id: row.get(1)?,
|
||||||
|
embedding,
|
||||||
|
model_name: row.get(3)?,
|
||||||
|
created_at: row.get(4)?,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.change_context(Error)?;
|
||||||
|
|
||||||
|
for embedding in embedding_iter {
|
||||||
|
embeddings.push(embedding.change_context(Error)?);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let mut stmt = self
|
||||||
|
.conn
|
||||||
|
.prepare("SELECT id, face_id, embedding, model_name, created_at FROM embeddings")
|
||||||
|
.change_context(Error)?;
|
||||||
|
|
||||||
|
let embedding_iter = stmt
|
||||||
|
.query_map([], |row| {
|
||||||
|
let embedding_bytes: Vec<u8> = row.get(2)?;
|
||||||
|
let embedding: ndarray::Array1<f32> = {
|
||||||
|
let sf = ndarray_safetensors::SafeArraysView::from_bytes(&embedding_bytes)
|
||||||
|
.change_context(Error)
|
||||||
|
.unwrap();
|
||||||
|
sf.tensor::<f32, ndarray::Ix1>("embedding")
|
||||||
|
.unwrap()
|
||||||
|
.to_owned()
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(EmbeddingRecord {
|
||||||
|
id: row.get(0)?,
|
||||||
|
face_id: row.get(1)?,
|
||||||
|
embedding,
|
||||||
|
model_name: row.get(3)?,
|
||||||
|
created_at: row.get(4)?,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.change_context(Error)?;
|
||||||
|
|
||||||
|
for embedding in embedding_iter {
|
||||||
|
embeddings.push(embedding.change_context(Error)?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(embeddings)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_sqlite_cosine_similarity(db: &Connection) -> Result<()> {
|
fn add_sqlite_cosine_similarity(db: &Connection) -> Result<()> {
|
||||||
|
|||||||
188
src/gui/app.rs
188
src/gui/app.rs
@@ -1,5 +1,5 @@
|
|||||||
use iced::{
|
use iced::{
|
||||||
Alignment, Element, Length, Task, Theme,
|
Alignment, Element, Length, Settings, Task, Theme,
|
||||||
widget::{
|
widget::{
|
||||||
Space, button, column, container, image, pick_list, progress_bar, row, scrollable, slider,
|
Space, button, column, container, image, pick_list, progress_bar, row, scrollable, slider,
|
||||||
text,
|
text,
|
||||||
@@ -10,6 +10,7 @@ use std::path::PathBuf;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::gui::bridge::FaceDetectionBridge;
|
use crate::gui::bridge::FaceDetectionBridge;
|
||||||
|
use ::image::{DynamicImage, ImageFormat, RgbImage};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Message {
|
pub enum Message {
|
||||||
@@ -43,6 +44,7 @@ pub enum Message {
|
|||||||
ImageLoaded(Option<Arc<Vec<u8>>>),
|
ImageLoaded(Option<Arc<Vec<u8>>>),
|
||||||
SecondImageLoaded(Option<Arc<Vec<u8>>>),
|
SecondImageLoaded(Option<Arc<Vec<u8>>>),
|
||||||
ProcessedImageUpdated(Option<Vec<u8>>),
|
ProcessedImageUpdated(Option<Vec<u8>>),
|
||||||
|
FaceRoisLoaded(Vec<image::Handle>, Vec<image::Handle>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
@@ -55,9 +57,13 @@ pub enum Tab {
|
|||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum ExecutorType {
|
pub enum ExecutorType {
|
||||||
MnnCpu,
|
MnnCpu,
|
||||||
|
#[cfg(feature = "mnn-metal")]
|
||||||
MnnMetal,
|
MnnMetal,
|
||||||
|
#[cfg(feature = "mnn-coreml")]
|
||||||
MnnCoreML,
|
MnnCoreML,
|
||||||
OnnxCpu,
|
OnnxCpu,
|
||||||
|
#[cfg(feature = "ort-cuda")]
|
||||||
|
OrtCuda,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -76,6 +82,8 @@ pub enum ComparisonResult {
|
|||||||
Success {
|
Success {
|
||||||
image1_faces: usize,
|
image1_faces: usize,
|
||||||
image2_faces: usize,
|
image2_faces: usize,
|
||||||
|
image1_face_rois: Vec<ndarray::Array3<u8>>,
|
||||||
|
image2_face_rois: Vec<ndarray::Array3<u8>>,
|
||||||
best_similarity: f32,
|
best_similarity: f32,
|
||||||
processing_time: f64,
|
processing_time: f64,
|
||||||
},
|
},
|
||||||
@@ -110,6 +118,10 @@ pub struct FaceDetectorApp {
|
|||||||
current_image_handle: Option<image::Handle>,
|
current_image_handle: Option<image::Handle>,
|
||||||
processed_image_handle: Option<image::Handle>,
|
processed_image_handle: Option<image::Handle>,
|
||||||
second_image_handle: Option<image::Handle>,
|
second_image_handle: Option<image::Handle>,
|
||||||
|
|
||||||
|
// Face ROI handles for comparison display
|
||||||
|
image1_face_roi_handles: Vec<image::Handle>,
|
||||||
|
image2_face_roi_handles: Vec<image::Handle>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for FaceDetectorApp {
|
impl Default for FaceDetectorApp {
|
||||||
@@ -121,7 +133,10 @@ impl Default for FaceDetectorApp {
|
|||||||
output_path: None,
|
output_path: None,
|
||||||
threshold: 0.8,
|
threshold: 0.8,
|
||||||
nms_threshold: 0.3,
|
nms_threshold: 0.3,
|
||||||
|
#[cfg(not(any(feature = "mnn-metal", feature = "ort-cuda")))]
|
||||||
executor_type: ExecutorType::MnnCpu,
|
executor_type: ExecutorType::MnnCpu,
|
||||||
|
#[cfg(feature = "ort-cuda")]
|
||||||
|
executor_type: ExecutorType::OrtCuda,
|
||||||
is_processing: false,
|
is_processing: false,
|
||||||
progress: 0.0,
|
progress: 0.0,
|
||||||
status_message: "Ready".to_string(),
|
status_message: "Ready".to_string(),
|
||||||
@@ -130,6 +145,8 @@ impl Default for FaceDetectorApp {
|
|||||||
current_image_handle: None,
|
current_image_handle: None,
|
||||||
processed_image_handle: None,
|
processed_image_handle: None,
|
||||||
second_image_handle: None,
|
second_image_handle: None,
|
||||||
|
image1_face_roi_handles: Vec::new(),
|
||||||
|
image2_face_roi_handles: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -313,6 +330,8 @@ impl FaceDetectorApp {
|
|||||||
self.detection_result = None;
|
self.detection_result = None;
|
||||||
self.comparison_result = None;
|
self.comparison_result = None;
|
||||||
self.processed_image_handle = None;
|
self.processed_image_handle = None;
|
||||||
|
self.image1_face_roi_handles.clear();
|
||||||
|
self.image2_face_roi_handles.clear();
|
||||||
self.status_message = "Results cleared".to_string();
|
self.status_message = "Results cleared".to_string();
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
@@ -356,6 +375,8 @@ impl FaceDetectorApp {
|
|||||||
ComparisonResult::Success {
|
ComparisonResult::Success {
|
||||||
best_similarity,
|
best_similarity,
|
||||||
processing_time,
|
processing_time,
|
||||||
|
image1_face_rois,
|
||||||
|
image2_face_rois,
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
let interpretation = if *best_similarity > 0.8 {
|
let interpretation = if *best_similarity > 0.8 {
|
||||||
@@ -372,6 +393,16 @@ impl FaceDetectorApp {
|
|||||||
"Comparison complete! Similarity: {:.3} - {} (Processing time: {:.2}s)",
|
"Comparison complete! Similarity: {:.3} - {} (Processing time: {:.2}s)",
|
||||||
best_similarity, interpretation, processing_time
|
best_similarity, interpretation, processing_time
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Convert face ROIs to image handles
|
||||||
|
let image1_handles = convert_face_rois_to_handles(image1_face_rois.clone());
|
||||||
|
let image2_handles = convert_face_rois_to_handles(image2_face_rois.clone());
|
||||||
|
|
||||||
|
self.comparison_result = Some(result);
|
||||||
|
return Task::perform(
|
||||||
|
async move { (image1_handles, image2_handles) },
|
||||||
|
|(h1, h2)| Message::FaceRoisLoaded(h1, h2),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
ComparisonResult::Error(error) => {
|
ComparisonResult::Error(error) => {
|
||||||
self.status_message = format!("Comparison failed: {}", error);
|
self.status_message = format!("Comparison failed: {}", error);
|
||||||
@@ -382,6 +413,12 @@ impl FaceDetectorApp {
|
|||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Message::FaceRoisLoaded(image1_handles, image2_handles) => {
|
||||||
|
self.image1_face_roi_handles = image1_handles;
|
||||||
|
self.image2_face_roi_handles = image2_handles;
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
|
||||||
Message::ProgressUpdate(progress) => {
|
Message::ProgressUpdate(progress) => {
|
||||||
self.progress = progress;
|
self.progress = progress;
|
||||||
Task::none()
|
Task::none()
|
||||||
@@ -765,6 +802,8 @@ impl FaceDetectorApp {
|
|||||||
ComparisonResult::Success {
|
ComparisonResult::Success {
|
||||||
image1_faces,
|
image1_faces,
|
||||||
image2_faces,
|
image2_faces,
|
||||||
|
image1_face_rois: _,
|
||||||
|
image2_face_rois: _,
|
||||||
best_similarity,
|
best_similarity,
|
||||||
processing_time,
|
processing_time,
|
||||||
} => {
|
} => {
|
||||||
@@ -790,7 +829,7 @@ impl FaceDetectorApp {
|
|||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
column![
|
let mut result_column = column![
|
||||||
text("Comparison Results").size(18),
|
text("Comparison Results").size(18),
|
||||||
text(format!("First image faces: {}", image1_faces)),
|
text(format!("First image faces: {}", image1_faces)),
|
||||||
text(format!("Second image faces: {}", image2_faces)),
|
text(format!("Second image faces: {}", image2_faces)),
|
||||||
@@ -800,7 +839,89 @@ impl FaceDetectorApp {
|
|||||||
}),
|
}),
|
||||||
text(format!("Processing time: {:.2}s", processing_time)),
|
text(format!("Processing time: {:.2}s", processing_time)),
|
||||||
]
|
]
|
||||||
.spacing(5)
|
.spacing(5);
|
||||||
|
|
||||||
|
// Add face ROI displays if available
|
||||||
|
if !self.image1_face_roi_handles.is_empty()
|
||||||
|
|| !self.image2_face_roi_handles.is_empty()
|
||||||
|
{
|
||||||
|
result_column = result_column.push(text("Detected Faces").size(16));
|
||||||
|
|
||||||
|
// Create face ROI rows
|
||||||
|
let image1_faces_row = if !self.image1_face_roi_handles.is_empty() {
|
||||||
|
let faces: Element<'_, Message> = self
|
||||||
|
.image1_face_roi_handles
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.fold(row![].spacing(5), |row, (i, handle)| {
|
||||||
|
row.push(
|
||||||
|
column![
|
||||||
|
text(format!("Face {}", i + 1)).size(12),
|
||||||
|
container(
|
||||||
|
image(handle.clone())
|
||||||
|
.width(80)
|
||||||
|
.height(80)
|
||||||
|
.content_fit(iced::ContentFit::Cover)
|
||||||
|
)
|
||||||
|
.style(container::bordered_box)
|
||||||
|
.padding(2),
|
||||||
|
]
|
||||||
|
.spacing(2)
|
||||||
|
.align_x(Alignment::Center),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.into();
|
||||||
|
|
||||||
|
column![
|
||||||
|
text("First Image Faces:").size(14),
|
||||||
|
scrollable(faces).direction(scrollable::Direction::Horizontal(
|
||||||
|
scrollable::Scrollbar::new()
|
||||||
|
)),
|
||||||
|
]
|
||||||
|
.spacing(5)
|
||||||
|
} else {
|
||||||
|
column![text("First Image Faces: None detected").size(14)]
|
||||||
|
};
|
||||||
|
|
||||||
|
let image2_faces_row = if !self.image2_face_roi_handles.is_empty() {
|
||||||
|
let faces: Element<'_, Message> = self
|
||||||
|
.image2_face_roi_handles
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.fold(row![].spacing(5), |row, (i, handle)| {
|
||||||
|
row.push(
|
||||||
|
column![
|
||||||
|
text(format!("Face {}", i + 1)).size(12),
|
||||||
|
container(
|
||||||
|
image(handle.clone())
|
||||||
|
.width(80)
|
||||||
|
.height(80)
|
||||||
|
.content_fit(iced::ContentFit::Cover)
|
||||||
|
)
|
||||||
|
.style(container::bordered_box)
|
||||||
|
.padding(2),
|
||||||
|
]
|
||||||
|
.spacing(2)
|
||||||
|
.align_x(Alignment::Center),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.into();
|
||||||
|
|
||||||
|
column![
|
||||||
|
text("Second Image Faces:").size(14),
|
||||||
|
scrollable(faces).direction(scrollable::Direction::Horizontal(
|
||||||
|
scrollable::Scrollbar::new()
|
||||||
|
)),
|
||||||
|
]
|
||||||
|
.spacing(5)
|
||||||
|
} else {
|
||||||
|
column![text("Second Image Faces: None detected").size(14)]
|
||||||
|
};
|
||||||
|
|
||||||
|
result_column = result_column.push(image1_faces_row).push(image2_faces_row);
|
||||||
|
}
|
||||||
|
|
||||||
|
result_column
|
||||||
}
|
}
|
||||||
ComparisonResult::Error(error) => column![
|
ComparisonResult::Error(error) => column![
|
||||||
text("Comparison Results").size(18),
|
text("Comparison Results").size(18),
|
||||||
@@ -816,19 +937,26 @@ impl FaceDetectorApp {
|
|||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
column![file_section, comparison_image_section, controls, results]
|
scrollable(
|
||||||
.spacing(20)
|
column![file_section, comparison_image_section, controls, results]
|
||||||
.padding(20)
|
.spacing(20)
|
||||||
.into()
|
.padding(20),
|
||||||
|
)
|
||||||
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn settings_view(&self) -> Element<'_, Message> {
|
fn settings_view(&self) -> Element<'_, Message> {
|
||||||
let executor_options = vec![
|
#[allow(unused_mut)]
|
||||||
ExecutorType::MnnCpu,
|
let mut executor_options = vec![ExecutorType::MnnCpu, ExecutorType::OnnxCpu];
|
||||||
ExecutorType::MnnMetal,
|
|
||||||
ExecutorType::MnnCoreML,
|
#[cfg(feature = "mnn-metal")]
|
||||||
ExecutorType::OnnxCpu,
|
executor_options.push(ExecutorType::MnnMetal);
|
||||||
];
|
|
||||||
|
#[cfg(feature = "mnn-coreml")]
|
||||||
|
executor_options.push(ExecutorType::MnnCoreML);
|
||||||
|
|
||||||
|
#[cfg(feature = "ort-cuda")]
|
||||||
|
executor_options.push(ExecutorType::OrtCuda);
|
||||||
|
|
||||||
container(
|
container(
|
||||||
column![
|
column![
|
||||||
@@ -874,18 +1002,52 @@ impl std::fmt::Display for ExecutorType {
|
|||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
ExecutorType::MnnCpu => write!(f, "MNN (CPU)"),
|
ExecutorType::MnnCpu => write!(f, "MNN (CPU)"),
|
||||||
|
#[cfg(feature = "mnn-metal")]
|
||||||
ExecutorType::MnnMetal => write!(f, "MNN (Metal)"),
|
ExecutorType::MnnMetal => write!(f, "MNN (Metal)"),
|
||||||
|
#[cfg(feature = "mnn-coreml")]
|
||||||
ExecutorType::MnnCoreML => write!(f, "MNN (CoreML)"),
|
ExecutorType::MnnCoreML => write!(f, "MNN (CoreML)"),
|
||||||
ExecutorType::OnnxCpu => write!(f, "ONNX (CPU)"),
|
ExecutorType::OnnxCpu => write!(f, "ONNX (CPU)"),
|
||||||
|
#[cfg(feature = "ort-cuda")]
|
||||||
|
ExecutorType::OrtCuda => write!(f, "ONNX (CUDA)"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper function to convert face ROIs to image handles
|
||||||
|
fn convert_face_rois_to_handles(face_rois: Vec<ndarray::Array3<u8>>) -> Vec<image::Handle> {
|
||||||
|
face_rois
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|roi| {
|
||||||
|
// Convert ndarray to image::RgbImage
|
||||||
|
let (height, width, _) = roi.dim();
|
||||||
|
let (raw_data, _offset) = roi.into_raw_vec_and_offset();
|
||||||
|
|
||||||
|
if let Some(img) = RgbImage::from_raw(width as u32, height as u32, raw_data) {
|
||||||
|
// Convert to PNG bytes
|
||||||
|
let mut buffer = Vec::new();
|
||||||
|
let mut cursor = std::io::Cursor::new(&mut buffer);
|
||||||
|
if DynamicImage::ImageRgb8(img)
|
||||||
|
.write_to(&mut cursor, ImageFormat::Png)
|
||||||
|
.is_ok()
|
||||||
|
{
|
||||||
|
return Some(image::Handle::from_bytes(buffer));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn run() -> iced::Result {
|
pub fn run() -> iced::Result {
|
||||||
|
let settings = Settings {
|
||||||
|
antialiasing: true,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
iced::application(
|
iced::application(
|
||||||
"Face Detector",
|
"Face Detector",
|
||||||
FaceDetectorApp::update,
|
FaceDetectorApp::update,
|
||||||
FaceDetectorApp::view,
|
FaceDetectorApp::view,
|
||||||
)
|
)
|
||||||
|
.settings(settings)
|
||||||
.run_with(FaceDetectorApp::new)
|
.run_with(FaceDetectorApp::new)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use std::path::PathBuf;
|
|||||||
|
|
||||||
use crate::errors;
|
use crate::errors;
|
||||||
use crate::facedet::{FaceDetectionConfig, FaceDetector, retinaface};
|
use crate::facedet::{FaceDetectionConfig, FaceDetector, retinaface};
|
||||||
use crate::faceembed::{FaceNetEmbedder, facenet};
|
use crate::faceembed::facenet;
|
||||||
use crate::gui::app::{ComparisonResult, DetectionResult, ExecutorType};
|
use crate::gui::app::{ComparisonResult, DetectionResult, ExecutorType};
|
||||||
use bounding_box::Aabb2;
|
use bounding_box::Aabb2;
|
||||||
use bounding_box::roi::MultiRoi as _;
|
use bounding_box::roi::MultiRoi as _;
|
||||||
@@ -70,11 +70,19 @@ impl FaceDetectionBridge {
|
|||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok((image1_faces, image2_faces, best_similarity)) => {
|
Ok((
|
||||||
|
image1_faces,
|
||||||
|
image2_faces,
|
||||||
|
image1_face_rois,
|
||||||
|
image2_face_rois,
|
||||||
|
best_similarity,
|
||||||
|
)) => {
|
||||||
let processing_time = start_time.elapsed().as_secs_f64();
|
let processing_time = start_time.elapsed().as_secs_f64();
|
||||||
ComparisonResult::Success {
|
ComparisonResult::Success {
|
||||||
image1_faces,
|
image1_faces,
|
||||||
image2_faces,
|
image2_faces,
|
||||||
|
image1_face_rois,
|
||||||
|
image2_face_rois,
|
||||||
best_similarity,
|
best_similarity,
|
||||||
processing_time,
|
processing_time,
|
||||||
}
|
}
|
||||||
@@ -106,17 +114,34 @@ impl FaceDetectionBridge {
|
|||||||
|
|
||||||
// Create detector and detect faces
|
// Create detector and detect faces
|
||||||
let faces = match executor_type {
|
let faces = match executor_type {
|
||||||
ExecutorType::MnnCpu | ExecutorType::MnnMetal | ExecutorType::MnnCoreML => {
|
ExecutorType::MnnCpu => {
|
||||||
let forward_type = match executor_type {
|
|
||||||
ExecutorType::MnnCpu => mnn::ForwardType::CPU,
|
|
||||||
ExecutorType::MnnMetal => mnn::ForwardType::Metal,
|
|
||||||
ExecutorType::MnnCoreML => mnn::ForwardType::CoreML,
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut detector = retinaface::mnn::FaceDetection::builder(RETINAFACE_MODEL_MNN)
|
let mut detector = retinaface::mnn::FaceDetection::builder(RETINAFACE_MODEL_MNN)
|
||||||
.map_err(|e| format!("Failed to create MNN detector: {}", e))?
|
.map_err(|e| format!("Failed to create MNN detector: {}", e))?
|
||||||
.with_forward_type(forward_type)
|
.with_forward_type(mnn::ForwardType::CPU)
|
||||||
|
.build()
|
||||||
|
.map_err(|e| format!("Failed to build MNN detector: {}", e))?;
|
||||||
|
|
||||||
|
detector
|
||||||
|
.detect_faces(image_array.view(), &config)
|
||||||
|
.map_err(|e| format!("Detection failed: {}", e))?
|
||||||
|
}
|
||||||
|
#[cfg(feature = "mnn-metal")]
|
||||||
|
ExecutorType::MnnMetal => {
|
||||||
|
let mut detector = retinaface::mnn::FaceDetection::builder(RETINAFACE_MODEL_MNN)
|
||||||
|
.map_err(|e| format!("Failed to create MNN detector: {}", e))?
|
||||||
|
.with_forward_type(mnn::ForwardType::Metal)
|
||||||
|
.build()
|
||||||
|
.map_err(|e| format!("Failed to build MNN detector: {}", e))?;
|
||||||
|
|
||||||
|
detector
|
||||||
|
.detect_faces(image_array.view(), &config)
|
||||||
|
.map_err(|e| format!("Detection failed: {}", e))?
|
||||||
|
}
|
||||||
|
#[cfg(feature = "mnn-coreml")]
|
||||||
|
ExecutorType::MnnCoreML => {
|
||||||
|
let mut detector = retinaface::mnn::FaceDetection::builder(RETINAFACE_MODEL_MNN)
|
||||||
|
.map_err(|e| format!("Failed to create MNN detector: {}", e))?
|
||||||
|
.with_forward_type(mnn::ForwardType::CoreML)
|
||||||
.build()
|
.build()
|
||||||
.map_err(|e| format!("Failed to build MNN detector: {}", e))?;
|
.map_err(|e| format!("Failed to build MNN detector: {}", e))?;
|
||||||
|
|
||||||
@@ -134,6 +159,21 @@ impl FaceDetectionBridge {
|
|||||||
.detect_faces(image_array.view(), &config)
|
.detect_faces(image_array.view(), &config)
|
||||||
.map_err(|e| format!("Detection failed: {}", e))?
|
.map_err(|e| format!("Detection failed: {}", e))?
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "ort-cuda")]
|
||||||
|
ExecutorType::OrtCuda => {
|
||||||
|
use crate::ort_ep::ExecutionProvider;
|
||||||
|
|
||||||
|
let ep = ExecutionProvider::CUDA;
|
||||||
|
let mut detector = retinaface::ort::FaceDetection::builder(RETINAFACE_MODEL_ONNX)
|
||||||
|
.map_err(|e| format!("Failed to create ONNX CUDA detector: {}", e))?
|
||||||
|
.with_execution_providers([ep])
|
||||||
|
.build()
|
||||||
|
.map_err(|e| format!("Failed to build ONNX CUDA detector: {}", e))?;
|
||||||
|
|
||||||
|
detector
|
||||||
|
.detect_faces(image_array.view(), &config)
|
||||||
|
.map_err(|e| format!("CUDA detection failed: {}", e))?
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let faces_count = faces.bbox.len();
|
let faces_count = faces.bbox.len();
|
||||||
@@ -180,53 +220,209 @@ impl FaceDetectionBridge {
|
|||||||
threshold: f32,
|
threshold: f32,
|
||||||
nms_threshold: f32,
|
nms_threshold: f32,
|
||||||
executor_type: ExecutorType,
|
executor_type: ExecutorType,
|
||||||
) -> Result<(usize, usize, f32), Box<dyn std::error::Error + Send + Sync>> {
|
) -> Result<
|
||||||
|
(usize, usize, Vec<Array3<u8>>, Vec<Array3<u8>>, f32),
|
||||||
|
Box<dyn std::error::Error + Send + Sync>,
|
||||||
|
> {
|
||||||
// Create detector and embedder, detect faces and generate embeddings
|
// Create detector and embedder, detect faces and generate embeddings
|
||||||
let (faces1, faces2, best_similarity) = match executor_type {
|
let (image1_faces, image2_faces, image1_rois, image2_rois, best_similarity) =
|
||||||
ExecutorType::MnnCpu | ExecutorType::MnnMetal | ExecutorType::MnnCoreML => {
|
match executor_type {
|
||||||
let forward_type = match executor_type {
|
ExecutorType::MnnCpu => {
|
||||||
ExecutorType::MnnCpu => mnn::ForwardType::CPU,
|
let mut detector =
|
||||||
ExecutorType::MnnMetal => mnn::ForwardType::Metal,
|
retinaface::mnn::FaceDetection::builder(RETINAFACE_MODEL_MNN)
|
||||||
ExecutorType::MnnCoreML => mnn::ForwardType::CoreML,
|
.map_err(|e| format!("Failed to create MNN detector: {}", e))?
|
||||||
_ => unreachable!(),
|
.with_forward_type(mnn::ForwardType::CPU)
|
||||||
};
|
.build()
|
||||||
|
.map_err(|e| format!("Failed to build MNN detector: {}", e))?;
|
||||||
|
|
||||||
let mut detector = retinaface::mnn::FaceDetection::builder(RETINAFACE_MODEL_MNN)
|
let mut embedder = facenet::mnn::EmbeddingGenerator::builder(FACENET_MODEL_MNN)
|
||||||
.map_err(|e| format!("Failed to create MNN detector: {}", e))?
|
.map_err(|e| format!("Failed to create MNN embedder: {}", e))?
|
||||||
.with_forward_type(forward_type.clone())
|
.with_forward_type(mnn::ForwardType::CPU)
|
||||||
.build()
|
.build()
|
||||||
.map_err(|e| format!("Failed to build MNN detector: {}", e))?;
|
.map_err(|e| format!("Failed to build MNN embedder: {}", e))?;
|
||||||
|
|
||||||
let mut embedder = facenet::mnn::EmbeddingGenerator::builder(FACENET_MODEL_MNN)
|
let img_1 = run_detection(
|
||||||
.map_err(|e| format!("Failed to create MNN embedder: {}", e))?
|
image1_path,
|
||||||
.with_forward_type(forward_type)
|
&mut detector,
|
||||||
.build()
|
&mut embedder,
|
||||||
.map_err(|e| format!("Failed to build MNN embedder: {}", e))?;
|
threshold,
|
||||||
|
nms_threshold,
|
||||||
|
2,
|
||||||
|
)?;
|
||||||
|
let img_2 = run_detection(
|
||||||
|
image2_path,
|
||||||
|
&mut detector,
|
||||||
|
&mut embedder,
|
||||||
|
threshold,
|
||||||
|
nms_threshold,
|
||||||
|
2,
|
||||||
|
)?;
|
||||||
|
|
||||||
let img_1 = run_detection(
|
let image1_rois = img_1.rois;
|
||||||
image1_path,
|
let image2_rois = img_2.rois;
|
||||||
&mut detector,
|
let image1_bbox_len = img_1.bbox.len();
|
||||||
&mut embedder,
|
let image2_bbox_len = img_2.bbox.len();
|
||||||
threshold,
|
let best_similarity = compare_faces(&img_1.embeddings, &img_2.embeddings)?;
|
||||||
nms_threshold,
|
|
||||||
2,
|
|
||||||
)?;
|
|
||||||
let img_2 = run_detection(
|
|
||||||
image2_path,
|
|
||||||
&mut detector,
|
|
||||||
&mut embedder,
|
|
||||||
threshold,
|
|
||||||
nms_threshold,
|
|
||||||
2,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let best_similarity = compare_faces(&img_1.embeddings, &img_2.embeddings)?;
|
(
|
||||||
(img_1, img_2, best_similarity)
|
image1_bbox_len,
|
||||||
}
|
image2_bbox_len,
|
||||||
ExecutorType::OnnxCpu => unimplemented!(),
|
image1_rois,
|
||||||
};
|
image2_rois,
|
||||||
|
best_similarity,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
#[cfg(feature = "mnn-metal")]
|
||||||
|
ExecutorType::MnnMetal => {
|
||||||
|
let mut detector =
|
||||||
|
retinaface::mnn::FaceDetection::builder(RETINAFACE_MODEL_MNN)
|
||||||
|
.map_err(|e| format!("Failed to create MNN detector: {}", e))?
|
||||||
|
.with_forward_type(mnn::ForwardType::Metal)
|
||||||
|
.build()
|
||||||
|
.map_err(|e| format!("Failed to build MNN detector: {}", e))?;
|
||||||
|
|
||||||
Ok((faces1.bbox.len(), faces2.bbox.len(), best_similarity))
|
let mut embedder = facenet::mnn::EmbeddingGenerator::builder(FACENET_MODEL_MNN)
|
||||||
|
.map_err(|e| format!("Failed to create MNN embedder: {}", e))?
|
||||||
|
.with_forward_type(mnn::ForwardType::Metal)
|
||||||
|
.build()
|
||||||
|
.map_err(|e| format!("Failed to build MNN embedder: {}", e))?;
|
||||||
|
|
||||||
|
let img_1 = run_detection(
|
||||||
|
image1_path,
|
||||||
|
&mut detector,
|
||||||
|
&mut embedder,
|
||||||
|
threshold,
|
||||||
|
nms_threshold,
|
||||||
|
2,
|
||||||
|
)?;
|
||||||
|
let img_2 = run_detection(
|
||||||
|
image2_path,
|
||||||
|
&mut detector,
|
||||||
|
&mut embedder,
|
||||||
|
threshold,
|
||||||
|
nms_threshold,
|
||||||
|
2,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let image1_rois = img_1.rois;
|
||||||
|
let image2_rois = img_2.rois;
|
||||||
|
let image1_bbox_len = img_1.bbox.len();
|
||||||
|
let image2_bbox_len = img_2.bbox.len();
|
||||||
|
let best_similarity = compare_faces(&img_1.embeddings, &img_2.embeddings)?;
|
||||||
|
|
||||||
|
(
|
||||||
|
image1_bbox_len,
|
||||||
|
image2_bbox_len,
|
||||||
|
image1_rois,
|
||||||
|
image2_rois,
|
||||||
|
best_similarity,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
#[cfg(feature = "mnn-coreml")]
|
||||||
|
ExecutorType::MnnCoreML => {
|
||||||
|
let mut detector =
|
||||||
|
retinaface::mnn::FaceDetection::builder(RETINAFACE_MODEL_MNN)
|
||||||
|
.map_err(|e| format!("Failed to create MNN detector: {}", e))?
|
||||||
|
.with_forward_type(mnn::ForwardType::CoreML)
|
||||||
|
.build()
|
||||||
|
.map_err(|e| format!("Failed to build MNN detector: {}", e))?;
|
||||||
|
|
||||||
|
let mut embedder = facenet::mnn::EmbeddingGenerator::builder(FACENET_MODEL_MNN)
|
||||||
|
.map_err(|e| format!("Failed to create MNN embedder: {}", e))?
|
||||||
|
.with_forward_type(mnn::ForwardType::CoreML)
|
||||||
|
.build()
|
||||||
|
.map_err(|e| format!("Failed to build MNN embedder: {}", e))?;
|
||||||
|
|
||||||
|
let img_1 = run_detection(
|
||||||
|
image1_path,
|
||||||
|
&mut detector,
|
||||||
|
&mut embedder,
|
||||||
|
threshold,
|
||||||
|
nms_threshold,
|
||||||
|
2,
|
||||||
|
)?;
|
||||||
|
let img_2 = run_detection(
|
||||||
|
image2_path,
|
||||||
|
&mut detector,
|
||||||
|
&mut embedder,
|
||||||
|
threshold,
|
||||||
|
nms_threshold,
|
||||||
|
2,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let image1_rois = img_1.rois;
|
||||||
|
let image2_rois = img_2.rois;
|
||||||
|
let image1_bbox_len = img_1.bbox.len();
|
||||||
|
let image2_bbox_len = img_2.bbox.len();
|
||||||
|
let best_similarity = compare_faces(&img_1.embeddings, &img_2.embeddings)?;
|
||||||
|
|
||||||
|
(
|
||||||
|
image1_bbox_len,
|
||||||
|
image2_bbox_len,
|
||||||
|
image1_rois,
|
||||||
|
image2_rois,
|
||||||
|
best_similarity,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
ExecutorType::OnnxCpu => unimplemented!("ONNX face comparison not yet implemented"),
|
||||||
|
#[cfg(feature = "ort-cuda")]
|
||||||
|
ExecutorType::OrtCuda => {
|
||||||
|
use crate::ort_ep::ExecutionProvider;
|
||||||
|
let ep = ExecutionProvider::CUDA;
|
||||||
|
let mut detector =
|
||||||
|
retinaface::ort::FaceDetection::builder(RETINAFACE_MODEL_ONNX)
|
||||||
|
.map_err(|e| format!("Failed to create MNN detector: {}", e))?
|
||||||
|
.with_execution_providers([ep])
|
||||||
|
.build()
|
||||||
|
.map_err(|e| format!("Failed to build MNN detector: {}", e))?;
|
||||||
|
|
||||||
|
let mut embedder =
|
||||||
|
facenet::ort::EmbeddingGenerator::builder(FACENET_MODEL_ONNX)
|
||||||
|
.map_err(|e| format!("Failed to create MNN embedder: {}", e))?
|
||||||
|
.with_execution_providers([ep])
|
||||||
|
.build()
|
||||||
|
.map_err(|e| format!("Failed to build MNN embedder: {}", e))?;
|
||||||
|
|
||||||
|
let img_1 = run_detection(
|
||||||
|
image1_path,
|
||||||
|
&mut detector,
|
||||||
|
&mut embedder,
|
||||||
|
threshold,
|
||||||
|
nms_threshold,
|
||||||
|
2,
|
||||||
|
)?;
|
||||||
|
let img_2 = run_detection(
|
||||||
|
image2_path,
|
||||||
|
&mut detector,
|
||||||
|
&mut embedder,
|
||||||
|
threshold,
|
||||||
|
nms_threshold,
|
||||||
|
2,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let image1_rois = img_1.rois;
|
||||||
|
let image2_rois = img_2.rois;
|
||||||
|
let image1_bbox_len = img_1.bbox.len();
|
||||||
|
let image2_bbox_len = img_2.bbox.len();
|
||||||
|
let best_similarity = compare_faces(&img_1.embeddings, &img_2.embeddings)?;
|
||||||
|
|
||||||
|
(
|
||||||
|
image1_bbox_len,
|
||||||
|
image2_bbox_len,
|
||||||
|
image1_rois,
|
||||||
|
image2_rois,
|
||||||
|
best_similarity,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((
|
||||||
|
image1_faces,
|
||||||
|
image2_faces,
|
||||||
|
image1_rois,
|
||||||
|
image2_rois,
|
||||||
|
best_similarity,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -296,19 +492,27 @@ where
|
|||||||
.change_context(errors::Error)
|
.change_context(errors::Error)
|
||||||
.attach_printable("Failed to detect faces")?;
|
.attach_printable("Failed to detect faces")?;
|
||||||
|
|
||||||
for bbox in &output.bbox {
|
let bboxes = output
|
||||||
|
.bbox
|
||||||
|
.iter()
|
||||||
|
.inspect(|bbox| tracing::info!("Raw bbox: {:?}", bbox))
|
||||||
|
.map(|bbox| bbox.as_::<f32>().scale_uniform(1.30).as_::<usize>())
|
||||||
|
.inspect(|bbox| tracing::info!("Padded bbox: {:?}", bbox))
|
||||||
|
.collect_vec();
|
||||||
|
for bbox in &bboxes {
|
||||||
tracing::info!("Detected face: {:?}", bbox);
|
tracing::info!("Detected face: {:?}", bbox);
|
||||||
use bounding_box::draw::*;
|
use bounding_box::draw::*;
|
||||||
array.draw(bbox, color::palette::css::GREEN_YELLOW.to_rgba8(), 1);
|
array.draw(bbox, color::palette::css::GREEN_YELLOW.to_rgba8(), 1);
|
||||||
}
|
}
|
||||||
|
use itertools::Itertools;
|
||||||
let face_rois = array
|
let face_rois = array
|
||||||
.view()
|
.view()
|
||||||
.multi_roi(&output.bbox)
|
.multi_roi(&bboxes)
|
||||||
.change_context(Error)?
|
.change_context(Error)?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|roi| {
|
.map(|roi| {
|
||||||
roi.as_standard_layout()
|
roi.as_standard_layout()
|
||||||
.fast_resize(320, 320, &ResizeOptions::default())
|
.fast_resize(224, 224, &ResizeOptions::default())
|
||||||
.change_context(Error)
|
.change_context(Error)
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<_>>>()?;
|
.collect::<Result<Vec<_>>>()?;
|
||||||
@@ -322,7 +526,7 @@ where
|
|||||||
let og_size = chunk.len();
|
let og_size = chunk.len();
|
||||||
if chunk.len() < chunk_size {
|
if chunk.len() < chunk_size {
|
||||||
tracing::warn!("Chunk size is less than 8, padding with zeros");
|
tracing::warn!("Chunk size is less than 8, padding with zeros");
|
||||||
let zeros = Array3::zeros((320, 320, 3));
|
let zeros = Array3::zeros((224, 224, 3));
|
||||||
let chunk: Vec<_> = chunk
|
let chunk: Vec<_> = chunk
|
||||||
.iter()
|
.iter()
|
||||||
.map(|arr| arr.reborrow())
|
.map(|arr| arr.reborrow())
|
||||||
@@ -358,7 +562,7 @@ where
|
|||||||
.collect::<Vec<Array1<f32>>>();
|
.collect::<Vec<Array1<f32>>>();
|
||||||
|
|
||||||
Ok(DetectionOutput {
|
Ok(DetectionOutput {
|
||||||
bbox: output.bbox,
|
bbox: bboxes,
|
||||||
rois: face_rois,
|
rois: face_rois,
|
||||||
embeddings,
|
embeddings,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ use ort::execution_providers::TensorRTExecutionProvider;
|
|||||||
use ort::execution_providers::{CPUExecutionProvider, ExecutionProviderDispatch};
|
use ort::execution_providers::{CPUExecutionProvider, ExecutionProviderDispatch};
|
||||||
|
|
||||||
/// Supported execution providers for ONNX Runtime
|
/// Supported execution providers for ONNX Runtime
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub enum ExecutionProvider {
|
pub enum ExecutionProvider {
|
||||||
/// CPU execution provider (always available)
|
/// CPU execution provider (always available)
|
||||||
CPU,
|
CPU,
|
||||||
|
|||||||
@@ -1,65 +0,0 @@
|
|||||||
//! Simple test to verify that the bbox replacement from bbox::BBox to bounding_box::Aabb2 works correctly
|
|
||||||
|
|
||||||
use bounding_box::Aabb2;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_bbox_replacement() {
|
|
||||||
// Test basic construction
|
|
||||||
let bbox1 = Aabb2::from_xywh(10.0, 20.0, 100.0, 200.0);
|
|
||||||
assert_eq!(bbox1.x1(), 10.0);
|
|
||||||
assert_eq!(bbox1.y1(), 20.0);
|
|
||||||
assert_eq!(bbox1.x2(), 110.0);
|
|
||||||
assert_eq!(bbox1.y2(), 220.0);
|
|
||||||
|
|
||||||
// Test from coordinates
|
|
||||||
let bbox2 = Aabb2::from_x1y1x2y2(10.0, 20.0, 110.0, 220.0);
|
|
||||||
assert_eq!(bbox2.x1(), 10.0);
|
|
||||||
assert_eq!(bbox2.y1(), 20.0);
|
|
||||||
assert_eq!(bbox2.x2(), 110.0);
|
|
||||||
assert_eq!(bbox2.y2(), 220.0);
|
|
||||||
|
|
||||||
// Test equality
|
|
||||||
assert_eq!(bbox1.x1(), bbox2.x1());
|
|
||||||
assert_eq!(bbox1.y1(), bbox2.y1());
|
|
||||||
assert_eq!(bbox1.x2(), bbox2.x2());
|
|
||||||
assert_eq!(bbox1.y2(), bbox2.y2());
|
|
||||||
|
|
||||||
// Test contains
|
|
||||||
let smaller = Aabb2::from_xywh(20.0, 30.0, 50.0, 100.0);
|
|
||||||
assert!(bbox1.contains_bbox(&smaller));
|
|
||||||
|
|
||||||
// Test padding
|
|
||||||
let padded = bbox1.padding(10.0);
|
|
||||||
assert_eq!(padded.x1(), 5.0);
|
|
||||||
assert_eq!(padded.y1(), 15.0);
|
|
||||||
assert_eq!(padded.x2(), 115.0);
|
|
||||||
assert_eq!(padded.y2(), 225.0);
|
|
||||||
|
|
||||||
// Test scale
|
|
||||||
let scaled = bbox1.scale(nalgebra::Vector2::new(2.0, 2.0));
|
|
||||||
assert_eq!(scaled.x1(), -40.0);
|
|
||||||
assert_eq!(scaled.y1(), -80.0);
|
|
||||||
assert_eq!(scaled.x2(), 160.0);
|
|
||||||
assert_eq!(scaled.y2(), 320.0);
|
|
||||||
|
|
||||||
println!("All bbox replacement tests passed!");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_integer_bbox() {
|
|
||||||
// Test with integer types
|
|
||||||
let bbox = Aabb2::from_xywh(1, 2, 10, 20);
|
|
||||||
assert_eq!(bbox.x1(), 1);
|
|
||||||
assert_eq!(bbox.y1(), 2);
|
|
||||||
assert_eq!(bbox.x2(), 11);
|
|
||||||
assert_eq!(bbox.y2(), 22);
|
|
||||||
|
|
||||||
let area = bbox.area();
|
|
||||||
assert_eq!(area, 200);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
test_bbox_replacement();
|
|
||||||
test_integer_bbox();
|
|
||||||
println!("Test completed successfully!");
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user