feat: 边缘侧服务代码初始化与配置同步准备
This commit is contained in:
149
bench_npu.py
Normal file
149
bench_npu.py
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
昇腾 310B4 NPU 推理性能基准测试
|
||||||
|
测试项目:
|
||||||
|
1. 确认 NPU 推理(非 CPU fallback)
|
||||||
|
2. 单次推理延迟
|
||||||
|
3. 连续推理吞吐 (FPS)
|
||||||
|
4. 多 worker 并发
|
||||||
|
5. 不同分辨率影响
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import socket
|
||||||
|
import struct
|
||||||
|
import json
|
||||||
|
import base64
|
||||||
|
import threading
|
||||||
|
import numpy as np
|
||||||
|
import cv2
|
||||||
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
|
|
||||||
|
SOCK_PATH = "/tmp/edge-infer.sock"
|
||||||
|
|
||||||
|
def make_test_image(w=640, h=640):
|
||||||
|
"""生成随机测试图片"""
|
||||||
|
img = np.random.randint(0, 255, (h, w, 3), dtype=np.uint8)
|
||||||
|
_, buf = cv2.imencode('.jpg', img, [cv2.IMWRITE_JPEG_QUALITY, 90])
|
||||||
|
return buf.tobytes()
|
||||||
|
|
||||||
|
def infer_one(jpeg_bytes):
|
||||||
|
"""发送一次推理请求,返回响应时间(ms)"""
|
||||||
|
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||||
|
sock.connect(SOCK_PATH)
|
||||||
|
|
||||||
|
msg = {
|
||||||
|
"stream_id": 0,
|
||||||
|
"device_id": "bench",
|
||||||
|
"url": "bench://test",
|
||||||
|
"ts": 1234567890.0,
|
||||||
|
"jpeg_b64": base64.b64encode(jpeg_bytes).decode("utf-8")
|
||||||
|
}
|
||||||
|
|
||||||
|
data = json.dumps(msg).encode("utf-8")
|
||||||
|
|
||||||
|
t0 = time.time()
|
||||||
|
sock.sendall(struct.pack(">I", len(data)) + data)
|
||||||
|
hdr = sock.recv(4)
|
||||||
|
length = struct.unpack(">I", hdr)[0]
|
||||||
|
result = json.loads(sock.recv(length).decode("utf-8"))
|
||||||
|
elapsed = (time.time() - t0) * 1000 # ms
|
||||||
|
|
||||||
|
sock.close()
|
||||||
|
return elapsed, result
|
||||||
|
|
||||||
|
def test_single_infer():
|
||||||
|
"""单次推理延迟"""
|
||||||
|
print("\n=== 单次推理延迟测试 ===")
|
||||||
|
jpeg = make_test_image()
|
||||||
|
times = []
|
||||||
|
for i in range(5):
|
||||||
|
t, _ = infer_one(jpeg)
|
||||||
|
times.append(t)
|
||||||
|
print(f" 第 {i+1} 次: {t:.1f} ms")
|
||||||
|
|
||||||
|
# 跳过第一次预热
|
||||||
|
times = times[1:]
|
||||||
|
avg = sum(times) / len(times)
|
||||||
|
print(f"\n 平均延迟 (排除预热): {avg:.1f} ms")
|
||||||
|
print(f" 理论 FPS: {1000/avg:.1f}")
|
||||||
|
return avg
|
||||||
|
|
||||||
|
def test_throughput():
|
||||||
|
"""连续推理吞吐"""
|
||||||
|
print("\n=== 连续推理吞吐测试 ===")
|
||||||
|
jpeg = make_test_image()
|
||||||
|
count = 30
|
||||||
|
t0 = time.time()
|
||||||
|
|
||||||
|
for i in range(count):
|
||||||
|
_, _ = infer_one(jpeg)
|
||||||
|
if (i+1) % 10 == 0:
|
||||||
|
elapsed = time.time() - t0
|
||||||
|
print(f" {i+1}/{count} 完成, 累计: {elapsed:.1f}s, FPS: {(i+1)/elapsed:.1f}")
|
||||||
|
|
||||||
|
total = time.time() - t0
|
||||||
|
fps = count / total
|
||||||
|
print(f"\n 总计 {count} 帧: {total:.2f}s")
|
||||||
|
print(f" 吞吐: {fps:.1f} FPS")
|
||||||
|
print(f" 每帧延迟: {total/count*1000:.1f} ms")
|
||||||
|
return fps
|
||||||
|
|
||||||
|
def test_concurrent(workers=4):
|
||||||
|
"""多 worker 并发推理"""
|
||||||
|
print(f"\n=== {workers} 路并发测试 ===")
|
||||||
|
jpeg = make_test_image()
|
||||||
|
results_per_worker = []
|
||||||
|
|
||||||
|
def worker_task(worker_id, n_frames=20):
|
||||||
|
times = []
|
||||||
|
for _ in range(n_frames):
|
||||||
|
t, _ = infer_one(jpeg)
|
||||||
|
times.append(t)
|
||||||
|
return worker_id, times
|
||||||
|
|
||||||
|
t0 = time.time()
|
||||||
|
with ThreadPoolExecutor(max_workers=workers) as pool:
|
||||||
|
futures = [pool.submit(worker_task, i, 15) for i in range(workers)]
|
||||||
|
for f in futures:
|
||||||
|
wid, times = f.result()
|
||||||
|
results_per_worker.append((wid, sum(times)/len(times), max(times), min(times)))
|
||||||
|
|
||||||
|
total = time.time() - t0
|
||||||
|
total_frames = workers * 15
|
||||||
|
total_fps = total_frames / total
|
||||||
|
|
||||||
|
for wid, avg, mx, mn in results_per_worker:
|
||||||
|
print(f" Worker {wid}: avg={avg:.1f}ms, max={mx:.1f}ms, min={mn:.1f}ms")
|
||||||
|
|
||||||
|
print(f"\n 并发 {workers} 路: 总计 {total_frames} 帧, {total:.2f}s")
|
||||||
|
print(f" 总吞吐: {total_fps:.1f} FPS")
|
||||||
|
print(f" 单路等效 FPS: {total_fps/workers:.1f}")
|
||||||
|
return total_fps
|
||||||
|
|
||||||
|
def test_power():
|
||||||
|
"""读取 NPU 功耗信息"""
|
||||||
|
import subprocess
|
||||||
|
try:
|
||||||
|
result = subprocess.run(['npu-smi', 'info'], capture_output=True, text=True, timeout=5)
|
||||||
|
print("\n=== NPU 状态 ===")
|
||||||
|
print(result.stdout)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"无法读取 NPU 状态: {e}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print("=" * 60)
|
||||||
|
print(" 昇腾 310B4 + ACL 原生推理 性能基准测试")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
test_power()
|
||||||
|
test_single_infer()
|
||||||
|
test_throughput()
|
||||||
|
test_concurrent(2)
|
||||||
|
test_concurrent(4)
|
||||||
|
test_concurrent(6)
|
||||||
|
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("测试完成")
|
||||||
|
print("=" * 60)
|
||||||
13
config/edge.yaml
Normal file
13
config/edge.yaml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
edge_id: edge-demo-001
|
||||||
|
cloud_url: http://101.36.73.102:8004
|
||||||
|
edge_token: ""
|
||||||
|
|
||||||
|
# RTSP 流地址(留空使用演示模式)
|
||||||
|
rtsp_urls: []
|
||||||
|
|
||||||
|
infer_socket: /tmp/edge-infer.sock
|
||||||
|
infer_fps: 5
|
||||||
|
infer_workers: 3
|
||||||
|
conf_threshold: 0.5
|
||||||
|
dedup_window_sec: 30
|
||||||
|
version: 1.0.0
|
||||||
119
debug_model.py
Normal file
119
debug_model.py
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""调试: 检查模型原始输出"""
|
||||||
|
import numpy as np
|
||||||
|
import cv2
|
||||||
|
import acl
|
||||||
|
|
||||||
|
ACL_MEMCPY_HOST_TO_DEVICE = 1
|
||||||
|
ACL_MEMCPY_DEVICE_TO_HOST = 2
|
||||||
|
|
||||||
|
img = cv2.imread("/home/强光车灯误报.png")
|
||||||
|
print("Image shape:", img.shape)
|
||||||
|
|
||||||
|
acl.init()
|
||||||
|
acl.rt.set_device(0)
|
||||||
|
ctx, _ = acl.rt.create_context(0)
|
||||||
|
|
||||||
|
model_id, _ = acl.mdl.load_from_file("/root/AI-tianyan/model/model.om")
|
||||||
|
desc = acl.mdl.create_desc()
|
||||||
|
acl.mdl.get_desc(desc, model_id)
|
||||||
|
|
||||||
|
in_sz = acl.mdl.get_input_size_by_index(desc, 0)
|
||||||
|
print("Input size:", in_sz, "bytes")
|
||||||
|
|
||||||
|
# Preprocess
|
||||||
|
h0, w0 = img.shape[:2]
|
||||||
|
inp_h, inp_w = 640, 640
|
||||||
|
scale = min(inp_h / h0, inp_w / w0)
|
||||||
|
nh, nw = int(h0 * scale), int(w0 * scale)
|
||||||
|
resized = cv2.resize(img, (nw, nh), interpolation=cv2.INTER_LINEAR)
|
||||||
|
canvas = np.full((inp_h, inp_w, 3), 114, dtype=np.uint8)
|
||||||
|
pad_top = (inp_h - nh) // 2
|
||||||
|
pad_left = (inp_w - nw) // 2
|
||||||
|
canvas[pad_top:pad_top + nh, pad_left:pad_left + nw] = resized
|
||||||
|
rgb = cv2.cvtColor(canvas, cv2.COLOR_BGR2RGB)
|
||||||
|
blob = rgb.astype(np.float32) / 255.0
|
||||||
|
blob = np.ascontiguousarray(blob.transpose(2, 0, 1)[np.newaxis])
|
||||||
|
print("Blob shape:", blob.shape, "dtype:", blob.dtype)
|
||||||
|
|
||||||
|
# Allocate device input
|
||||||
|
in_ds = acl.mdl.create_dataset()
|
||||||
|
host_buf = np.ascontiguousarray(blob)
|
||||||
|
host_addr = acl.util.bytes_to_ptr(host_buf.tobytes())
|
||||||
|
in_buf, _ = acl.rt.malloc(in_sz, 0)
|
||||||
|
acl.rt.memcpy(in_buf, in_sz, host_addr, in_sz, ACL_MEMCPY_HOST_TO_DEVICE)
|
||||||
|
acl.mdl.add_dataset_buffer(in_ds, acl.create_data_buffer(in_buf, in_sz))
|
||||||
|
|
||||||
|
# Allocate output
|
||||||
|
out_num = acl.mdl.get_num_outputs(desc)
|
||||||
|
out_sizes = [acl.mdl.get_output_size_by_index(desc, i) for i in range(out_num)]
|
||||||
|
out_ds = acl.mdl.create_dataset()
|
||||||
|
out_bufs = []
|
||||||
|
for i in range(out_num):
|
||||||
|
buf, _ = acl.rt.malloc(out_sizes[i], 0)
|
||||||
|
out_bufs.append(buf)
|
||||||
|
acl.mdl.add_dataset_buffer(out_ds, acl.create_data_buffer(buf, out_sizes[i]))
|
||||||
|
print(f"Output[{i}] size: {out_sizes[i]} bytes")
|
||||||
|
|
||||||
|
# Execute
|
||||||
|
ret = acl.mdl.execute(model_id, in_ds, out_ds)
|
||||||
|
print("Execute ret:", ret)
|
||||||
|
|
||||||
|
# Get output
|
||||||
|
for i in range(out_num):
|
||||||
|
sz = out_sizes[i]
|
||||||
|
host = np.zeros(sz, dtype=np.uint8)
|
||||||
|
host_addr = acl.util.bytes_to_ptr(host.tobytes())
|
||||||
|
acl.rt.memcpy(host_addr, sz, out_bufs[i], sz, ACL_MEMCPY_DEVICE_TO_HOST)
|
||||||
|
|
||||||
|
dims_out, _ = acl.mdl.get_output_dims(desc, i)
|
||||||
|
d = dims_out['dims']
|
||||||
|
print(f"\nOutput[{i}] dims: {d}")
|
||||||
|
print(f" Size: {sz} bytes")
|
||||||
|
|
||||||
|
# Try FP16
|
||||||
|
fp16 = host.view(np.float16)
|
||||||
|
print(f" FP16 shape: {fp16.shape}")
|
||||||
|
|
||||||
|
reshaped = fp16.astype(np.float32).reshape(d)
|
||||||
|
print(f" Reshaped: {reshaped.shape}")
|
||||||
|
print(f" Min: {reshaped.min():.4f}, Max: {reshaped.max():.4f}, Mean: {reshaped.mean():.4f}")
|
||||||
|
|
||||||
|
# Check if it's all zeros or NaN
|
||||||
|
nan_count = np.isnan(reshaped).sum()
|
||||||
|
zero_count = (reshaped == 0).sum()
|
||||||
|
print(f" NaN count: {nan_count}, Zero count: {zero_count}/{reshaped.size}")
|
||||||
|
|
||||||
|
# For YOLOv8 output [1, 84, 8400], check class scores
|
||||||
|
# boxes: reshaped[:, :4, :]
|
||||||
|
# scores: reshaped[:, 4:, :]
|
||||||
|
scores = reshaped[0, 4:, :]
|
||||||
|
max_scores = scores.max(axis=0) # max class score per anchor
|
||||||
|
print(f"\n Max class scores per anchor:")
|
||||||
|
print(f" Min: {max_scores.min():.4f}, Max: {max_scores.max():.4f}, Mean: {max_scores.mean():.4f}")
|
||||||
|
|
||||||
|
# Count anchors with score > 0.1
|
||||||
|
high_conf = (max_scores > 0.1).sum()
|
||||||
|
print(f" Anchors with conf > 0.1: {high_conf}")
|
||||||
|
print(f" Anchors with conf > 0.01: {(max_scores > 0.01).sum()}")
|
||||||
|
|
||||||
|
# Print top 5
|
||||||
|
top5_idx = np.argsort(max_scores)[-5:][::-1]
|
||||||
|
for idx in top5_idx:
|
||||||
|
best_cls = scores[:, idx].argmax()
|
||||||
|
print(f" Anchor {idx}: class={best_cls} (score={max_scores[idx]:.4f})")
|
||||||
|
box = reshaped[0, :4, idx]
|
||||||
|
print(f" box: {box}")
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
for buf in out_bufs:
|
||||||
|
acl.rt.free(buf)
|
||||||
|
acl.rt.free(in_buf)
|
||||||
|
acl.mdl.destroy_dataset(in_ds)
|
||||||
|
acl.mdl.destroy_dataset(out_ds)
|
||||||
|
acl.mdl.unload(model_id)
|
||||||
|
acl.mdl.destroy_desc(desc)
|
||||||
|
acl.rt.destroy_context(ctx)
|
||||||
|
acl.rt.reset_device(0)
|
||||||
|
acl.finalize()
|
||||||
|
print("\nDone")
|
||||||
76
detect_one.py
Normal file
76
detect_one.py
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""检测单张图片 - 通过 Unix socket 发送到推理服务"""
|
||||||
|
import sys
|
||||||
|
import socket
|
||||||
|
import struct
|
||||||
|
import json
|
||||||
|
import base64
|
||||||
|
import cv2
|
||||||
|
|
||||||
|
SOCK_PATH = "/tmp/edge-infer.sock"
|
||||||
|
|
||||||
|
def detect_image(image_path):
|
||||||
|
img = cv2.imread(image_path)
|
||||||
|
if img is None:
|
||||||
|
print("无法读取图片:", image_path)
|
||||||
|
return
|
||||||
|
|
||||||
|
# 编码为 JPEG
|
||||||
|
_, buf = cv2.imencode('.jpg', img, [cv2.IMWRITE_JPEG_QUALITY, 90])
|
||||||
|
jpeg_bytes = buf.tobytes()
|
||||||
|
|
||||||
|
# 连接推理服务
|
||||||
|
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||||
|
sock.connect(SOCK_PATH)
|
||||||
|
|
||||||
|
# 构造请求
|
||||||
|
msg = {
|
||||||
|
"stream_id": 0,
|
||||||
|
"device_id": "cli-test",
|
||||||
|
"url": "file://" + image_path,
|
||||||
|
"ts": 1234567890.0,
|
||||||
|
"jpeg_b64": base64.b64encode(jpeg_bytes).decode("utf-8")
|
||||||
|
}
|
||||||
|
|
||||||
|
# 发送
|
||||||
|
data = json.dumps(msg).encode("utf-8")
|
||||||
|
sock.sendall(struct.pack(">I", len(data)) + data)
|
||||||
|
|
||||||
|
# 接收结果
|
||||||
|
hdr = sock.recv(4)
|
||||||
|
if not hdr:
|
||||||
|
print("未收到响应")
|
||||||
|
sock.close()
|
||||||
|
return
|
||||||
|
|
||||||
|
length = struct.unpack(">I", hdr)[0]
|
||||||
|
result = json.loads(sock.recv(length).decode("utf-8"))
|
||||||
|
sock.close()
|
||||||
|
|
||||||
|
# 输出结果
|
||||||
|
dets = result.get("detections", [])
|
||||||
|
print(f"\n图片: {image_path}")
|
||||||
|
print(f"尺寸: {img.shape[1]}x{img.shape[0]}")
|
||||||
|
print(f"检测到 {len(dets)} 个目标\n")
|
||||||
|
print(f"{'类别':<20} {'置信度':<10} {'边界框'}")
|
||||||
|
print("-" * 60)
|
||||||
|
|
||||||
|
for d in dets:
|
||||||
|
bbox = d["bbox"]
|
||||||
|
print(f'{d["class"]:<20} {d["conf"]:<10.3f} [{bbox[0]:.0f}, {bbox[1]:.0f}, {bbox[2]:.0f}, {bbox[3]:.0f}]')
|
||||||
|
|
||||||
|
# 保存带标注的图片
|
||||||
|
if dets:
|
||||||
|
for d in dets:
|
||||||
|
x1, y1, x2, y2 = [int(x) for x in d["bbox"]]
|
||||||
|
cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2)
|
||||||
|
label = f'{d["class"]} {d["conf"]:.2f}'
|
||||||
|
cv2.putText(img, label, (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
|
||||||
|
|
||||||
|
out_path = image_path.rsplit(".", 1)[0] + "_result.jpg"
|
||||||
|
cv2.imwrite(out_path, img)
|
||||||
|
print(f"\n已保存标注图片: {out_path}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
path = sys.argv[1] if len(sys.argv) > 1 else "/home/强光车灯误报.png"
|
||||||
|
detect_image(path)
|
||||||
10
go.sum
Normal file
10
go.sum
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||||
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
BIN
model/model.om
Normal file
BIN
model/model.om
Normal file
Binary file not shown.
80
model/names.txt
Normal file
80
model/names.txt
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
person
|
||||||
|
bicycle
|
||||||
|
car
|
||||||
|
motorcycle
|
||||||
|
airplane
|
||||||
|
bus
|
||||||
|
train
|
||||||
|
truck
|
||||||
|
boat
|
||||||
|
traffic light
|
||||||
|
fire hydrant
|
||||||
|
stop sign
|
||||||
|
parking meter
|
||||||
|
bench
|
||||||
|
bird
|
||||||
|
cat
|
||||||
|
dog
|
||||||
|
horse
|
||||||
|
sheep
|
||||||
|
cow
|
||||||
|
elephant
|
||||||
|
bear
|
||||||
|
zebra
|
||||||
|
giraffe
|
||||||
|
backpack
|
||||||
|
umbrella
|
||||||
|
handbag
|
||||||
|
tie
|
||||||
|
suitcase
|
||||||
|
frisbee
|
||||||
|
skis
|
||||||
|
snowboard
|
||||||
|
sports ball
|
||||||
|
kite
|
||||||
|
baseball bat
|
||||||
|
baseball glove
|
||||||
|
skateboard
|
||||||
|
surfboard
|
||||||
|
tennis racket
|
||||||
|
bottle
|
||||||
|
wine glass
|
||||||
|
cup
|
||||||
|
fork
|
||||||
|
knife
|
||||||
|
spoon
|
||||||
|
bowl
|
||||||
|
banana
|
||||||
|
apple
|
||||||
|
sandwich
|
||||||
|
orange
|
||||||
|
broccoli
|
||||||
|
carrot
|
||||||
|
hot dog
|
||||||
|
pizza
|
||||||
|
donut
|
||||||
|
cake
|
||||||
|
chair
|
||||||
|
couch
|
||||||
|
potted plant
|
||||||
|
bed
|
||||||
|
dining table
|
||||||
|
toilet
|
||||||
|
tv
|
||||||
|
laptop
|
||||||
|
mouse
|
||||||
|
remote
|
||||||
|
keyboard
|
||||||
|
cell phone
|
||||||
|
microwave
|
||||||
|
oven
|
||||||
|
toaster
|
||||||
|
sink
|
||||||
|
refrigerator
|
||||||
|
book
|
||||||
|
clock
|
||||||
|
vase
|
||||||
|
scissors
|
||||||
|
teddy bear
|
||||||
|
hair drier
|
||||||
|
toothbrush
|
||||||
79
test_infer.py
Normal file
79
test_infer.py
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""测试推理服务 - 发送一张测试图片"""
|
||||||
|
import socket
|
||||||
|
import struct
|
||||||
|
import json
|
||||||
|
import base64
|
||||||
|
import numpy as np
|
||||||
|
import cv2
|
||||||
|
import os
|
||||||
|
|
||||||
|
SOCK_PATH = "/tmp/edge-infer.sock"
|
||||||
|
|
||||||
|
def create_test_image():
|
||||||
|
"""创建一张 640x480 的测试图片,画几个几何图形"""
|
||||||
|
img = np.zeros((480, 640, 3), dtype=np.uint8)
|
||||||
|
# 画一些简单的形状模拟检测目标
|
||||||
|
cv2.rectangle(img, (50, 50), (200, 200), (255, 255, 255), -1)
|
||||||
|
cv2.circle(img, (400, 300), 80, (255, 255, 255), -1)
|
||||||
|
# 编码为 JPEG
|
||||||
|
_, buf = cv2.imencode('.jpg', img)
|
||||||
|
return buf.tobytes()
|
||||||
|
|
||||||
|
def send_msg(conn, payload):
|
||||||
|
data = json.dumps(payload).encode("utf-8")
|
||||||
|
conn.sendall(struct.pack(">I", len(data)) + data)
|
||||||
|
|
||||||
|
def recv_msg(conn):
|
||||||
|
hdr = conn.recv(4)
|
||||||
|
if not hdr:
|
||||||
|
return None
|
||||||
|
length = struct.unpack(">I", hdr)[0]
|
||||||
|
data = b""
|
||||||
|
while len(data) < length:
|
||||||
|
chunk = conn.recv(length - len(data))
|
||||||
|
if not chunk:
|
||||||
|
return None
|
||||||
|
data += chunk
|
||||||
|
return json.loads(data.decode("utf-8"))
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if not os.path.exists(SOCK_PATH):
|
||||||
|
print(f"错误: socket {SOCK_PATH} 不存在")
|
||||||
|
return
|
||||||
|
|
||||||
|
print("连接推理服务...")
|
||||||
|
conn = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||||
|
conn.connect(SOCK_PATH)
|
||||||
|
|
||||||
|
# 创建测试图片
|
||||||
|
jpeg_bytes = create_test_image()
|
||||||
|
msg = {
|
||||||
|
"stream_id": 0,
|
||||||
|
"device_id": "test-001",
|
||||||
|
"url": "test://local",
|
||||||
|
"ts": 1234567890.0,
|
||||||
|
"jpeg_b64": base64.b64encode(jpeg_bytes).decode("utf-8")
|
||||||
|
}
|
||||||
|
|
||||||
|
print("发送测试图片...")
|
||||||
|
send_msg(conn, msg)
|
||||||
|
|
||||||
|
print("等待推理结果...")
|
||||||
|
result = recv_msg(conn)
|
||||||
|
|
||||||
|
if result:
|
||||||
|
print(f"\n推理结果:")
|
||||||
|
print(f" stream_id: {result['stream_id']}")
|
||||||
|
print(f" device_id: {result['device_id']}")
|
||||||
|
print(f" detections: {len(result['detections'])} 个目标")
|
||||||
|
for d in result['detections']:
|
||||||
|
print(f" - {d['class']}: {d['conf']:.3f} bbox={d['bbox']}")
|
||||||
|
else:
|
||||||
|
print("未收到结果")
|
||||||
|
|
||||||
|
conn.close()
|
||||||
|
print("\n测试完成!")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
BIN
yolov8n.om
Normal file
BIN
yolov8n.om
Normal file
Binary file not shown.
Reference in New Issue
Block a user