欢迎光临
我们一直在努力

Orin 上目标检测最小闭环(含代码)

文章目录

    • 1. 最小闭环指什么
    • 2. 开始前确认
    • 3. 输入怎么选
    • 4. 导出 ONNX(完整代码)
    • 5. 本板转到 TensorRT(完整代码)
    • 6. 跑通推理(完整代码)
    • 7. 计时怎么记
    • 8. 再接相机(完整代码)
    • 9. 一键对照清单
    • 10. 常见问题
    • 11. 做到哪算闭环完成
    • 12. 和 Orin / Thor 选型的关系
    • 13. 小结

摘要:环境通了之后,下一步通常是把检测跑起来。本文按最小路径写:导出 YOLOv8n ONNX → 本板 trtexec 建引擎 → Python + TensorRT 出框,并给出可直接保存运行的完整脚本。适合 Orin Nano / NX / AGX,已经做过第一天环境确认的人。不绑具体第三方板卡;命令和包名随 JetPack 会变,以你板上版本为准。

建议先建工作目录,把下文四个代码块分别存成文件:

mkdir -p ~/orin-detect && cd ~/orin-detect
# 保存为:export_onnx.py / build_engine.sh / detect_trt.py / detect_camera.py
chmod +x build_engine.sh

文件作用
export_onnx.py 导出固定 640 的 YOLOv8n ONNX
build_engine.sh 本板 FP16 建引擎
detect_trt.py 图片/视频推理 + 拆段计时
detect_camera.py USB 相机实时预览(依赖同目录 detect_trt.py)

1. 最小闭环指什么

这里说的闭环,不是完整产品,只是证明这条链通:

输入 → 预处理 → 推理 → 后处理 → 你能看到框或分数

第一天环境 Checklist 过了,再做这一步。GPU 路径还没确认,就上 YOLO 和大工程,排障会混在一起。

建议范围先收窄:

先做先别做
本地视频或图片 一上来多路 RTSP
单个检测模型 检测 + 跟踪 + 重识别一起上
TensorRT 引擎在本板构建 从别的架构拷 .engine
记清功耗档和输入尺寸 和网上 FPS 硬比

跑通之后,再加相机、DeepStream、多模型。


2. 开始前确认

和第一天类似,写进备忘录:

  • 板型:Nano / NX / AGX,内存多大
  • JetPack / L4T 版本
  • nvpmodel -q 当前档
  • 系统盘空间还够不够导引擎

cat /etc/nv_tegra_release
sudo nvpmodel -q
df -h
tegrastats

依赖(跑本文脚本至少要这些):

# OpenCV / NumPy 多数镜像已有;没有就按 JetPack 文档装
python3 -c "import cv2, numpy; print(cv2.__version__)"

# TensorRT Python:一般随 JetPack
python3 -c "import tensorrt as trt; print(trt.__version__)"

# 主机侧内存拷贝常用 pycuda
pip3 install pycuda

# 仅导出 ONNX 时需要(可在 PC 上做完再拷到板)
pip3 install ultralytics

引擎文件和测试视频放到数据盘,别把系统盘写满。满盘时导出引擎失败,看起来很像模型或 TensorRT 坏了。


3. 输入怎么选

输入适合注意
本地图片 / 视频文件 第一次跑通、对齐 FPS 路径固定,可复现
USB 相机 看实时效果 驱动、曝光、分辨率先单独确认
CSI 相机 量产常见 和载板、设备树相关,问题更多
RTSP 接近现场 网络抖动会搅进测速

我建议顺序:文件 → USB → CSI/RTSP。文件阶段把预处理、引擎、后处理弄稳,再引入相机变量。

测速度时,用同一段 10~30 秒视频反复跑,比随便拿摄像头对着桌面有意义。手头没有视频时,任意一张含人或车的 jpg 也能先验证出框。


4. 导出 ONNX(完整代码)

本文示例用 Ultralytics YOLOv8n,固定 640×640、静态 shape,减少第一次踩动态维度的坑。可在 PC 或 Orin 上跑;ONNX 可以跨机器拷,但后面的 .engine 必须在目标 Orin 上建。

保存为 export_onnx.py:

#!/usr/bin/env python3
"""在 Orin 或 PC 上导出 YOLOv8n ONNX,供本板 trtexec 建引擎。

依赖:
pip3 install ultralytics
首次会下载 yolov8n.pt(需联网)。
"""

from ultralytics import YOLO

def main():
model = YOLO("yolov8n.pt")
path = model.export(
format="onnx",
imgsz=640,
simplify=True,
opset=12,
dynamic=False,
)
print(f"ONNX 已导出: {path}")
print("下一步在 Orin 上执行: bash build_engine.sh")

if __name__ == "__main__":
main()

运行:

python3 export_onnx.py
# 得到 yolov8n.onnx;若在 PC 导出,用 scp 拷到 Orin 的 ~/orin-detect

注意:

  • 在 Orin 上构建的 TensorRT 引擎,不要拿到别的 GPU 架构去跑,反之亦然
  • 输入尺寸、NCHW、是否 /255 归一化,必须和训练/导出一致;下文推理脚本按 Ultralytics 默认处理
  • 第一遍用公开小模型即可;业务权重若输出布局不是 YOLOv8 这种 (1, 84, 8400),要改 postprocess

5. 本板转到 TensorRT(完整代码)

保存为 build_engine.sh:

#!/usr/bin/env bash
# 在目标 Orin 本机执行,把 ONNX 建成 TensorRT 引擎。
set -euo pipefail

ONNX="${1:-yolov8n.onnx}"
ENGINE="${2:-yolov8n_fp16.engine}"
TRTEXEC="${TRTEXEC:-/usr/src/tensorrt/bin/trtexec}"

if [[ ! -x "$TRTEXEC" ]]; then
echo "找不到 trtexec: $TRTEXEC"
echo "用 which trtexec 或 find /usr -name trtexec 2>/dev/null 定位后:"
echo " TRTEXEC=/path/to/trtexec bash build_engine.sh"
exit 1
fi

if [[ ! -f "$ONNX" ]]; then
echo "找不到 ONNX: $ONNX"
echo "先跑: python3 export_onnx.py"
exit 1
fi

echo "板型/版本参考:"
cat /etc/nv_tegra_release 2>/dev/null || true
sudo nvpmodel -q 2>/dev/null || nvpmodel -q 2>/dev/null || true
df -h .

echo "开始构建: $ONNX -> $ENGINE"
"$TRTEXEC" \\
–onnx="$ONNX" \\
–saveEngine="$ENGINE" \\
–fp16 \\
–workspace=2048

echo "完成: $ENGINE"
ls -lh "$ENGINE"

执行:

chmod +x build_engine.sh
# 长任务建议放 tmux 里,避免 SSH 断开
bash build_engine.sh yolov8n.onnx yolov8n_fp16.engine

找不到 trtexec 时:

find /usr -name trtexec 2>/dev/null
TRTEXEC=/实际路径/trtexec bash build_engine.sh

INT8 还要校准数据,第一遍闭环先 FP16 就够。笔记本 4090 上导出来的引擎,不能假设 Orin 能直接加载。

导出时建议记下:

记录项例子
ONNX 文件名与来源 yolov8n.onnx
输入尺寸 640×640
精度 FP16
板型与 JetPack AGX Orin + 某版本
功耗档 导出时用的 nvpmodel
耗时 大概几分钟,心里有数

动态 batch、动态分辨率能省事,也更容易第一次就配错。最小闭环用固定 shape 更省心,跑通再开动态。


6. 跑通推理(完整代码)

核心步骤:读图 → letterbox → NCHW /255 → TensorRT → 解析 YOLOv8 输出 → NMS → 画框。脚本兼容 TensorRT 8 / 10 两套 API。

保存为 detect_trt.py(全文,直接复制即可):

#!/usr/bin/env python3
"""Orin 上 TensorRT 目标检测最小闭环(适配 Ultralytics YOLOv8 固定 640 ONNX)。

依赖(板上常见已有 OpenCV / NumPy;另外需要):
sudo apt-get install -y python3-pip
pip3 install pycuda
# tensorrt Python 包一般随 JetPack 自带;没有就按对应 JetPack 文档装

用法:
python3 detect_trt.py –engine yolov8n_fp16.engine –source bus.jpg
python3 detect_trt.py –engine yolov8n_fp16.engine –source demo.mp4 –save out.mp4
python3 detect_trt.py –engine yolov8n_fp16.engine –source demo.mp4 –benchmark
"""

from __future__ import annotations

import argparse
import time
from pathlib import Path

import cv2
import numpy as np
import pycuda.autoinit # noqa: F401 # 初始化 CUDA 上下文
import pycuda.driver as cuda
import tensorrt as trt

# COCO 80 类(YOLOv8 预训练默认)
COCO80 = [
"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",
]

class TrtYolo:
"""同时兼容 TensorRT 8(binding API)和 TensorRT 10(tensor API)。"""

def __init__(self, engine_path: str):
logger = trt.Logger(trt.Logger.WARNING)
with open(engine_path, "rb") as f, trt.Runtime(logger) as runtime:
self.engine = runtime.deserialize_cuda_engine(f.read())
self.context = self.engine.create_execution_context()
self.inputs = []
self.outputs = []
self.bindings = []
self.stream = cuda.Stream()
self.use_tensor_api = hasattr(self.engine, "num_io_tensors")

if self.use_tensor_api:
self._init_trt10()
else:
self._init_trt8()

self.in_h, self.in_w = self.inputs[0]["shape"][2], self.inputs[0]["shape"][3]
print(f"engine input shape: {self.inputs[0]['shape']} api={'trt10' if self.use_tensor_api else 'trt8'}")

def _alloc(self, shape, dtype):
size = int(np.prod(shape))
host = cuda.pagelocked_empty(size, dtype)
device = cuda.mem_alloc(host.nbytes)
return host, device

def _init_trt8(self):
for i in range(self.engine.num_bindings):
name = self.engine.get_binding_name(i)
dtype = trt.nptype(self.engine.get_binding_dtype(i))
shape = tuple(self.engine.get_binding_shape(i))
host, device = self._alloc(shape, dtype)
self.bindings.append(int(device))
item = {"name": name, "host": host, "device": device, "shape": shape}
if self.engine.binding_is_input(i):
self.inputs.append(item)
else:
self.outputs.append(item)

def _init_trt10(self):
for i in range(self.engine.num_io_tensors):
name = self.engine.get_tensor_name(i)
dtype = trt.nptype(self.engine.get_tensor_dtype(name))
shape = tuple(self.engine.get_tensor_shape(name))
host, device = self._alloc(shape, dtype)
self.context.set_tensor_address(name, int(device))
item = {"name": name, "host": host, "device": device, "shape": shape}
if self.engine.get_tensor_mode(name) == trt.TensorIOMode.INPUT:
self.inputs.append(item)
else:
self.outputs.append(item)

def infer(self, nchw: np.ndarray) > np.ndarray:
np.copyto(self.inputs[0]["host"], nchw.ravel())
cuda.memcpy_htod_async(self.inputs[0]["device"], self.inputs[0]["host"], self.stream)
if self.use_tensor_api:
self.context.execute_async_v3(stream_handle=self.stream.handle)
else:
self.context.execute_async_v2(
bindings=self.bindings, stream_handle=self.stream.handle
)
for out in self.outputs:
cuda.memcpy_dtoh_async(out["host"], out["device"], self.stream)
self.stream.synchronize()
# YOLOv8 ONNX 常见输出: (1, 84, 8400)
return self.outputs[0]["host"].reshape(self.outputs[0]["shape"])

def letterbox(im, new_shape=(640, 640), color=(114, 114, 114)):
"""保持比例缩放并填充,返回图、比例、padding。"""
h, w = im.shape[:2]
if isinstance(new_shape, int):
new_shape = (new_shape, new_shape)
r = min(new_shape[0] / h, new_shape[1] / w)
nh, nw = int(round(h * r)), int(round(w * r))
resized = cv2.resize(im, (nw, nh), interpolation=cv2.INTER_LINEAR)
top = (new_shape[0] nh) // 2
bottom = new_shape[0] nh top
left = (new_shape[1] nw) // 2
right = new_shape[1] nw left
out = cv2.copyMakeBorder(
resized, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color
)
return out, r, (left, top)

def preprocess(bgr, input_hw):
img, ratio, (dw, dh) = letterbox(bgr, input_hw)
rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB).astype(np.float32) / 255.0
nchw = np.transpose(rgb, (2, 0, 1))[None, ...]
return np.ascontiguousarray(nchw), ratio, dw, dh

def postprocess(output, ratio, dw, dh, conf_thres=0.25, iou_thres=0.45, orig_shape=None):
"""解析 YOLOv8 输出 (1, 4+nc, n) -> boxes xyxy、scores、cls。"""
pred = np.squeeze(output)
if pred.ndim != 2:
raise ValueError(f"意外输出形状: {output.shape}")
# (84, 8400) -> (8400, 84)
if pred.shape[0] < pred.shape[1]:
pred = pred.T

boxes_xywh = pred[:, :4]
cls_scores = pred[:, 4:]
cls_ids = np.argmax(cls_scores, axis=1)
scores = cls_scores[np.arange(len(cls_ids)), cls_ids]

mask = scores >= conf_thres
boxes_xywh = boxes_xywh[mask]
scores = scores[mask]
cls_ids = cls_ids[mask]
if len(scores) == 0:
return [], [], []

# xywh(center) -> xyxy,并去掉 letterbox padding,再除以 ratio
x, y, w, h = boxes_xywh.T
x1 = (x w / 2 dw) / ratio
y1 = (y h / 2 dh) / ratio
x2 = (x + w / 2 dw) / ratio
y2 = (y + h / 2 dh) / ratio
boxes = np.stack([x1, y1, x2, y2], axis=1)

if orig_shape is not None:
oh, ow = orig_shape
boxes[:, [0, 2]] = boxes[:, [0, 2]].clip(0, ow)
boxes[:, [1, 3]] = boxes[:, [1, 3]].clip(0, oh)

idx = cv2.dnn.NMSBoxes(
boxes.tolist(), scores.tolist(), conf_thres, iou_thres
)
if len(idx) == 0:
return [], [], []
idx = np.array(idx).reshape(1)
return boxes[idx], scores[idx], cls_ids[idx]

def draw(bgr, boxes, scores, cls_ids):
out = bgr.copy()
for box, score, cid in zip(boxes, scores, cls_ids):
x1, y1, x2, y2 = map(int, box)
name = COCO80[int(cid)] if int(cid) < len(COCO80) else str(int(cid))
label = f"{name} {score:.2f}"
cv2.rectangle(out, (x1, y1), (x2, y2), (0, 180, 0), 2)
cv2.putText(
out, label, (x1, max(0, y1 5)),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 180, 0), 1, cv2.LINE_AA,
)
return out

def open_source(source: str):
p = Path(source)
if p.suffix.lower() in {".jpg", ".jpeg", ".png", ".bmp", ".webp"}:
img = cv2.imread(str(p))
if img is None:
raise FileNotFoundError(f"读图失败: {source}")
return "image", img
cap = cv2.VideoCapture(source if source.isdigit() else str(p))
if not cap.isOpened():
raise RuntimeError(f"打开失败: {source}")
return "video", cap

def main():
ap = argparse.ArgumentParser(description="Orin TensorRT YOLOv8 最小检测闭环")
ap.add_argument("–engine", required=True, help="本板构建的 .engine")
ap.add_argument("–source", required=True, help="图片/视频路径,或相机索引如 0")
ap.add_argument("–save", default="result.jpg", help="输出图或视频路径")
ap.add_argument("–conf", type=float, default=0.25)
ap.add_argument("–iou", type=float, default=0.45)
ap.add_argument("–benchmark", action="store_true", help="拆段计时,不写盘")
ap.add_argument("–warmup", type=int, default=10)
ap.add_argument("–max-frames", type=int, default=0, help="0 表示不限制")
args = ap.parse_args()

detector = TrtYolo(args.engine)
input_hw = (detector.in_h, detector.in_w)
kind, src = open_source(args.source)

if kind == "image":
t0 = time.perf_counter()
nchw, ratio, dw, dh = preprocess(src, input_hw)
t1 = time.perf_counter()
out = detector.infer(nchw)
t2 = time.perf_counter()
boxes, scores, cls_ids = postprocess(
out, ratio, dw, dh, args.conf, args.iou, src.shape[:2]
)
t3 = time.perf_counter()
vis = draw(src, boxes, scores, cls_ids)
t4 = time.perf_counter()
if not args.benchmark:
cv2.imwrite(args.save, vis)
print(f"检出 {len(boxes)} 个目标,已保存 {args.save}")
print(
f"预处理 { (t1t0)*1000:.1f} ms | "
f"推理 {(t2t1)*1000:.1f} ms | "
f"后处理 {(t3t2)*1000:.1f} ms | "
f"画框 {(t4t3)*1000:.1f} ms"
)
return

# 视频 / 相机
cap = src
writer = None
times = {"read": [], "pre": [], "infer": [], "post": []}
frame_i = 0
saved = 0

while True:
t_r0 = time.perf_counter()
ok, frame = cap.read()
t_r1 = time.perf_counter()
if not ok:
break
frame_i += 1

t_p0 = time.perf_counter()
nchw, ratio, dw, dh = preprocess(frame, input_hw)
t_p1 = time.perf_counter()
out = detector.infer(nchw)
t_i1 = time.perf_counter()
boxes, scores, cls_ids = postprocess(
out, ratio, dw, dh, args.conf, args.iou, frame.shape[:2]
)
t_post = time.perf_counter()

if frame_i > args.warmup:
times["read"].append(t_r1 t_r0)
times["pre"].append(t_p1 t_p0)
times["infer"].append(t_i1 t_p1)
times["post"].append(t_post t_i1)

if not args.benchmark:
vis = draw(frame, boxes, scores, cls_ids)
if writer is None and args.save.lower().endswith((".mp4", ".avi")):
h, w = vis.shape[:2]
fourcc = cv2.VideoWriter_fourcc(*"mp4v")
fps = cap.get(cv2.CAP_PROP_FPS) or 25
writer = cv2.VideoWriter(args.save, fourcc, fps, (w, h))
if writer is not None:
writer.write(vis)
elif args.save:
# 默认只存首帧带框图,避免视频路径写成 jpg 时刷一堆文件
if saved == 0 and not args.save.lower().endswith((".mp4", ".avi")):
cv2.imwrite(args.save, vis)
saved = 1

if args.max_frames and frame_i >= args.max_frames:
break

cap.release()
if writer is not None:
writer.release()
print(f"视频已保存: {args.save}")
elif saved:
print(f"结果图已保存: {args.save}")

def avg_ms(xs):
return (sum(xs) / len(xs) * 1000) if xs else 0.0

n = len(times["infer"])
if n == 0:
print("有效统计帧为 0,试试减小 –warmup 或加长视频")
return
r, p, i, po = map(avg_ms, (times["read"], times["pre"], times["infer"], times["post"]))
e2e = r + p + i + po
print(f"统计帧数={n}(已跳过 warmup={args.warmup})")
print(
f"读帧 {r:.1f} ms | 预处理 {p:.1f} ms | "
f"推理 {i:.1f} ms | 后处理 {po:.1f} ms | "
f"合计 {e2e:.1f} ms -> ~{1000/e2e:.1f} FPS(端到端不含画框写盘)"
)
print(f"纯推理约 {1000/i:.1f} FPS(仅 infer 段)")

if __name__ == "__main__":
main()

用法:

# 任选一张测试图
python3 detect_trt.py –engine yolov8n_fp16.engine –source bus.jpg –save result.jpg

# 视频并落盘
python3 detect_trt.py –engine yolov8n_fp16.engine –source demo.mp4 –save out.mp4

# 只测速,不画框写盘
python3 detect_trt.py –engine yolov8n_fp16.engine –source demo.mp4 –benchmark –warmup 10

成功标准:

  • result.jpg 上有合理框,不是满屏乱框
  • tegrastats 里 GPU 有占用
  • –benchmark 能打出预处理 / 推理 / 后处理分段毫秒

出框很歪时,先查 letterbox / 坐标还原,再怀疑引擎精度。闭环阶段阈值先用默认 0.25,别一边调阈值一边怀疑引擎。


7. 计时怎么记

detect_trt.py 加 –benchmark 会跳过画框写盘,并在 warmup 之后统计:

段落含义
读帧 VideoCapture.read
预处理 letterbox + 转 tensor
推理 TensorRT execute
后处理 解码 + NMS

示例输出类似:

统计帧数=200(已跳过 warmup=10)
读帧 2.1 ms | 预处理 3.4 ms | 推理 8.7 ms | 后处理 1.5 ms | 合计 15.7 ms -> ~63.7 FPS(端到端不含画框写盘)
纯推理约 114.9 FPS(仅 infer 段)

对外对齐时写清:

  • 是否含读帧和画框
  • 输入分辨率、batch
  • nvpmodel 档
  • 预热了多少帧

只报峰值、不报条件,后面一定对不上。同一配置连跑三次,看方差;第一次含冷启动。


8. 再接相机(完整代码)

文件链路通了,再接 USB:

ls /dev/video*
v4l2-ctl –list-devices
# 用户需在 video 组;加完重新登录
groups | grep video

保存为 detect_camera.py(需与 detect_trt.py 放在同一目录):

#!/usr/bin/env python3
"""USB 相机实时检测(复用 detect_trt.py 同套预处理/引擎)。

用法:
python3 detect_camera.py –engine yolov8n_fp16.engine –device 0
按 q 退出。
"""

from __future__ import annotations

import argparse
import time

import cv2

from detect_trt import TrtYolo, draw, postprocess, preprocess

def main():
ap = argparse.ArgumentParser()
ap.add_argument("–engine", required=True)
ap.add_argument("–device", default="0", help="V4L2 索引,常见 0")
ap.add_argument("–conf", type=float, default=0.25)
ap.add_argument("–iou", type=float, default=0.45)
ap.add_argument("–width", type=int, default=640)
ap.add_argument("–height", type=int, default=480)
args = ap.parse_args()

detector = TrtYolo(args.engine)
input_hw = (detector.in_h, detector.in_w)

cap = cv2.VideoCapture(int(args.device) if args.device.isdigit() else args.device)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, args.width)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, args.height)
if not cap.isOpened():
raise RuntimeError(f"打不开相机: {args.device},先检查 ls /dev/video* 与 video 组权限")

print("按 q 退出窗口")
while True:
ok, frame = cap.read()
if not ok:
print("读帧失败")
break
t0 = time.perf_counter()
nchw, ratio, dw, dh = preprocess(frame, input_hw)
out = detector.infer(nchw)
boxes, scores, cls_ids = postprocess(
out, ratio, dw, dh, args.conf, args.iou, frame.shape[:2]
)
vis = draw(frame, boxes, scores, cls_ids)
dt = time.perf_counter() t0
cv2.putText(
vis, f"{1.0/dt:.1f} FPS e2e", (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0, 255, 255), 2,
)
cv2.imshow("orin-detect", vis)
if cv2.waitKey(1) & 0xFF == ord("q"):
break

cap.release()
cv2.destroyAllWindows()

if __name__ == "__main__":
main()

python3 detect_camera.py –engine yolov8n_fp16.engine –device 0

无桌面、只有 SSH 时,imshow 会失败,改回文件输入或开虚拟显示。远程桌面叠一层也会拖帧率;测性能时尽量减少预览,或分开报“纯推理”和“预览端到端”。

CSI 相机还要确认载板接口和分辨率模式,适合放在这条 USB 链路稳定之后。


9. 一键对照清单

按顺序勾(四个文件都已从上文保存到 ~/orin-detect):

cd ~/orin-detect

# 1) 环境
python3 -c "import tensorrt, cv2, pycuda.driver; print('ok')"

# 2) ONNX(可在 PC 完成再 scp)
python3 export_onnx.py

# 3) 本板建引擎
bash build_engine.sh

# 4) 出框
python3 detect_trt.py –engine yolov8n_fp16.engine –source bus.jpg –save result.jpg

# 5) 计时
python3 detect_trt.py –engine yolov8n_fp16.engine –source demo.mp4 –benchmark

# 6) 相机(可选)
python3 detect_camera.py –engine yolov8n_fp16.engine –device 0


10. 常见问题

现象多见原因先查什么
import tensorrt 失败 JetPack 不完整 / 虚拟环境不对 用系统 Python,对照 JP 文档
import pycuda 失败 未装或 CUDA 路径不对 pip3 install pycuda,确认 nvcc
建引擎失败 算子不支持、ONNX 坏、磁盘满 日志、df -h、换固定 shape 导出
能跑但框乱 letterbox / 坐标还原不一致 与 Ultralytics 预处理对齐
输出 shape 对不上 不是 YOLOv8 这类输出 打印 output.shape,改 postprocess
很慢 功耗档低、走了 CPU、分辨率过大 nvpmodel、tegrastats
换板不能跑 引擎架构绑定 在目标板上重建引擎
相机黑屏 权限、占用、格式 video 组、fuser /dev/video0

权限问题也常见:用户不在 video 组,相机打不开。加组后重新登录再试。

路径尽量用绝对路径试一次,能排除软链指向已删文件这类问题。


11. 做到哪算闭环完成

  • 本板生成了可用的 TensorRT 引擎
  • 固定视频或图片能稳定出框(result.jpg 能给人看)
  • 知道当前功耗档和输入尺寸
  • 能说清自己的 FPS 是“纯推理”还是“含前后处理”
  • (可选)USB 相机也能接到同一条链

都满足后,再考虑:

  • 换成业务模型与数据(注意输出布局是否仍是 YOLOv8 风格)
  • DeepStream 多路
  • 跟踪、报警、落库等业务逻辑
  • 量产外壳下的热和掉帧

  • 12. 和 Orin / Thor 选型的关系

    最小检测闭环在 Orin Nano 上就能做教学和流程验证。路数多、模型重、还要并发生成式,再回头看 NX / AGX / Thor。

    流程验证用小板,量产指标在目标板上复测。引擎和数字都不要跨板硬搬。

    选型可参考之前写的《Jetson Orin 和 Thor 怎么选》;环境确认可以看《Orin 开发环境搭建与第一天 Checklist》。


    13. 小结

    Orin 上做检测,先求闭环通,再求快和准。固定输入、本板建引擎、预处理对齐、计时写清条件,这四步做到,后面换模型和加相机才有根基。

    换业务模型、DeepStream 图怎么搭,可以另开篇写。

    赞(0)
    未经允许不得转载:171主机测评 » Orin 上目标检测最小闭环(含代码)
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址