first commit
Browse files- .gitattributes +3 -0
- AX650/fall_ax650_npu3.axmodel +3 -0
- README.md +91 -0
- axmodel_infer_fall.py +378 -0
- axmodel_res.jpg +3 -0
- config.json +0 -0
- fall4.png +3 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,6 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
*.jpg filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
*.axmodel filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
*.png filter=lfs diff=lfs merge=lfs -text
|
AX650/fall_ax650_npu3.axmodel
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:0ee38051240bd19e4a9b77030e983378ced8ac581f9f7c824349527340613eb8
|
| 3 |
+
size 1129098
|
README.md
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: agpl-3.0
|
| 3 |
+
language:
|
| 4 |
+
- en
|
| 5 |
+
pipeline_tag: object-detection
|
| 6 |
+
tags:
|
| 7 |
+
- Axera
|
| 8 |
+
- YOLOv7-pose
|
| 9 |
+
- NPU
|
| 10 |
+
- Object Detection
|
| 11 |
+
- Keypoint Detection
|
| 12 |
+
---
|
| 13 |
+
|
| 14 |
+
# fall-axera
|
| 15 |
+
|
| 16 |
+
This version of **fall-axera** has been converted to run on the Axera NPU using **w8a16** quantization. It is trained with modified yolov7-pose model to detect bbox and 14 keypoints of human, and to determine whether a fall behavior is likely to occur.
|
| 17 |
+
|
| 18 |
+
## Supported Classes
|
| 19 |
+
|
| 20 |
+
This model is trained to detect the following classes:
|
| 21 |
+
1. **normal**
|
| 22 |
+
2. **fall**
|
| 23 |
+
|
| 24 |
+
## Supported keypoints
|
| 25 |
+
This model is trained to detect the following 14 keypoints:
|
| 26 |
+
```
|
| 27 |
+
"keypoints": { 0: "right shoulder", 1: "right elbow", 2: "right wrist", 3: "left shoulder", 4: "left elbow", 5: "left wrist", 6: "right hip", 7: "right knee", 8: "right ankle", 9: "left hip", 10: "left knee", 11: "left ankle", 12: "head tops", 13: "upper neck" }"
|
| 28 |
+
```
|
| 29 |
+
|
| 30 |
+
Compatible with Pulsar2 version: 5.2.
|
| 31 |
+
|
| 32 |
+
## Convert tools links:
|
| 33 |
+
|
| 34 |
+
For those who are interested in model conversion, you can try to export axmodel through:
|
| 35 |
+
- [The repo of AXera Platform](https://github.com/AXERA-TECH/ax-samples), where you can get the detailed guide.
|
| 36 |
+
- [Pulsar2 Link, How to Convert ONNX to axmodel](https://pulsar2-docs.readthedocs.io/en/latest/pulsar2/introduction.html)
|
| 37 |
+
|
| 38 |
+
## Support Platform
|
| 39 |
+
|
| 40 |
+
https://docs.m5stack.com/zh_CN/ai_hardware/AI_Pyramid-Pro
|
| 41 |
+
|
| 42 |
+
- **AX650N/AX8850**
|
| 43 |
+
- [M4N-Dock(爱芯派Pro)](https://wiki.sipeed.com/hardware/zh/maixIV/m4ndock/m4ndock.html)
|
| 44 |
+
- [AI Pyramid](https://docs.m5stack.com/zh_CN/ai_hardware/AI_Pyramid-Pro)
|
| 45 |
+
- [M.2 Accelerator card](https://docs.m5stack.com/en/ai_hardware/LLM-8850_Card)
|
| 46 |
+
|
| 47 |
+
## How to use
|
| 48 |
+
|
| 49 |
+
Download all files from this repository to the device.
|
| 50 |
+
|
| 51 |
+
### python env requirement
|
| 52 |
+
|
| 53 |
+
#### pyaxengine
|
| 54 |
+
|
| 55 |
+
https://github.com/AXERA-TECH/pyaxengine
|
| 56 |
+
|
| 57 |
+
```bash
|
| 58 |
+
wget https://github.com/AXERA-TECH/pyaxengine/releases/download/0.1.3.rc2/axengine-0.1.3-py3-none-any.whl
|
| 59 |
+
pip install axengine-0.1.3-py3-none-any.whl
|
| 60 |
+
```
|
| 61 |
+
|
| 62 |
+
### Inference with AX650 Host, such as M4N-Dock(爱芯派Pro)
|
| 63 |
+
|
| 64 |
+
Input image:
|
| 65 |
+

|
| 66 |
+
|
| 67 |
+
run
|
| 68 |
+
```
|
| 69 |
+
python3 axmodel_infer_fall.py
|
| 70 |
+
```
|
| 71 |
+
|
| 72 |
+
```
|
| 73 |
+
root@ax650:~/fall-axera# python3 axmodel_infer_fall.py
|
| 74 |
+
[INFO] Available providers: ['AxEngineExecutionProvider', 'AXCLRTExecutionProvider']
|
| 75 |
+
[INFO] Using provider: AxEngineExecutionProvider
|
| 76 |
+
[INFO] Chip type: ChipType.MC50
|
| 77 |
+
[INFO] VNPU type: VNPUType.DISABLED
|
| 78 |
+
[INFO] Engine version: 2.12.0s
|
| 79 |
+
[INFO] Model type: 2 (triple core)
|
| 80 |
+
[INFO] Compiler version: 5.2 eccb31f5
|
| 81 |
+
class: fall left:281 top:396 right:734 bottom:629 conf: 74%
|
| 82 |
+
Result saved to axmodel_res.jpg
|
| 83 |
+
|
| 84 |
+
```
|
| 85 |
+
|
| 86 |
+
Output image:
|
| 87 |
+

|
| 88 |
+
|
| 89 |
+
### Extra
|
| 90 |
+
This example only shows the model's predicted bounding boxes and keypoints. You can further assist in determining human falls based on the physical information of the boxes and keypoints, or by adding tracking and action recognition models like st-gcn. From the experiments I have conducted, factors such as occlusion, direction of falling, camera angle, and even the scene (such as on the bed or on the floor) can affect the results of fall detection.
|
| 91 |
+
|
axmodel_infer_fall.py
ADDED
|
@@ -0,0 +1,378 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import cv2
|
| 2 |
+
import numpy as np
|
| 3 |
+
import axengine as axe
|
| 4 |
+
import matplotlib
|
| 5 |
+
import argparse
|
| 6 |
+
|
| 7 |
+
class Colors:
|
| 8 |
+
|
| 9 |
+
def __init__(self):
|
| 10 |
+
self.palette = [self.hex2rgb(c) for c in matplotlib.colors.TABLEAU_COLORS.values()]
|
| 11 |
+
self.n = len(self.palette)
|
| 12 |
+
|
| 13 |
+
def __call__(self, i, bgr=False):
|
| 14 |
+
c = self.palette[int(i) % self.n]
|
| 15 |
+
return (c[2], c[1], c[0]) if bgr else c
|
| 16 |
+
|
| 17 |
+
@staticmethod
|
| 18 |
+
def hex2rgb(h):
|
| 19 |
+
return tuple(int(h[1 + i:1 + i + 2], 16) for i in (0, 2, 4))
|
| 20 |
+
|
| 21 |
+
colors = Colors()
|
| 22 |
+
|
| 23 |
+
def plot_one_box(x, im, color=None, label=None, line_thickness=3, kpt_label=False, kpts=None, steps=2, orig_shape=None):
|
| 24 |
+
|
| 25 |
+
assert im.data.contiguous, 'Image not contiguous. Apply np.ascontiguousarray(im) to plot_on_box() input image.'
|
| 26 |
+
tl = line_thickness or round(0.002 * (im.shape[0] + im.shape[1]) / 2) + 1
|
| 27 |
+
|
| 28 |
+
c1, c2 = (int(x[0]), int(x[1])), (int(x[2]), int(x[3]))
|
| 29 |
+
cv2.rectangle(im, c1, c2, color, thickness=tl*1//3, lineType=cv2.LINE_AA)
|
| 30 |
+
|
| 31 |
+
if label:
|
| 32 |
+
if len(label.split(' ')) > 1:
|
| 33 |
+
|
| 34 |
+
tf = max(tl - 1, 1)
|
| 35 |
+
t_size = cv2.getTextSize(label, 0, fontScale=tl / 6, thickness=tf)[0]
|
| 36 |
+
c2 = c1[0] + t_size[0], c1[1] - t_size[1] - 3
|
| 37 |
+
cv2.rectangle(im, c1, c2, color, -1, cv2.LINE_AA)
|
| 38 |
+
cv2.putText(im, label, (c1[0], c1[1] - 2), 0, tl / 6, [225, 255, 255], thickness=tf//2, lineType=cv2.LINE_AA)
|
| 39 |
+
if kpt_label:
|
| 40 |
+
plot_skeleton_kpts(im, kpts, steps, orig_shape=orig_shape)
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def plot_skeleton_kpts(im, kpts, steps, orig_shape=None):
|
| 45 |
+
|
| 46 |
+
palette = np.array([[255, 128, 0], [255, 153, 51], [255, 178, 102],
|
| 47 |
+
[230, 230, 0], [255, 153, 255], [153, 204, 255],
|
| 48 |
+
[255, 102, 255], [255, 51, 255], [102, 178, 255],
|
| 49 |
+
[51, 153, 255], [255, 153, 153], [255, 102, 102],
|
| 50 |
+
[255, 51, 51], [153, 255, 153], [102, 255, 102],
|
| 51 |
+
[51, 255, 51], [0, 255, 0], [0, 0, 255], [255, 0, 0],
|
| 52 |
+
[255, 255, 255]])
|
| 53 |
+
num_kpts = len(kpts) // steps
|
| 54 |
+
|
| 55 |
+
skeleton = [[1, 2], [2, 3], [14, 1], [14, 4], [4, 5], [5, 6], [13, 14], [7, 14], [10, 14], [7, 8], [8, 9],[10,11],[11, 12]]
|
| 56 |
+
|
| 57 |
+
pose_limb_color = palette[[9, 9, 9, 9, 7, 7, 7, 0, 0, 0, 0, 0, 16, 16, 16, 16, 16, 16, 16][:(num_kpts+2)]]
|
| 58 |
+
|
| 59 |
+
pose_kpt_color = palette[[16, 16, 16, 16, 16, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9][:num_kpts]]
|
| 60 |
+
radius = 5
|
| 61 |
+
for kid in range(num_kpts):
|
| 62 |
+
r, g, b = pose_kpt_color[kid]
|
| 63 |
+
|
| 64 |
+
x_coord, y_coord = kpts[steps * kid], kpts[steps * kid + 1]
|
| 65 |
+
if not (x_coord % 640 == 0 or y_coord % 640 == 0):
|
| 66 |
+
if steps == 3:
|
| 67 |
+
conf = kpts[steps * kid + 2]
|
| 68 |
+
|
| 69 |
+
cv2.circle(im, (int(x_coord), int(y_coord)), radius, (int(r), int(g), int(b)), -1)
|
| 70 |
+
cv2.putText(im,str(kid),(int(x_coord-2), int(y_coord-2)),cv2.FONT_HERSHEY_COMPLEX_SMALL, 1,(0,0,255),1)
|
| 71 |
+
|
| 72 |
+
for sk_id, sk in enumerate(skeleton):
|
| 73 |
+
r, g, b = pose_limb_color[sk_id]
|
| 74 |
+
|
| 75 |
+
pos1 = (int(kpts[(sk[0]-1)*steps]), int(kpts[(sk[0]-1)*steps+1]))
|
| 76 |
+
pos2 = (int(kpts[(sk[1]-1)*steps]), int(kpts[(sk[1]-1)*steps+1]))
|
| 77 |
+
if steps == 3:
|
| 78 |
+
conf1 = kpts[(sk[0]-1)*steps+2]
|
| 79 |
+
conf2 = kpts[(sk[1]-1)*steps+2]
|
| 80 |
+
|
| 81 |
+
if pos1[0]%640 == 0 or pos1[1]%640==0 or pos1[0]<0 or pos1[1]<0:
|
| 82 |
+
continue
|
| 83 |
+
if pos2[0] % 640 == 0 or pos2[1] % 640 == 0 or pos2[0]<0 or pos2[1]<0:
|
| 84 |
+
continue
|
| 85 |
+
cv2.line(im, pos1, pos2, (int(r), int(g), int(b)), thickness=2)
|
| 86 |
+
|
| 87 |
+
def box_iou(box1, box2, eps=1e-7):
|
| 88 |
+
(a1, a2), (b1, b2) = box1.unsqueeze(1).chunk(2, 2), box2.unsqueeze(0).chunk(2, 2)
|
| 89 |
+
inter = (np.min(a2, b2) - np.max(a1, b1)).clamp(0).prod(2)
|
| 90 |
+
return inter / ((a2 - a1).prod(2) + (b2 - b1).prod(2) - inter + eps)
|
| 91 |
+
|
| 92 |
+
def letterbox(im, new_shape=(640, 640), color=(114, 114, 114), auto=True, scaleFill=False, scaleup=True, stride=32):
|
| 93 |
+
|
| 94 |
+
shape = im.shape[:2]
|
| 95 |
+
if isinstance(new_shape, int):
|
| 96 |
+
new_shape = (new_shape, new_shape)
|
| 97 |
+
|
| 98 |
+
r = min(new_shape[0] / shape[0], new_shape[1] / shape[1])
|
| 99 |
+
if not scaleup:
|
| 100 |
+
r = min(r, 1.0)
|
| 101 |
+
|
| 102 |
+
ratio = r, r
|
| 103 |
+
new_unpad = int(round(shape[1] * r)), int(round(shape[0] * r))
|
| 104 |
+
dw, dh = new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1]
|
| 105 |
+
if auto:
|
| 106 |
+
dw, dh = np.mod(dw, stride), np.mod(dh, stride)
|
| 107 |
+
elif scaleFill:
|
| 108 |
+
dw, dh = 0.0, 0.0
|
| 109 |
+
new_unpad = (new_shape[1], new_shape[0])
|
| 110 |
+
ratio = new_shape[1] / shape[1], new_shape[0] / shape[0]
|
| 111 |
+
|
| 112 |
+
dw /= 2
|
| 113 |
+
dh /= 2
|
| 114 |
+
|
| 115 |
+
if shape[::-1] != new_unpad:
|
| 116 |
+
im = cv2.resize(im, new_unpad, interpolation=cv2.INTER_LINEAR)
|
| 117 |
+
top, bottom = int(round(dh - 0.1)), int(round(dh + 0.1))
|
| 118 |
+
left, right = int(round(dw - 0.1)), int(round(dw + 0.1))
|
| 119 |
+
im = cv2.copyMakeBorder(im, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color)
|
| 120 |
+
return im, ratio, (dw, dh)
|
| 121 |
+
|
| 122 |
+
def scale_coords(img1_shape, coords, img0_shape, ratio_pad=None, kpt_label=False, step=2):
|
| 123 |
+
|
| 124 |
+
if ratio_pad is None:
|
| 125 |
+
gain = min(img1_shape[0] / img0_shape[0], img1_shape[1] / img0_shape[1])
|
| 126 |
+
pad = (img1_shape[1] - img0_shape[1] * gain) / 2, (img1_shape[0] - img0_shape[0] * gain) / 2
|
| 127 |
+
else:
|
| 128 |
+
gain = ratio_pad[0]
|
| 129 |
+
pad = ratio_pad[1]
|
| 130 |
+
if isinstance(gain, (list, tuple)):
|
| 131 |
+
gain = gain[0]
|
| 132 |
+
if not kpt_label:
|
| 133 |
+
coords[:, [0, 2]] -= pad[0]
|
| 134 |
+
coords[:, [1, 3]] -= pad[1]
|
| 135 |
+
coords[:, [0, 2]] /= gain
|
| 136 |
+
coords[:, [1, 3]] /= gain
|
| 137 |
+
else:
|
| 138 |
+
coords[:, 0::step] -= pad[0]
|
| 139 |
+
coords[:, 1::step] -= pad[1]
|
| 140 |
+
coords[:, 0::step] /= gain
|
| 141 |
+
coords[:, 1::step] /= gain
|
| 142 |
+
|
| 143 |
+
return coords
|
| 144 |
+
|
| 145 |
+
|
| 146 |
+
def clip_coords(boxes, img_shape, step=2):
|
| 147 |
+
|
| 148 |
+
boxes[:, 0::step].clamp_(0, img_shape[1])
|
| 149 |
+
boxes[:, 1::step].clamp_(0, img_shape[0])
|
| 150 |
+
|
| 151 |
+
def model_inference(model_path=None, input=None):
|
| 152 |
+
session = axe.InferenceSession(model_path, None)
|
| 153 |
+
input_name = session.get_inputs()[0].name
|
| 154 |
+
output = session.run(None, {input_name: input})
|
| 155 |
+
return output
|
| 156 |
+
|
| 157 |
+
def xywh2xyxy(x):
|
| 158 |
+
|
| 159 |
+
y = np.copy(x)
|
| 160 |
+
y[..., 0] = x[..., 0] - x[..., 2] / 2
|
| 161 |
+
y[..., 1] = x[..., 1] - x[..., 3] / 2
|
| 162 |
+
y[..., 2] = x[..., 0] + x[..., 2] / 2
|
| 163 |
+
y[..., 3] = x[..., 1] + x[..., 3] / 2
|
| 164 |
+
return y
|
| 165 |
+
|
| 166 |
+
def nms_boxes(boxes, scores):
|
| 167 |
+
|
| 168 |
+
x = boxes[:, 0]
|
| 169 |
+
y = boxes[:, 1]
|
| 170 |
+
w = boxes[:, 2] - boxes[:, 0]
|
| 171 |
+
h = boxes[:, 3] - boxes[:, 1]
|
| 172 |
+
|
| 173 |
+
areas = w * h
|
| 174 |
+
order = scores.argsort()[::-1]
|
| 175 |
+
|
| 176 |
+
keep = []
|
| 177 |
+
while order.size > 0:
|
| 178 |
+
i = order[0]
|
| 179 |
+
keep.append(i)
|
| 180 |
+
|
| 181 |
+
xx1 = np.maximum(x[i], x[order[1:]])
|
| 182 |
+
yy1 = np.maximum(y[i], y[order[1:]])
|
| 183 |
+
xx2 = np.minimum(x[i] + w[i], x[order[1:]] + w[order[1:]])
|
| 184 |
+
yy2 = np.minimum(y[i] + h[i], y[order[1:]] + h[order[1:]])
|
| 185 |
+
|
| 186 |
+
w1 = np.maximum(0.0, xx2 - xx1 + 0.00001)
|
| 187 |
+
h1 = np.maximum(0.0, yy2 - yy1 + 0.00001)
|
| 188 |
+
inter = w1 * h1
|
| 189 |
+
|
| 190 |
+
ovr = inter / (areas[i] + areas[order[1:]] - inter)
|
| 191 |
+
inds = np.where(ovr <= 0.45)[0]
|
| 192 |
+
|
| 193 |
+
order = order[inds + 1]
|
| 194 |
+
keep = np.array(keep)
|
| 195 |
+
return keep
|
| 196 |
+
|
| 197 |
+
def non_max_suppression(prediction, conf_thres=0.25, iou_thres=0.45, classes=None, agnostic=False, multi_label=False,
|
| 198 |
+
labels=(), kpt_label=False, nc=None, nkpt=14):
|
| 199 |
+
|
| 200 |
+
if nc is None:
|
| 201 |
+
nc = prediction.shape[2] - 5 if not kpt_label else prediction.shape[2] - (5+3*nkpt)
|
| 202 |
+
xc = prediction[..., 4] > conf_thres
|
| 203 |
+
|
| 204 |
+
min_wh, max_wh = 2, 4096
|
| 205 |
+
max_det = 300
|
| 206 |
+
max_nms = 30000
|
| 207 |
+
redundant = True
|
| 208 |
+
multi_label &= nc > 1
|
| 209 |
+
merge = False
|
| 210 |
+
output = [np.zeros((0,6))] * prediction.shape[0]
|
| 211 |
+
|
| 212 |
+
for xi, x in enumerate(prediction):
|
| 213 |
+
x = x[xc[xi]]
|
| 214 |
+
if labels and len(labels[xi]):
|
| 215 |
+
l = labels[xi]
|
| 216 |
+
|
| 217 |
+
v = np.zeros(len(l), nc + 5)
|
| 218 |
+
v[:, :4] = l[:, 1:5]
|
| 219 |
+
v[:, 4] = 1.0
|
| 220 |
+
v[range(len(l)), l[:, 0].long() + 5] = 1.0
|
| 221 |
+
|
| 222 |
+
x = np.concatenate((x, v), 0)
|
| 223 |
+
if not x.shape[0]:
|
| 224 |
+
continue
|
| 225 |
+
|
| 226 |
+
x[:, 5:5+nc] *= x[:, 4:5]
|
| 227 |
+
box = xywh2xyxy(x[:, :4])
|
| 228 |
+
if multi_label:
|
| 229 |
+
if not kpt_label:
|
| 230 |
+
i, j = (x[:, 5:] > conf_thres).nonzero(as_tuple=False).T
|
| 231 |
+
x = np.concatenate((box[i], x[i, j + 5, None], j[:, None].float()), 1)
|
| 232 |
+
else:
|
| 233 |
+
kpts = x[:, 5+nc:]
|
| 234 |
+
i, j = (x[:, 5:5+nc] > conf_thres).nonzero(as_tuple=False).T
|
| 235 |
+
x = np.concatenate((box[i], x[i, j + 5, None], j[:, None].float(),kpts[i]), 1)
|
| 236 |
+
else:
|
| 237 |
+
if not kpt_label:
|
| 238 |
+
conf, j = x[:, 5:].max(1, keepdim=True)
|
| 239 |
+
x = np.concatenate((box, conf, j.float()), 1)[conf.view(-1) > conf_thres]
|
| 240 |
+
else:
|
| 241 |
+
kpts = x[:, 5+nc:]
|
| 242 |
+
conf = np.max(x[:, 5:5+nc], 1).reshape(box.shape[:1][0], 1)
|
| 243 |
+
j = np.argmax(x[:, 5:5+nc], 1).reshape(box.shape[:1][0], 1)
|
| 244 |
+
x = np.concatenate((box, conf, j, kpts), 1)[conf.reshape(box.shape[:1][0]) > conf_thres]
|
| 245 |
+
|
| 246 |
+
if classes is not None:
|
| 247 |
+
x = x[(x[:, 5:6] == np.array(classes, device=x.device)).any(1)]
|
| 248 |
+
|
| 249 |
+
n = x.shape[0]
|
| 250 |
+
|
| 251 |
+
if not n:
|
| 252 |
+
continue
|
| 253 |
+
elif n > max_nms:
|
| 254 |
+
x = x[x[:, 4].argsort(descending=True)[:max_nms]]
|
| 255 |
+
|
| 256 |
+
c = x[:, 5:6] * (0 if agnostic else max_wh)
|
| 257 |
+
|
| 258 |
+
boxes, scores = x[:, :4] + c, x[:, 4]
|
| 259 |
+
|
| 260 |
+
i = nms_boxes(boxes, scores)
|
| 261 |
+
if i.shape[0] > max_det:
|
| 262 |
+
i = i[:max_det]
|
| 263 |
+
if merge and (1 < n < 3E3):
|
| 264 |
+
|
| 265 |
+
iou = box_iou(boxes[i], boxes) > iou_thres
|
| 266 |
+
weights = iou * scores[None]
|
| 267 |
+
x[i, :4] = np.multiply(weights, x[:, :4]).float() / weights.sum(1, keepdim=True)
|
| 268 |
+
|
| 269 |
+
if redundant:
|
| 270 |
+
i = i[iou.sum(1) > 1]
|
| 271 |
+
|
| 272 |
+
output[xi] = x[i]
|
| 273 |
+
|
| 274 |
+
return output
|
| 275 |
+
|
| 276 |
+
def _make_grid(nx=20, ny=20):
|
| 277 |
+
y, x = np.arange(ny, dtype=np.float32), np.arange(nx, dtype=np.float32)
|
| 278 |
+
|
| 279 |
+
yv, xv = np.meshgrid(y, x, indexing='ij')
|
| 280 |
+
return np.stack((xv, yv), 2).reshape((1, 1, ny, nx, 2))
|
| 281 |
+
|
| 282 |
+
def sigmoid(x):
|
| 283 |
+
return 1 / (1 + np.exp(-x))
|
| 284 |
+
|
| 285 |
+
def preprocess(img_path, imgsz):
|
| 286 |
+
"""预处理:读取图像并进行归一化"""
|
| 287 |
+
im0 = cv2.imread(img_path)
|
| 288 |
+
img = letterbox(im0, imgsz, auto=False, stride=32)[0]
|
| 289 |
+
img = np.ascontiguousarray(img[:, :, ::-1].transpose(2, 0, 1))
|
| 290 |
+
img = np.asarray(img, dtype=np.uint8)
|
| 291 |
+
img = np.expand_dims(img, 0)
|
| 292 |
+
return img, im0
|
| 293 |
+
|
| 294 |
+
def model_postprocess(preds, anchors, stride, names, nkpt, conf_thres, iou_thres):
|
| 295 |
+
"""后处理:解码预测结果、NMS和坐标变换"""
|
| 296 |
+
na = len(anchors[0]) // 2
|
| 297 |
+
nl = len(anchors)
|
| 298 |
+
nc = len(names)
|
| 299 |
+
no = len(names) + 5 + nkpt * 3
|
| 300 |
+
|
| 301 |
+
z = []
|
| 302 |
+
for i, pred in enumerate(preds):
|
| 303 |
+
bs, _, ny, nx = pred.shape
|
| 304 |
+
pred = pred.reshape(bs, na, no, ny, nx).transpose(0, 1, 3, 4, 2)
|
| 305 |
+
pred_det = pred[..., :5+nc]
|
| 306 |
+
pred_kpt = pred[..., 5+nc:]
|
| 307 |
+
grid = _make_grid(nx, ny)
|
| 308 |
+
kpt_grid_x = grid[..., 0:1]
|
| 309 |
+
kpt_grid_y = grid[..., 1:2]
|
| 310 |
+
|
| 311 |
+
y = sigmoid(pred_det)
|
| 312 |
+
|
| 313 |
+
xy = (y[..., 0:2] * 2. - 0.5 + grid) * stride[i]
|
| 314 |
+
wh = (y[..., 2:4] * 2) ** 2 * np.array(anchors[i]).reshape(1, 3, 1, 1, 2)
|
| 315 |
+
|
| 316 |
+
pred_kpt[..., 0::3] = (pred_kpt[..., ::3] * 2. - 0.5 + np.tile(kpt_grid_x, (1,1,1,1,nkpt))) * stride[i]
|
| 317 |
+
pred_kpt[..., 1::3] = (pred_kpt[..., 1::3] * 2. - 0.5 + np.tile(kpt_grid_y, (1,1,1,1,nkpt))) * stride[i]
|
| 318 |
+
pred_kpt[..., 2::3] = sigmoid(pred_kpt[..., 2::3])
|
| 319 |
+
y = np.concatenate((xy, wh, y[..., 4:], pred_kpt), axis=-1)
|
| 320 |
+
|
| 321 |
+
z.append(y.reshape(bs, na * nx * ny, no))
|
| 322 |
+
|
| 323 |
+
preds = np.concatenate(z, 1)
|
| 324 |
+
preds = non_max_suppression(preds, conf_thres, iou_thres, nc=nc, nkpt=nkpt, kpt_label=True)
|
| 325 |
+
|
| 326 |
+
return preds
|
| 327 |
+
|
| 328 |
+
def draw_predictions(preds, img, im0, names, imgsz):
|
| 329 |
+
"""绘制检测结果和关键点"""
|
| 330 |
+
for i, det in enumerate(preds):
|
| 331 |
+
if len(det):
|
| 332 |
+
scale_coords(imgsz, det[:, :4], im0.shape, kpt_label=False)
|
| 333 |
+
scale_coords(imgsz, det[:, 6:], im0.shape, kpt_label=True, step=3)
|
| 334 |
+
|
| 335 |
+
for det_index, (*xyxy, conf, cls) in enumerate(reversed(det[:, :6])):
|
| 336 |
+
print("class:",names[int(cls)], "left:%.0f" % xyxy[0],"top:%.0f" % xyxy[1],"right:%.0f" % xyxy[2],"bottom:%.0f" % xyxy[3], "conf:",'{:.0f}%'.format(float(conf)*100))
|
| 337 |
+
c = int(cls)
|
| 338 |
+
label = f'{names[c]} {conf:.2f}'
|
| 339 |
+
kpts = det[det_index, 6:]
|
| 340 |
+
plot_one_box(xyxy, im0, label=label, color=colors(c, True), line_thickness=2,
|
| 341 |
+
kpt_label=True, kpts=kpts, steps=3, orig_shape=im0.shape[:2])
|
| 342 |
+
|
| 343 |
+
return im0
|
| 344 |
+
|
| 345 |
+
if __name__ == "__main__":
|
| 346 |
+
parser = argparse.ArgumentParser(description='跌倒检测模型推理脚本')
|
| 347 |
+
parser.add_argument('--model', type=str, default='./fall_ax650_npu3.axmodel',
|
| 348 |
+
help='axmodel 模型路径')
|
| 349 |
+
parser.add_argument('--img', type=str, default='./fall4.png',
|
| 350 |
+
help='输入图像路径')
|
| 351 |
+
parser.add_argument('--output', type=str, default='axmodel_res.jpg',
|
| 352 |
+
help='输出结果图像路径')
|
| 353 |
+
parser.add_argument('--imgsz', type=int, nargs=2, default=[320, 480],
|
| 354 |
+
help='输入图像尺寸 (height width)')
|
| 355 |
+
parser.add_argument('--conf-thres', type=float, default=0.3,
|
| 356 |
+
help='置信度阈值')
|
| 357 |
+
parser.add_argument('--iou-thres', type=float, default=0.45,
|
| 358 |
+
help='IOU阈值')
|
| 359 |
+
|
| 360 |
+
args = parser.parse_args()
|
| 361 |
+
|
| 362 |
+
# model params
|
| 363 |
+
names = ['normal', 'fall']
|
| 364 |
+
anchors = [[30, 61, 55, 124, 90, 207], [149, 232, 128, 357, 221, 308]]
|
| 365 |
+
stride = [16, 32]
|
| 366 |
+
nkpt = 14
|
| 367 |
+
imgsz = tuple(args.imgsz)
|
| 368 |
+
|
| 369 |
+
img, im0 = preprocess(args.img, imgsz)
|
| 370 |
+
|
| 371 |
+
preds = model_inference(args.model, img)
|
| 372 |
+
|
| 373 |
+
preds = model_postprocess(preds, anchors, stride, names, nkpt, args.conf_thres, args.iou_thres)
|
| 374 |
+
|
| 375 |
+
im0 = draw_predictions(preds, img, im0, names, imgsz)
|
| 376 |
+
|
| 377 |
+
cv2.imwrite(args.output, im0)
|
| 378 |
+
print(f"Result saved to {args.output}")
|
axmodel_res.jpg
ADDED
|
Git LFS Details
|
config.json
ADDED
|
File without changes
|
fall4.png
ADDED
|
Git LFS Details
|