新增卡点相关代码
This commit is contained in:
275
npu_yolo_pose_onnx.py
Normal file
275
npu_yolo_pose_onnx.py
Normal file
@@ -0,0 +1,275 @@
|
||||
# npu_yolo_pose_onnx.py
|
||||
# 修复要点:
|
||||
# 1. 正确处理 YOLOv8 Pose anchor 输出(避免 40+ 人)
|
||||
# 2. 关键点坐标正确逆 letterbox(减 padding 再除 ratio)
|
||||
# 3. visibility 使用 sigmoid
|
||||
# 4. NMS 后限制最大人数,保证工程稳定性
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
import onnxruntime as ort
|
||||
|
||||
|
||||
|
||||
|
||||
# -------------------------------------------------
|
||||
# Letterbox
|
||||
# -------------------------------------------------
|
||||
def letterbox(img, new_shape=(1280, 1280), color=(114, 114, 114)):
|
||||
shape = img.shape[:2] # h, w
|
||||
r = min(new_shape[0] / shape[0], new_shape[1] / shape[1])
|
||||
|
||||
new_unpad = (int(round(shape[1] * r)), int(round(shape[0] * r)))
|
||||
dw = new_shape[1] - new_unpad[0]
|
||||
dh = new_shape[0] - new_unpad[1]
|
||||
dw /= 2
|
||||
dh /= 2
|
||||
|
||||
if shape[::-1] != new_unpad:
|
||||
img = cv2.resize(img, new_unpad, interpolation=cv2.INTER_LINEAR)
|
||||
|
||||
top, bottom = int(round(dh - 0.1)), int(round(dh + 0.1))
|
||||
left, right = int(round(dw - 0.1)), int(round(dw + 0.1))
|
||||
img = cv2.copyMakeBorder(
|
||||
img, top, bottom, left, right,
|
||||
cv2.BORDER_CONSTANT, value=color
|
||||
)
|
||||
return img, r, (dw, dh)
|
||||
|
||||
|
||||
# -------------------------------------------------
|
||||
# Pose Skeleton Definition (COCO-17)
|
||||
# -------------------------------------------------
|
||||
POSE_SKELETON = [
|
||||
(16,14),(14,12),(17,15),(15,13),(12,13),
|
||||
(6,12),(7,13),(6,7),
|
||||
(6,8),(7,9),(8,10),(9,11),
|
||||
(2,3),(1,2),(1,3),
|
||||
(2,4),(3,5),(4,6),(5,7)
|
||||
]
|
||||
|
||||
POSE_SKELETON = [(a-1, b-1) for (a, b) in POSE_SKELETON]
|
||||
|
||||
POSE_COLORS = [
|
||||
(255,0,0),(255,85,0),(255,170,0),(255,255,0),
|
||||
(170,255,0),(85,255,0),(0,255,0),
|
||||
(0,255,85),(0,255,170),(0,255,255),
|
||||
(0,170,255),(0,85,255),(0,0,255),
|
||||
(85,0,255),(170,0,255),(255,0,255),(255,0,170)
|
||||
]
|
||||
|
||||
|
||||
# -------------------------------------------------
|
||||
# YOLOv8 Pose ONNX
|
||||
# -------------------------------------------------
|
||||
class YOLOv8_Pose_ONNX:
|
||||
def __init__(
|
||||
self,
|
||||
onnx_path,
|
||||
conf_threshold=0.6, # ★ 提高阈值,避免 anchor 噪声
|
||||
iou_threshold=0.45,
|
||||
input_size=1280,
|
||||
max_persons=5 # ★ 限制最大人数
|
||||
):
|
||||
providers = [
|
||||
("CANNExecutionProvider", {
|
||||
"device_id": 0,
|
||||
"arena_extend_strategy": "kNextPowerOfTwo",
|
||||
"npu_mem_limit": 16 * 1024 * 1024 * 1024,
|
||||
"precision_mode": "allow_fp32_to_fp16",
|
||||
"op_select_impl_mode": "high_precision",
|
||||
"enable_cann_graph": True,
|
||||
}),
|
||||
"CUDAExecutionProvider",
|
||||
"CPUExecutionProvider",
|
||||
]
|
||||
|
||||
self.session = ort.InferenceSession(onnx_path, providers=providers)
|
||||
|
||||
# 获取真实工作 provider
|
||||
actual_providers = self.session.get_providers()
|
||||
|
||||
print("YOLO Providers:", actual_providers)
|
||||
|
||||
if "CANNExecutionProvider" in actual_providers:
|
||||
print("[INFO] YOLO 使用 CANNExecutionProvider(昇腾)")
|
||||
elif 'CUDAExecutionProvider' in actual_providers:
|
||||
print("[INFO] YOLO 使用 CUDAExecutionProvider(NVIDIA GPU)")
|
||||
else:
|
||||
print("[INFO] YOLO 使用 CPUExecutionProvider(非昇腾环境)")
|
||||
|
||||
self.conf_threshold = conf_threshold
|
||||
self.iou_threshold = iou_threshold
|
||||
self.max_persons = max_persons
|
||||
|
||||
self.input_name = self.session.get_inputs()[0].name
|
||||
self.input_size = (input_size, input_size)
|
||||
print(f"模型输入名称: {self.input_name}")
|
||||
print(f"模型输入形状: {self.session.get_inputs()[0].shape}")
|
||||
print(f"模型输出形状: {self.session.get_outputs()[0].shape}")
|
||||
|
||||
|
||||
def nms(self, boxes, scores, iou_threshold=0.45):
|
||||
"""
|
||||
boxes: [N,4] xyxy
|
||||
scores: [N]
|
||||
"""
|
||||
x1 = boxes[:, 0]
|
||||
y1 = boxes[:, 1]
|
||||
x2 = boxes[:, 2]
|
||||
y2 = boxes[:, 3]
|
||||
|
||||
areas = (x2 - x1) * (y2 - y1)
|
||||
order = scores.argsort()[::-1]
|
||||
|
||||
keep = []
|
||||
while order.size > 0:
|
||||
i = order[0]
|
||||
keep.append(i)
|
||||
xx1 = np.maximum(x1[i], x1[order[1:]])
|
||||
yy1 = np.maximum(y1[i], y1[order[1:]])
|
||||
xx2 = np.minimum(x2[i], x2[order[1:]])
|
||||
yy2 = np.minimum(y2[i], y2[order[1:]])
|
||||
|
||||
w = np.maximum(0.0, xx2 - xx1)
|
||||
h = np.maximum(0.0, yy2 - yy1)
|
||||
inter = w * h
|
||||
ovr = inter / (areas[i] + areas[order[1:]] - inter)
|
||||
|
||||
inds = np.where(ovr <= iou_threshold)[0]
|
||||
order = order[inds + 1]
|
||||
|
||||
# 限制最大人数
|
||||
if len(keep) >= self.max_persons:
|
||||
break
|
||||
|
||||
return np.array(keep, dtype=int)
|
||||
# -------------------------------------------------
|
||||
def preprocess(self, img):
|
||||
self.orig_shape = img.shape[:2]
|
||||
img, self.ratio, (self.dw, self.dh) = letterbox(img, self.input_size)
|
||||
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
|
||||
img = img.transpose(2, 0, 1).astype(np.float32) / 255.0
|
||||
img = np.expand_dims(img, axis=0)
|
||||
return img
|
||||
|
||||
def postprocess(self, preds, im0_shape):
|
||||
"""
|
||||
preds: onnx output, shape = [1, 56, 33600]
|
||||
im0_shape: (h, w) of original frame
|
||||
"""
|
||||
|
||||
preds = preds[0] # [56, 33600]
|
||||
preds = preds.transpose(1, 0) # [33600, 56]
|
||||
|
||||
# =============================
|
||||
# 1. 拆分输出
|
||||
# =============================
|
||||
boxes = preds[:, 0:4] # cx, cy, w, h (input scale)
|
||||
scores = preds[:, 4] # obj conf
|
||||
kpts_raw = preds[:, 5:] # [33600, 51] = 17*3
|
||||
|
||||
# =============================
|
||||
# 2. 置信度筛选
|
||||
# =============================
|
||||
mask = scores > self.conf_threshold
|
||||
boxes = boxes[mask]
|
||||
scores = scores[mask]
|
||||
kpts_raw = kpts_raw[mask]
|
||||
|
||||
if boxes.shape[0] == 0:
|
||||
return []
|
||||
|
||||
# =============================
|
||||
# 3. bbox cxcywh -> xyxy(input scale)
|
||||
# =============================
|
||||
boxes_xyxy = np.zeros_like(boxes)
|
||||
boxes_xyxy[:, 0] = boxes[:, 0] - boxes[:, 2] / 2 # x1
|
||||
boxes_xyxy[:, 1] = boxes[:, 1] - boxes[:, 3] / 2 # y1
|
||||
boxes_xyxy[:, 2] = boxes[:, 0] + boxes[:, 2] / 2 # x2
|
||||
boxes_xyxy[:, 3] = boxes[:, 1] + boxes[:, 3] / 2 # y2
|
||||
|
||||
# =============================
|
||||
# 4. inverse letterbox(bbox)
|
||||
# =============================
|
||||
boxes_xyxy[:, [0, 2]] = (boxes_xyxy[:, [0, 2]] - self.dw) / self.ratio
|
||||
boxes_xyxy[:, [1, 3]] = (boxes_xyxy[:, [1, 3]] - self.dh) / self.ratio
|
||||
|
||||
boxes_xyxy[:, 0] = np.clip(boxes_xyxy[:, 0], 0, im0_shape[1])
|
||||
boxes_xyxy[:, 1] = np.clip(boxes_xyxy[:, 1], 0, im0_shape[0])
|
||||
boxes_xyxy[:, 2] = np.clip(boxes_xyxy[:, 2], 0, im0_shape[1])
|
||||
boxes_xyxy[:, 3] = np.clip(boxes_xyxy[:, 3], 0, im0_shape[0])
|
||||
|
||||
# =============================
|
||||
# 5. NMS
|
||||
# =============================
|
||||
keep = self.nms(boxes_xyxy, scores, self.iou_threshold)
|
||||
boxes_xyxy = boxes_xyxy[keep]
|
||||
scores = scores[keep]
|
||||
kpts_raw = kpts_raw[keep]
|
||||
|
||||
# =============================
|
||||
# 6. 逐人处理 keypoints(关键)
|
||||
# =============================
|
||||
results = []
|
||||
|
||||
for i in range(len(boxes_xyxy)):
|
||||
x1, y1, x2, y2 = boxes_xyxy[i]
|
||||
|
||||
# (51,) -> (17,3)
|
||||
kpts = kpts_raw[i].reshape(17, 3).copy()
|
||||
|
||||
|
||||
kpts[:, 0] = (kpts[:, 0] - self.dw) / self.ratio
|
||||
kpts[:, 1] = (kpts[:, 1] - self.dh) / self.ratio
|
||||
|
||||
# ✅ 加回 bbox offset(核心修复点)
|
||||
#kpts[:, 0] += x1
|
||||
#kpts[:, 1] += y1
|
||||
|
||||
# clip
|
||||
kpts[:, 0] = np.clip(kpts[:, 0], 0, im0_shape[1])
|
||||
kpts[:, 1] = np.clip(kpts[:, 1], 0, im0_shape[0])
|
||||
|
||||
# visibility sigmoid(防溢出)
|
||||
kpts[:, 2] = 1.0 / (1.0 + np.exp(-np.clip(kpts[:, 2], -50, 50)))
|
||||
|
||||
results.append({
|
||||
"bbox": [float(x1), float(y1), float(x2), float(y2)],
|
||||
"conf": float(scores[i]),
|
||||
"kpts": kpts
|
||||
})
|
||||
|
||||
return results
|
||||
|
||||
# -------------------------------------------------
|
||||
def __call__(self, frame):
|
||||
inp = self.preprocess(frame)
|
||||
pred = self.session.run(None, {self.input_name: inp})[0]
|
||||
return self.postprocess(pred, frame.shape[:2])
|
||||
|
||||
@staticmethod
|
||||
def draw_keypoints(frame, pose_results, vis_thres=0.3):
|
||||
for res in pose_results:
|
||||
kpts = res.get("kpts", None) # 注意这里对应 postprocess 返回的 key
|
||||
if kpts is None or len(kpts) != 17:
|
||||
continue
|
||||
# 如果是 ndarray,转换为 list
|
||||
if isinstance(kpts, np.ndarray):
|
||||
kpts = kpts.tolist()
|
||||
|
||||
for i, (x, y, v) in enumerate(kpts):
|
||||
if v > vis_thres:
|
||||
cv2.circle(frame, (int(x), int(y)), 5, POSE_COLORS[i], -1)
|
||||
|
||||
for a, b in POSE_SKELETON:
|
||||
if kpts[a][2] > vis_thres and kpts[b][2] > vis_thres:
|
||||
cv2.line(
|
||||
frame,
|
||||
(int(kpts[a][0]), int(kpts[a][1])),
|
||||
(int(kpts[b][0]), int(kpts[b][1])),
|
||||
POSE_COLORS[a],
|
||||
2
|
||||
)
|
||||
return frame
|
||||
|
||||
1072
rtsp_service_ws_kadian.py
Normal file
1072
rtsp_service_ws_kadian.py
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user