Skip to main content

2 posts tagged with "NPU"

View All Tags

DshanPI-A1 Review Part 5: NPU in Action - YOLOv5 Real-Time Object Detection Acceleration

· 18 min read
Yuxuan
100askTeam yuxuan.

Preface

In previous articles we implemented CPU-based MediaPipe gesture recognition. Although it runs, the 15-25 FPS performance is still a bit strained, and CPU usage is high. This time I will squeeze the full hardware potential of the RK3576 - using the onboard NPU (Neural Processing Unit) to accelerate deep-learning inference. First, let's cover a few concepts.

What is an NPU? An NPU (Neural Processing Unit) is a hardware accelerator designed specifically for AI operations. Unlike a CPU/GPU, an NPU is deeply optimized for the matrix operations, convolutions, and other operations used in neural networks. The RK3576 chip has a built-in dual-core NPU with a theoretical compute capacity of 6 TOPS, which can dramatically boost model inference speed and lower power consumption.

Why YOLOv5? Actually, I originally intended to keep optimizing my previous MediaPipe TFLite model conversion, but I ran into dependency hell (a pit I stumbled into for a long time...), so this time I first used the officially provided YOLOv5 model to validate the NPU functionality. YOLOv5 is one of the most popular real-time object detection algorithms today and can simultaneously detect multiple objects and their positions in an image.

1. Environment Preparation

1.1 Hardware Connections

  • RK3576 development board (already flashed with Buildroot)
  • IMX415 camera (connected at /dev/video11)
  • HDMI monitor
  • Serial connection (for command-line operation)

image-20251222175427219

1.2 Check the NPU Hardware

First, log in to the board and check whether the NPU is working normally:

# View the NPU load (should show Core0 and Core1)
cat /sys/kernel/debug/rknpu/load

image-20251222175450647

This shows that both NPU cores are idle and ready to go!

Tip: The RK3576's NPU uses a dual-core architecture and can process two models in parallel, or pipeline the different layers of one large model across the two cores.

1.3 Check the Python Environment

# View the Python version
python3 --version

image-20251222175518307

My output is Python 3.11.8; this version matters, as it must match when installing libraries later.

2. Install the RKNN Runtime Environment

2.1 What is RKNN?

RKNN (Rockchip Neural Network) is the deep-learning inference framework Rockchip developed for its own NPU. The whole toolchain is split into two parts:

  • rknn-toolkit2 (PC side): used for model conversion, turning TensorFlow/PyTorch/ONNX models into .rknn format
  • rknn-toolkit-lite2 (board side): a lightweight runtime library used to load and infer .rknn models on RK chips

This time we only need on-board inference, so we only install the lite version.

2.2 Get the Installation Package

The good news is that if you don't want to download from GitHub because it's slow or unstable, you can use the download link provided by our 100ask: https://dl.100ask.net/Hardware/MPU/RK3576-DshanPi-A1/utils/rknn-toolkit2.zip Download it and transfer it to our DshanPi-A1.

cd /rknn-toolkit2/rknn-toolkit-lite2/packages/
ls -lh

image-20251222175600879

You can see there are .whl installation packages for multiple Python versions; the one we need is the cp311 (Python 3.11) ARM64 version.

2.3 Install rknn-toolkit-lite2

# First force-install the main package (skip dependency checks, because dependencies are installed separately later)
pip3 install --no-deps rknn_toolkit_lite2-2.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

# Then use the Tsinghua mirror to install the missing dependencies
pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple psutil ruamel.yaml

This way we don't waste time downloading unnecessary packages.

When you see Successfully installed, it's done!

2.4 Verify the Installation

python3 -c "from rknnlite.api import RKNNLite; print('✅ rknn-toolkit-lite2 installed successfully!')"

image-20251222175629367

If the check mark appears, you're good!

3. NPU Benchmarking

Before running real-time detection, first use a simple image-classification model to test the NPU's performance.

3.1 Test with ResNet18

Enter the example directory:

cd /rknn-toolkit2/rknn-toolkit-lite2/examples/resnet18
ls -lh

image-20251222175647550

You can see:

  • resnet18_for_rk3576.rknn - a model specifically optimized for RK3576
  • space_shuttle_224.jpg - test image
  • test.py - inference script

Run the test:

python3 test.py

image-20251222175712361

My results:

  • Recognition result: Space Shuttle - 99.96% confidence
  • Inference latency: 11.21 ms
  • Average FPS: 89.24

This means the NPU can process 89 images per second - 3-6x faster than my previous CPU-based MediaPipe!

3.2 Performance Benchmark

To test the NPU performance more accurately, I wrote a script that loops 100 times (you can test it yourself):

cd ~
mkdir -p npu_test
cd npu_test

# Copy the model and image
cp /rknn-toolkit2/rknn-toolkit-lite2/examples/resnet18/resnet18_for_rk3576.rknn ./
cp /rknn-toolkit2/rknn-toolkit-lite2/examples/resnet18/space_shuttle_224.jpg ./

Create the test script benchmark.py:

import cv2
import numpy as np
import time
from rknnlite.api import RKNNLite

rknn = RKNNLite()
rknn.load_rknn('resnet18_for_rk3576.rknn')
rknn.init_runtime(core_mask=RKNNLite.NPU_CORE_0)

img = cv2.imread('space_shuttle_224.jpg')
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img = np.expand_dims(img, 0)

# Warm up
for _ in range(10):
rknn.inference(inputs=[img])

# Test 100 times
times = []
for i in range(100):
start = time.time()
rknn.inference(inputs=[img])
times.append((time.time() - start) * 1000)

print(f'Average latency: {np.mean(times):.2f} ms')
print(f'Min latency: {np.min(times):.2f} ms')
print(f'Max latency: {np.max(times):.2f} ms')
print(f'Average FPS: {1000/np.mean(times):.2f}')

rknn.release()

Run it:

python3 benchmark.py

4. YOLOv5 Object Detection

Now for the main event - using the NPU for real-time object detection!

4.1 What is YOLOv5?

YOLO (You Only Look Once) is a single-stage object detection algorithm that can simultaneously predict the positions and categories of multiple objects in a single forward pass. Compared with the two-stage R-CNN family, YOLO is faster and well suited for real-time scenarios.

YOLOv5 is the fifth generation of this family and supports detecting 80 common object categories (people, cars, animals, furniture, etc.).

4.2 Prepare the Model and Test Image

cd ~
mkdir -p npu_yolo_test
cd npu_yolo_test

# Copy the YOLOv5 model specialized for RK3576
cp /rknn-toolkit2/rknpu2/examples/rknn_yolov5_demo/model/RK3576/yolov5s-640-640.rknn ./

# Copy the test image (a bus photo)
cp /rknn-toolkit2/rknpu2/examples/rknn_yolov5_demo/model/bus.jpg ./

ls -lh

image-20251222175741596

4.3 Single-Image Detection Test

The complete post-processing code here is fairly long (including the NMS non-maximum suppression algorithm and so on), so I consolidated it into one script.

Create yolo_npu_test.py (see Appendix A for the full code), then run it:

python3 yolo_npu_test.py

image-20251222175811624

My results:

  • Detected 5 objects:
    • 3 persons: 88.0%, 87.1%, 82.8%
    • 1 bus: 70.1%
    • 1 partially occluded person: 30.7%
  • NPU inference latency: 87.94 ms
  • FPS: 11.37

The detection result is saved in result_npu.jpg, which you can transfer to your PC to view:

# Run in PowerShell on the PC (replace <board IP> with the actual IP)
scp root@<board IP>:/npu_yolo_test/result_npu.jpg .

[Detection Result Image] image-20251222175836265

Why is YOLOv5 slower than ResNet18?

  • ResNet18 only does classification, outputting the probabilities of 1000 categories (simple)
  • YOLOv5 detects the positions + categories of multiple objects, outputting feature maps at 3 different scales (complex)
  • But 11 FPS is already pretty good for object detection!

5. Real-Time Camera Detection

Single-image testing succeeded - now for a real challenge: using the IMX415 camera for real-time detection and displaying the result on the screen!

5.1 Display Solution: FIFO + GStreamer

As before, since Buildroot has no graphical interface and OpenCV's imshow() cannot be used, we adopt the named pipe (FIFO) + GStreamer solution:

  1. Python reads the camera -> NPU inference -> draws boxes -> encodes to JPEG
  2. Writes into the FIFO pipe
  3. GStreamer reads from the pipe -> decodes -> displays on the screen

This is a common inter-process communication method on Linux and was also used in the previous gesture recognition project.

5.2 Create a One-Click Launch Script

For convenience, I packed the whole flow into a Shell script yolo_npu_display.sh:

cd /npu_yolo_test

cat > yolo_npu_display.sh << 'EOF'
#!/bin/bash

echo "=========================================="
echo "YOLOv5 NPU Real-time Detection - RK3576"
echo "=========================================="
echo ""

# Restart the 3A server (camera auto-exposure/white-balance/auto-focus)
echo "Restarting 3A server..."
killall rkaiq_3A_server 2>/dev/null
sleep 2
rm -f /tmp/.rkaiq_3A* 2>/dev/null
/etc/init.d/S40rkaiq_3A start >/dev/null 2>&1
sleep 3

# Create the FIFO pipe
FIFO_PATH="/tmp/yolo_fifo"
rm -f $FIFO_PATH
mkfifo $FIFO_PATH

echo "Starting display pipeline..."
gst-launch-1.0 -q filesrc location=$FIFO_PATH ! jpegparse ! jpegdec ! videoconvert ! videoscale ! video/x-raw,width=1280,height=720 ! waylandsink fullscreen=true sync=false &
GST_PID=$!

sleep 2

echo "Starting YOLOv5 NPU detection..."
python3 - <<'PYTHON_CODE' &
import cv2
import numpy as np
import time
from rknnlite.api import RKNNLite
from collections import deque

RKNN_MODEL = '/npu_yolo_test/yolov5s-640-640.rknn'
CAMERA_ID = 11
IMG_SIZE = 640
OBJ_THRESH = 0.25
NMS_THRESH = 0.45
FIFO_PATH = '/tmp/yolo_fifo'

CLASSES = ("person", "bicycle", "car", "motorbike", "aeroplane", "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", "sofa",
"pottedplant", "bed", "diningtable", "toilet", "tvmonitor", "laptop", "mouse", "remote", "keyboard",
"cell phone", "microwave", "oven", "toaster", "sink", "refrigerator", "book", "clock", "vase",
"scissors", "teddy bear", "hair drier", "toothbrush")

def xywh2xyxy(x):
y = np.copy(x)
y[:, 0] = x[:, 0] - x[:, 2] / 2
y[:, 1] = x[:, 1] - x[:, 3] / 2
y[:, 2] = x[:, 0] + x[:, 2] / 2
y[:, 3] = x[:, 1] + x[:, 3] / 2
return y

def process(input, mask, anchors):
anchors = [anchors[i] for i in mask]
grid_h, grid_w = map(int, input.shape[0:2])
box_confidence = np.expand_dims(input[..., 4], axis=-1)
box_class_probs = input[..., 5:]
box_xy = input[..., :2]*2 - 0.5
col = np.tile(np.arange(0, grid_w), grid_w).reshape(-1, grid_w)
row = np.tile(np.arange(0, grid_h).reshape(-1, 1), grid_h)
col = col.reshape(grid_h, grid_w, 1, 1).repeat(3, axis=-2)
row = row.reshape(grid_h, grid_w, 1, 1).repeat(3, axis=-2)
grid = np.concatenate((col, row), axis=-1)
box_xy += grid
box_xy *= int(IMG_SIZE/grid_h)
box_wh = pow(input[..., 2:4]*2, 2)
box_wh = box_wh * anchors
box = np.concatenate((box_xy, box_wh), axis=-1)
return box, box_confidence, box_class_probs

def filter_boxes(boxes, box_confidences, box_class_probs):
boxes = boxes.reshape(-1, 4)
box_confidences = box_confidences.reshape(-1)
box_class_probs = box_class_probs.reshape(-1, box_class_probs.shape[-1])
_box_pos = np.where(box_confidences >= OBJ_THRESH)
boxes = boxes[_box_pos]
box_confidences = box_confidences[_box_pos]
box_class_probs = box_class_probs[_box_pos]
class_max_score = np.max(box_class_probs, axis=-1)
classes = np.argmax(box_class_probs, axis=-1)
_class_pos = np.where(class_max_score >= OBJ_THRESH)
boxes = boxes[_class_pos]
classes = classes[_class_pos]
scores = (class_max_score * box_confidences)[_class_pos]
return boxes, classes, scores

def nms_boxes(boxes, scores):
x, y = boxes[:, 0], boxes[:, 1]
w, h = boxes[:, 2] - boxes[:, 0], boxes[:, 3] - boxes[:, 1]
areas = w * h
order = scores.argsort()[::-1]
keep = []
while order.size > 0:
i = order[0]
keep.append(i)
xx1 = np.maximum(x[i], x[order[1:]])
yy1 = np.maximum(y[i], y[order[1:]])
xx2 = np.minimum(x[i] + w[i], x[order[1:]] + w[order[1:]])
yy2 = np.minimum(y[i] + h[i], y[order[1:]] + h[order[1:]])
w1 = np.maximum(0.0, xx2 - xx1 + 0.00001)
h1 = np.maximum(0.0, yy2 - yy1 + 0.00001)
inter = w1 * h1
ovr = inter / (areas[i] + areas[order[1:]] - inter)
inds = np.where(ovr <= NMS_THRESH)[0]
order = order[inds + 1]
return np.array(keep)

def yolov5_post_process(input_data):
masks = [[0, 1, 2], [3, 4, 5], [6, 7, 8]]
anchors = [[10, 13], [16, 30], [33, 23], [30, 61], [62, 45],
[59, 119], [116, 90], [156, 198], [373, 326]]
boxes, classes, scores = [], [], []
for input, mask in zip(input_data, masks):
b, c, s = process(input, mask, anchors)
b, c, s = filter_boxes(b, c, s)
boxes.append(b)
classes.append(c)
scores.append(s)
if len(boxes) == 0:
return None, None, None
boxes = np.concatenate(boxes)
boxes = xywh2xyxy(boxes)
classes = np.concatenate(classes)
scores = np.concatenate(scores)
nboxes, nclasses, nscores = [], [], []
for c in set(classes):
inds = np.where(classes == c)
b, c, s = boxes[inds], classes[inds], scores[inds]
keep = nms_boxes(b, s)
nboxes.append(b[keep])
nclasses.append(c[keep])
nscores.append(s[keep])
if not nclasses:
return None, None, None
return np.concatenate(nboxes), np.concatenate(nclasses), np.concatenate(nscores)

class YOLODetector:
def __init__(self):
self.fps_queue = deque(maxlen=30)
self.last_time = time.time()
self.fps = 0.0

def calc_fps(self):
t = time.time()
if t - self.last_time > 0:
self.fps_queue.append(1.0 / (t - self.last_time))
self.fps = sum(self.fps_queue) / len(self.fps_queue)
self.last_time = t

def draw_detections(self, frame, boxes, scores, classes, scale_x, scale_y):
for box, score, cl in zip(boxes, scores, classes):
x1 = int(box[0] * scale_x)
y1 = int(box[1] * scale_y)
x2 = int(box[2] * scale_x)
y2 = int(box[3] * scale_y)
cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2)
label = f'{CLASSES[cl]} {score:.2f}'
cv2.putText(frame, label, (x1, y1 - 10),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)

def run(self):
print("Initializing NPU...")
rknn_lite = RKNNLite()
rknn_lite.load_rknn(RKNN_MODEL)
rknn_lite.init_runtime(core_mask=RKNNLite.NPU_CORE_0)
print("NPU ready!")

print(f"Opening camera /dev/video{CAMERA_ID}...")
cap = cv2.VideoCapture(CAMERA_ID, cv2.CAP_V4L2)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
cap.set(cv2.CAP_PROP_FPS, 30)

width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
print(f"Camera: {width}x{height}")
print("Detection running...\n")

fifo = open(FIFO_PATH, 'wb')
frame_count = 0
scale_x = width / IMG_SIZE
scale_y = height / IMG_SIZE

try:
while True:
ret, frame = cap.read()
if not ret:
time.sleep(0.1)
continue

frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
img_resized = cv2.resize(frame_rgb, (IMG_SIZE, IMG_SIZE))
img_input = np.expand_dims(img_resized, 0)

inf_start = time.time()
outputs = rknn_lite.inference(inputs=[img_input])
inf_time = (time.time() - inf_start) * 1000

input0 = outputs[0].reshape([3, -1] + list(outputs[0].shape[-2:]))
input1 = outputs[1].reshape([3, -1] + list(outputs[1].shape[-2:]))
input2 = outputs[2].reshape([3, -1] + list(outputs[2].shape[-2:]))
input_data = [
np.transpose(input0, (2, 3, 0, 1)),
np.transpose(input1, (2, 3, 0, 1)),
np.transpose(input2, (2, 3, 0, 1))
]

boxes, classes, scores = yolov5_post_process(input_data)

if boxes is not None:
self.draw_detections(frame, boxes, scores, classes, scale_x, scale_y)
obj_count = len(boxes)
else:
obj_count = 0

self.calc_fps()
cv2.rectangle(frame, (5, 5), (400, 120), (0, 100, 0), -1)
cv2.putText(frame, f'FPS: {self.fps:.1f}', (15, 35),
cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2)
cv2.putText(frame, f'NPU: {inf_time:.1f}ms', (15, 70),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
cv2.putText(frame, f'Objects: {obj_count}', (15, 105),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 0), 2)

_, jpeg = cv2.imencode('.jpg', frame, [cv2.IMWRITE_JPEG_QUALITY, 85])
fifo.write(jpeg.tobytes())
fifo.flush()

frame_count += 1
if frame_count % 50 == 0:
print(f"Frame {frame_count}: FPS={self.fps:.1f}, NPU={inf_time:.1f}ms, Objects={obj_count}")

except KeyboardInterrupt:
print("\nStopping...")
finally:
fifo.close()
cap.release()
rknn_lite.release()
print("Released resources")

YOLODetector().run()
PYTHON_CODE

PYTHON_PID=$!

echo ""
echo "=========================================="
echo "System started!"
echo "Screen should show real-time detection"
echo "Press Ctrl+C to exit"
echo "=========================================="
echo ""

trap "echo ''; echo 'Stopping...'; kill $PYTHON_PID $GST_PID 2>/dev/null; rm -f $FIFO_PATH; echo 'Cleaned up'; exit" INT

wait $PYTHON_PID

kill $GST_PID 2>/dev/null
rm -f $FIFO_PATH
echo "Cleaned"
EOF

chmod +x yolo_npu_display.sh

[Screenshot 13: Script creation complete]

5.3 Run Real-Time Detection

./yolo_npu_display.sh

image-20251222175903207

You will see:

  1. The 3A server restart
  2. The FIFO pipe being created
  3. The GStreamer display pipeline starting
  4. YOLOv5 detection starting

image-20251222175922272

The terminal will print performance statistics every 50 frames, for example:

Frame 50: FPS=10.2, NPU=52.3ms, Objects=2
Frame 100: FPS=10.5, NPU=48.7ms, Objects=1

image-20251222175948756

  • Green detection boxes mark the objects
  • The top-left corner shows the FPS, NPU latency, and number of detections

Press Ctrl+C to stop the program.

6. Performance Analysis

6.1 Measured Data

My real-time detection results:

MetricValue
Average FPS10.0-10.8
NPU inference latency47-59 ms
Total latency (incl. capture/draw/display)84-101 ms
Max number of detected objects13 objects

image-20251222180048556

6.2 Comparison with the CPU Solution

SolutionFPSCPU UsagePower
MediaPipe (CPU)15-2550-65%High
YOLOv5 (NPU)10-1115-25%Low

Although YOLOv5's FPS is slightly lower than MediaPipe's gesture recognition, note that:

  • YOLOv5 does full-scene object detection (80 classes), whereas MediaPipe only does hand detection (a much simpler task)
  • YOLOv5 uses the NPU, reducing CPU usage by more than 60%
  • The NPU's power consumption is far lower than the CPU running at full speed, so heat is notably reduced
  • If you only use YOLOv5 to detect humans (the person class), you can further optimize the post-processing and the FPS can go even higher

6.3 Why Didn't It Reach the Theoretical 89 FPS?

ResNet18 can hit 89 FPS on a single image - why does real-time detection only reach 10 FPS? Where is the bottleneck?

Based on profiling analysis:

  • NPU inference: ~50ms (main bottleneck)
  • Camera capture: ~5ms
  • Post-processing (NMS, etc.): ~15ms
  • Drawing boxes and text: ~8ms
  • JPEG encoding: ~10ms
  • FIFO transfer + GStreamer: ~5ms

Summary:

  1. The YOLOv5 model is much larger than ResNet18 (7.9MB vs 12MB) and has a larger compute load
  2. The NMS algorithm in post-processing is pure Python and relatively slow (could be rewritten in C++ or accelerated with CUDA)
  3. JPEG encoding also takes a fair amount of time (could be replaced with H.264 hardware encoding)

Optimization directions:

  • Use YOLOv5-nano (a smaller model)
  • Accelerate post-processing with Cython
  • Enable NPU dual-core parallelism
  • Use RK3576's hardware video encoder

7. Pitfalls Encountered and Solutions

7.1 PC-Side Model Conversion Dependency Hell

Problem: I wanted to use rknn-toolkit2 on the PC to convert MediaPipe's TFLite model to .rknn format, but ran into a protobuf version conflict - TensorFlow requires <3.20, but rknn-toolkit2 requires >=4.25, completely incompatible.

Attempted solutions:

  • Switch TensorFlow version -> failed
  • Use a virtual environment -> user refused (I was too lazy...)
  • Tsinghua mirror acceleration -> still conflicted

Final solution: Gave up on PC-side conversion and directly used the officially provided .rknn model to test the NPU. I'll use Docker to run the conversion tool later if needed.

Lesson: Python dependency management is a huge pit, especially for deep-learning frameworks. Strongly recommend using Docker or conda environments for isolation.

7.2 Camera Could Not Be Opened

Problem: Using cv2.VideoCapture(11) directly failed to open.

Reason: The rkaiq_3A server (responsible for the camera's auto-exposure/white balance) was not restarted.

Solution: Add this at the start of the script:

killall rkaiq_3A_server 2>/dev/null
sleep 2
rm -f /tmp/.rkaiq_3A* 2>/dev/null
/etc/init.d/S40rkaiq_3A start >/dev/null 2>&1
sleep 3

7.3 GStreamer Could Not Find videoparse

Problem: At first I wanted to use the videoparse plugin, but it reported that the plugin was missing.

Reason: Buildroot is a stripped-down system, and many GStreamer plugins are not installed.

Solution: Switch to JPEG-stream transmission:

  • Python encodes to JPEG -> FIFO -> GStreamer's jpegparse decodes
  • This plugin is installed by default

7.4 Python Script Chinese-Encoding Error

Problem: The script had Chinese comments, and running it threw an error:

SyntaxError: Non-UTF-8 code starting with '\xe5'

Solution: Change all Chinese comments to English, or add this at the top of the file:

# -*- coding: utf-8 -*-

8. Summary and Outlook

8.1 Takeaways from This Practice

  1. Successfully validated the RK3576's NPU hardware-acceleration capability

    • ResNet18: 89 FPS (11ms latency)
    • YOLOv5: 10 FPS (50ms NPU latency)
    • CPU usage down 60%, power consumption significantly reduced
  2. Mastered the use of the RKNN toolchain

    • Installation and API of rknn-toolkit-lite2
    • Loading and inference of .rknn models
    • Specifying and configuring NPU cores
  3. Built a complete real-time detection pipeline

    • Camera capture -> NPU inference -> post-processing -> display
    • The FIFO + GStreamer display solution
    • Performance monitoring and FPS calculation
  4. Stumbled through various pitfalls

    • Dependency conflicts, camera initialization, display pipeline, etc.
    • Accumulated valuable debugging experience

8.2 Reflections

The RK3576's NPU is indeed powerful; 6 TOPS of compute is a top-tier configuration among edge devices. Although there are some pitfalls in using it (mainly dependency management), the overall experience is still good.

My biggest takeaway is: AI deployment is not easy! From model training to deployment, there is so much to consider - accuracy, speed, power, cost... every link requires trade-offs. But the moment I saw the real-time detection picture running smoothly, all the effort was worth it!

9. References

  1. RKNN-Toolkit2 Official Documentation
  2. RK3576 NPU Technical White Paper
  3. YOLOv5 Official Repository
  4. GStreamer Pipeline Design Guide
  5. My earlier articles

Appendix A: Complete YOLOv5 Inference Script

Due to length, the complete Python code has been integrated into the yolo_npu_display.sh script.

Key function descriptions:

  • xywh2xyxy(): bounding-box coordinate conversion
  • process(): YOLO output parsing
  • filter_boxes(): confidence filtering
  • nms_boxes(): non-maximum suppression (removes overlapping boxes)
  • yolov5_post_process(): complete post-processing flow

DshanPI-A1 Setting Up the RKNN Environment under Buildroot

· 15 min read
Yuxuan
100askTeam yuxuan.

Development Environment

PC side: ubuntu22.04-x86-64

Board side: buildroot

The specific theoretical part won't be elaborated here; there's plenty online. This records the entire operation process, divided into two main parts: 1. PC side 2. Board side

1. PC Side

Setting Up the RKNN-Toolkit2 Environment

# Download the code repository
mkdir rknn
cd rknn
wget https://dl.100ask.net/Hardware/MPU/RK3576-DshanPi-A1/utils/rknn-toolkit2.zip
unzip rknn-toolkit2.zip
wget https://dl.100ask.net/Hardware/MPU/RK3576-DshanPi-A1/utils/rknn_model_zoo.zip
unzip rknn_model_zoo.zip
# Set up conda environment
wget -c https://repo.anaconda.com/archive/Anaconda3-2025.06-1-Linux-x86_64.sh
bash Anaconda3-2025.06-1-Linux-x86_64.sh
Please, press ENTER to continue
>>>
Do you accept the license terms? [yes|no]
>>> yes
Anaconda3 will now be installed into this location:
/home/ubuntu/anaconda3
- Press ENTER to confirm the location
- Press CTRL-C to abort the installation
- Or specify a different location below

[/home/ubuntu/anaconda3] >>>
You can undo this by running `conda init --reverse $SHELL`? [yes|no]
[no] >>> yes
Thank you for installing Anaconda3!
# Activate environment variables
source ~/.bashrc
# Create RKNN environment
conda create -n rknn-toolkit2 python=3.8
conda activate rknn-toolkit2
# Install RKNN-Toolkit2
cd rknn-toolkit2/rknn-toolkit2/packages/x86_64/
conda install compilers cmake
pip install -r requirements_cp38-2.3.2.txt
pip install rknn_toolkit2-2.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
# Verify installation
(rknn-toolkit2) ubuntu@ubuntu-2204:~/rknn/rknn-toolkit2/rknn-toolkit2/packages/x86_64$ python3
Python 3.8.20 (default, Oct 3 2024, 15:24:27)
[GCC 11.2.0] :: Anaconda, Inc. on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from rknn.api import RKNN
>>> exit()
(rknn-toolkit2) ubuntu@ubuntu-2204:~/rknn/rknn-toolkit2/rknn-toolkit2/packages/x86_64$

2. On-Board Inference

First, here is my environment: I use the buildroot that comes with the SDK, adapted by the 100ASK team, with rknnruntime already enabled, and I did not update the runtime.

root@rk3576-buildroot:/# uname -a
Linux rk3576-buildroot 6.1.75 #3 SMP Fri Nov 28 09:41:14 EST 2025 aarch64 GNU/Linux
root@rk3576-buildroot:/# find ./ -name *rknn*
./rockchip-test/npu2/model/RK356X/mobilenet_v1.rknn
./rockchip-test/npu2/model/RK3588/vgg16_max_pool_fp16.rknn
./sys/kernel/debug/clk/hclk_rknn_root
./sys/kernel/debug/clk/clk_rknn_dsu0
./sys/kernel/debug/clk/aclk_rknn0
./sys/kernel/debug/clk/aclk_rknn1
./sys/kernel/debug/clk/aclk_rknn_cbuf
./sys/kernel/debug/clk/hclk_rknn_cbuf
./usr/share/model/RK3562/mobilenet_v1.rknn
./usr/share/model/RK3566_RK3568/mobilenet_v1.rknn
./usr/share/model/RK3588/mobilenet_v1.rknn
./usr/share/model/RK3576/mobilenet_v1.rknn
./usr/lib/librknnrt.so
./usr/bin/start_rknn.sh
./usr/bin/rknn_common_test
./usr/bin/restart_rknn.sh
./usr/bin/rknn_server

tip: My buildroot configuration ./build.sh bconfig

 [*] Rockchip NPU power control for linux                                                  │ │
│ │ [ ] Rockchip NPU power control combine for linux │ │
│ │ [ ] Rockchip recovery for linux │ │
│ │ [ ] rkadk │ │
│ │ [ ] rknpu │ │
│ │ [ ] rknpu pcie │ │
│ │ [ ] python-rknn │ │
│ │ [*] rknpu2 │ │
│ │ [*] rknpu2 example │ │
│ │ [ ] rknpu firmware │ │
│ │ [ ] RKPARTYBOX demo │ │
│ │ [*] rockchip script

As you can see, I did not change the rknpu configuration; buildroot already configures the rknpu driver and rknnruntime by default.

Problem 1: Solving ADB Permission Issues on the PC Side

When running on-board inference on the PC, the following error occurs:

:(rknn-toolkit2) ubuntu@ubuntu-2204:~/rknn/rknn_model_zoo/examples/yolov8/python$ sudo python3 yolov8.py --target rk3576 --model_path ../model/yolov8.rknn --img_show
Traceback (most recent call last):
File "/home/ubuntu/rknn/rknn_model_zoo/examples/yolov8/python/yolov8.py", line 2, in <module>
import cv2
ModuleNotFoundError: No module named 'cv2'
(rknn-toolkit2) ubuntu@ubuntu-2204:~/rknn/rknn_model_zoo/examples/yolov8/python$ python3 yolov8.py --target rk3576 --model_path ../model/yolov8.rknn --img_show
I rknn-toolkit2 version: 2.3.2
--> Init runtime environment
adb: unable to connect for root: insufficient permissions for device: user in plugdev group; are your udev rules wrong?
See [http://developer.android.com/tools/device.html] for more information
I target set by user is: rk3576
E init_runtime: Get board target failed, ret code: 1. error: insufficient permissions for device: user in plugdev group; are your udev rules wrong?
See [http://developer.android.com/tools/device.html] for more information

E init_runtime: Traceback (most recent call last):
File "rknn/api/rknn_log.py", line 344, in rknn.api.rknn_log.error_catch_decorator.error_catch_wrapper
File "rknn/api/rknn_base.py", line 2566, in rknn.api.rknn_base.RKNNBase.init_runtime
File "rknn/api/rknn_runtime.py", line 223, in rknn.api.rknn_runtime.RKNNRuntime.__init__
File "rknn/api/rknn_platform.py", line 607, in rknn.api.rknn_platform.get_board_info
RuntimeError

Note this line

adb: unable to connect for root: insufficient permissions for device: user in plugdev group; are your udev rules wrong?
See [http://developer.android.com/tools/device.html] for more information

It indicates insufficient adbd permissions; fix it with:

# Add udev rules
echo 'SUBSYSTEM=="usb", ATTR{idVendor}=="2207", MODE="0666", GROUP="plugdev"' | sudo tee /etc/udev/rules.d/51-android.rules
# Reload rules
sudo udevadm control --reload-rules
sudo udevadm trigger
# Restart ADB
adb kill-server
adb start-server
adb devices
List of devices attached
8074683be1050187 device

Problem 2: Solving the Board-Side adbd Port 5037 Not Open Issue

Symptom:

# Run on-board inference
python3 yolov8.py --target rk3576 --model_path ../model/yolov8.rknn --img_show
I rknn-toolkit2 version: 2.3.2
--> Init runtime environment
adbd is already running as root
I target set by user is: rk3576
I Get hardware info: target_platform = rk3576, os = Linux, aarch = aarch64
I Check RK3576 board npu runtime version
W kill server failed while restarting, ret code: 1. warning: killall: rknn_server: no process killed
Please skip it if rknn_server not running on board.
I Starting ntp or adb, target is RK3576
I Start adb...
I Connect to Device success!
I NPUTransfer(3672747): Starting NPU Transfer Client, Transfer version 2.2.2 (12abf2a@2024-09-02T03:22:41)
E RKNNAPI: rknn_init, server connect fail! ret = -9(ERROR_PIPE)!
E init_runtime: The rknn_server on the concected device is abnormal, please start the rknn_server on the device according to:
https://github.com/airockchip/rknn-toolkit2/blob/master/doc/rknn_server_proxy.md
W init_runtime: ===================== WARN(1) =====================
E rknn-toolkit2 version: 2.3.2
E init_runtime: Traceback (most recent call last):
File "rknn/api/rknn_log.py", line 344, in rknn.api.rknn_log.error_catch_decorator.error_catch_wrapper

Note

    I NPUTransfer(3672747): Starting NPU Transfer Client, Transfer version 2.2.2 (12abf2a@2024-09-02T03:22:41)
E RKNNAPI: rknn_init, server connect fail! ret = -9(ERROR_PIPE)!
E init_runtime: The rknn_server on the concected device is abnormal, please start the rknn_server on the device according to:
https://github.com/airockchip/rknn-toolkit2/blob/master/doc/rknn_server_proxy.md

Following the hint, go to https://github.com/airockchip/rknn-toolkit2/blob/master/doc/rknn_server_proxy.md to find the solution

In this document's

6. FAQ

Problem 1

On Debian systems, the rknn_server service has been started in the background, but the following error still occurs during on-board inference:

D NPUTransfer: ERROR: socket read fd = 4, n = -1: Connection reset by peer
D NPUTransfer: Transfer client closed, fd = 4
E RKNNAPI: rknn_init, server connect fail! ret = -9(ERROR_PIPE)!
E build_graph: The rknn_server on the concected device is abnormal, please start the rknn_server on the device according to:
https://github.com/airockchip/rknn-toolkit2/blob/master/doc/rknn_server_proxy.md

Solution: This is usually because the adbd program on the Debian firmware is not listening on port 5037. You can run the following command on the board to check:

netstat -n -t -u -a

If the output does not contain port 5037, run the following commands to download and update the adbd program, and reboot the board; otherwise, skip the following steps.

wget -O adbd.zip https://ftzr.zbox.filez.com/v2/delivery/data/7f0ac30dfa474892841fcb2cd29ad924/adbd.zip
unzip adbd.zip
adb push adbd/linux-aarch64/adbd /usr/bin/adbd

Enter the device shell command to add executable permission to adbd

adb shell "chmod +x /usr/bin/adbd"
adb reboot

After rebooting the device, follow the startup steps to start the rknn_server service and try on-board inference again.

Although this document uses Debian firmware and I use buildroot, the symptom is the same. Follow its solution to troubleshoot:

# This is the board side
root@rk3576-buildroot:/# netstat -n -t -u -a
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 0.0.0.0:53 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN
tcp 0 0 :::53 :::* LISTEN
tcp 0 0 :::22 :::* LISTEN
tcp 0 0 :::5555 :::* LISTEN
udp 0 0 0.0.0.0:53 0.0.0.0:*
udp 0 0 0.0.0.0:67 0.0.0.0:*
udp 0 0 0.0.0.0:68 0.0.0.0:*
udp 0 0 127.0.0.1:323 0.0.0.0:*
udp 0 0 :::53 :::*
udp 0 0 ::1:323 0.0.0.0:*
udp 0 0 :::546 :::*

As you can see, port 5037 is indeed not open

# This is the PC side
wget -O adbd.zip https://ftzr.zbox.filez.com/v2/delivery/data/7f0ac30dfa474892841fcb2cd29ad924/adbd.zip
unzip adbd.zip
adb push adbd/linux-aarch64/adbd /usr/bin/adbd
adb shell "chmod +x /usr/bin/adbd"
adb reboot

After rebooting the development board, retest

# This is the board side
restart_rknn.sh
root@rk3576-buildroot:/# netstat -n -t -u -a
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 127.0.0.1:5037 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:53 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:5555 0.0.0.0:* LISTEN
tcp 0 0 :::22 :::* LISTEN
tcp 0 0 :::53 :::* LISTEN
udp 0 0 0.0.0.0:53 0.0.0.0:*
udp 0 0 0.0.0.0:67 0.0.0.0:*
udp 0 0 0.0.0.0:68 0.0.0.0:*
udp 0 0 127.0.0.1:323 0.0.0.0:*
udp 0 0 :::546 :::*
udp 0 0 :::53 :::*
udp 0 0 ::1:323 0.0.0.0:*

As you can see, port 5037 is now being listened on. Continue trying on-board inference

# This is the PC side
(rknn-toolkit2) ubuntu@ubuntu-2204:~/rknn/rknn_model_zoo/examples/yolov8/python$ python3 yolov8.py --target rk3576 --model_path ../model/yolov8.rknn --img_show
I rknn-toolkit2 version: 2.3.2
--> Init runtime environment
adb: unable to connect for root: closed
I target set by user is: rk3576
I Get hardware info: target_platform = rk3576, os = Linux, aarch = aarch64
I Check RK3576 board npu runtime version
I Starting ntp or adb, target is RK3576
I Start adb...
I Connect to Device success!
I NPUTransfer(3675220): Starting NPU Transfer Client, Transfer version 2.2.2 (12abf2a@2024-09-02T03:22:41)
I NPUTransfer(3675220): TransferBuffer: min aligned size: 1024
D RKNNAPI: ==============================================
D RKNNAPI: RKNN VERSION:
D RKNNAPI: API: 2.3.2 (1842325 build@2025-03-30T09:55:23)

On-board inference succeeded, showing the face recognition image

b70deb69-aaeb-4e8f-a09f-c77091008511

3. On-Board Inference (Native)

Since buildroot is used, and the RK platform after rk1808 does not support python deployment in buildroot, setting up a python inference software stack yourself is time-consuming and encounters many issues. For the python demo, refer to the 100ASK RKNN环境搭建 | 东山Π; it won't be elaborated here. Using the cpp interface, we still use the aforementioned yolov8 for on-board inference:

Prepare the Model

cd ~/rknn/rknn_model_zoo/examples/yolov8/model
sh download_model.sh
ls -lah yolov8n.onnx
-rw-rw-r-- 1 ubuntu ubuntu 13M Nov 29 07:36 yolov8n.onnx

Model Conversion

cd ../python/
(rknn-toolkit2) ubuntu@ubuntu-2204:~/rknn/rknn_model_zoo/examples/yolov8/python$ python convert.py ../model/yolov8n.onnx rk3576 i8 ../model/yolov8n.rknn

python convert.py ../model/yolov5s_relu.onnx rk3576 i8 ../model/yolov5s_relu.rknn

I rknn-toolkit2 version: 2.3.2
--> Config model
done
--> Loading model
I Loading : 100%|██████████████████████████████████████████████| 126/126 [00:00<00:00, 43282.74it/s]
done
--> Building model
I OpFusing 0: 100%|█████████████████████████████████████████████| 100/100 [00:00<00:00, 1590.15it/s]
I OpFusing 1 : 100%|█████████████████████████████████████████████| 100/100 [00:00<00:00, 860.03it/s]
I OpFusing 0 : 100%|█████████████████████████████████████████████| 100/100 [00:00<00:00, 739.11it/s]
I OpFusing 1 : 100%|█████████████████████████████████████████████| 100/100 [00:00<00:00, 652.15it/s]
I OpFusing 2 : 100%|█████████████████████████████████████████████| 100/100 [00:00<00:00, 229.74it/s]
W build: found outlier value, this may affect quantization accuracy
const name abs_mean abs_std outlier value
model.0.conv.weight 2.44 2.47 -17.494
model.22.cv3.2.1.conv.weight 0.09 0.14 -10.215
model.22.cv3.1.1.conv.weight 0.12 0.19 13.361, 13.317
model.22.cv3.0.1.conv.weight 0.18 0.20 -11.216
I GraphPreparing : 100%|████████████████████████████████████████| 161/161 [00:00<00:00, 3291.17it/s]
I Quantizating : 100%|████████████████████████████████████████████| 161/161 [00:04<00:00, 34.87it/s]
W build: The default input dtype of 'images' is changed from 'float32' to 'int8' in rknn model for performance!
Please take care of this change when deploy rknn model with Runtime API!
W build: The default output dtype of '318' is changed from 'float32' to 'int8' in rknn model for performance!
Please take care of this change when deploy rknn model with Runtime API!
W build: The default output dtype of 'onnx::ReduceSum_326' is changed from 'float32' to 'int8' in rknn model for performance!
Please take care of this change when deploy rknn model with Runtime API!
W build: The default output dtype of '331' is changed from 'float32' to 'int8' in rknn model for performance!
Please take care of this change when deploy rknn model with Runtime API!
W build: The default output dtype of '338' is changed from 'float32' to 'int8' in rknn model for performance!
Please take care of this change when deploy rknn model with Runtime API!
W build: The default output dtype of 'onnx::ReduceSum_346' is changed from 'float32' to 'int8' in rknn model for performance!
Please take care of this change when deploy rknn model with Runtime API!
W build: The default output dtype of '350' is changed from 'float32' to 'int8' in rknn model for performance!
Please take care of this change when deploy rknn model with Runtime API!
W build: The default output dtype of '357' is changed from 'float32' to 'int8' in rknn model for performance!
Please take care of this change when deploy rknn model with Runtime API!
W build: The default output dtype of 'onnx::ReduceSum_365' is changed from 'float32' to 'int8' in rknn model for performance!
Please take care of this change when deploy rknn model with Runtime API!
W build: The default output dtype of '369' is changed from 'float32' to 'int8' in rknn model for performance!
Please take care of this change when deploy rknn model with Runtime API!
I rknn building ...
I rknn building done.
done
--> Export rknn model
done
cd ../model
ls -lah yolov8n.rknn
-rw-rw-r-- 1 ubuntu ubuntu 6.2M Nov 29 07:39 yolov8n.rknn

Run the RKNN C Example

First, compile the C example, then deploy the executable file, model file, and resource files to the board.

Compilation

For compilation, use the build-linux.sh script in the rknn_model_zoo directory; you need to configure the toolchain first. Modify build-linux.sh:

GCC_COMPILER=/home/ubuntu/rk3576/prebuilts/gcc/linux-x86/aarch64/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu
chmod +x ./build-linux.sh
./build-linux.sh -t rk3576 -a aarch64 -d yolov8
-- Set runtime path of "/home/ubuntu/rknn/rknn_model_zoo/install/rk3576_linux_aarch64/rknn_yolov8_demo/./rknn_yolov8_demo" to "$ORIGIN/../lib"
-- Installing: /home/ubuntu/rknn/rknn_model_zoo/install/rk3576_linux_aarch64/rknn_yolov8_demo/model/bus.jpg
-- Installing: /home/ubuntu/rknn/rknn_model_zoo/install/rk3576_linux_aarch64/rknn_yolov8_demo/model/coco_80_labels_list.txt
-- Installing: /home/ubuntu/rknn/rknn_model_zoo/install/rk3576_linux_aarch64/rknn_yolov8_demo/model/yolov8.rknn
-- Installing: /home/ubuntu/rknn/rknn_model_zoo/install/rk3576_linux_aarch64/rknn_yolov8_demo/model/yolov8n.rknn
-- Installing: /home/ubuntu/rknn/rknn_model_zoo/install/rk3576_linux_aarch64/rknn_yolov8_demo/lib/librknnrt.so
-- Installing: /home/ubuntu/rknn/rknn_model_zoo/install/rk3576_linux_aarch64/rknn_yolov8_demo/lib/librga.so
# Take a look
ubuntu@ubuntu-2204:~/rknn/rknn_model_zoo/install$ tree -L 4
.
└── rk3576_linux_aarch64
└── rknn_yolov8_demo
├── lib
│ ├── librga.so
│ └── librknnrt.so
├── model
│ ├── bus.jpg
│ ├── coco_80_labels_list.txt
│ ├── yolov8n.rknn
│ └── yolov8.rknn
├── rknn_yolov8_demo
└── rknn_yolov8_demo_zero_copy

This rknn_yolov8_demo is the set of files to be deployed to the board

adb push install/rk3576_linux_aarch64/rknn_yolov8_demo /data/
install/rk3576_linux_aarch64/rknn_yolov8_demo/: 8 files pushed. 3.6 MB/s (23008641 bytes in 6.049s)

Run on the board side

root@rk3576-buildroot:/data/rknn_yolov8_demo# ./rknn_yolov8_demo ./model/yolov8.rknn ./model/bus.jpg
load lable ./model/coco_80_labels_list.txt
model input num: 1, output num: 9
input tensors:
index=0, name=images, n_dims=4, dims=[1, 640, 640, 3], n_elems=1228800, size=1228800, fmt=NHWC, type=INT8, qnt_type=AFFINE, zp=-128, scale=0.003922
output tensors:
index=0, name=318, n_dims=4, dims=[1, 64, 80, 80], n_elems=409600, size=409600, fmt=NCHW, type=INT8, qnt_type=AFFINE, zp=-58, scale=0.117659
index=1, name=onnx::ReduceSum_326, n_dims=4, dims=[1, 80, 80, 80], n_elems=512000, size=512000, fmt=NCHW, type=INT8, qnt_type=AFFINE, zp=-128, scale=0.003104
index=2, name=331, n_dims=4, dims=[1, 1, 80, 80], n_elems=6400, size=6400, fmt=NCHW, type=INT8, qnt_type=AFFINE, zp=-128, scale=0.003173
index=3, name=338, n_dims=4, dims=[1, 64, 40, 40], n_elems=102400, size=102400, fmt=NCHW, type=INT8, qnt_type=AFFINE, zp=-45, scale=0.093747
index=4, name=onnx::ReduceSum_346, n_dims=4, dims=[1, 80, 40, 40], n_elems=128000, size=128000, fmt=NCHW, type=INT8, qnt_type=AFFINE, zp=-128, scale=0.003594
index=5, name=350, n_dims=4, dims=[1, 1, 40, 40], n_elems=1600, size=1600, fmt=NCHW, type=INT8, qnt_type=AFFINE, zp=-128, scale=0.003627
index=6, name=357, n_dims=4, dims=[1, 64, 20, 20], n_elems=25600, size=25600, fmt=NCHW, type=INT8, qnt_type=AFFINE, zp=-34, scale=0.083036
index=7, name=onnx::ReduceSum_365, n_dims=4, dims=[1, 80, 20, 20], n_elems=32000, size=32000, fmt=NCHW, type=INT8, qnt_type=AFFINE, zp=-128, scale=0.003874
index=8, name=369, n_dims=4, dims=[1, 1, 20, 20], n_elems=400, size=400, fmt=NCHW, type=INT8, qnt_type=AFFINE, zp=-128, scale=0.003922
model is NHWC input fmt
model input height=640, width=640, channel=3
origin size=640x640 crop size=640x640
input image: 640 x 640, subsampling: 4:2:0, colorspace: YCbCr, orientation: 1
scale=1.000000 dst_box=(0 0 639 639) allow_slight_change=1 _left_offset=0 _top_offset=0 padding_w=0 padding_h=0
rga_api version 1.10.1_[0]
rknn_run
person @ (211 241 282 507) 0.864
person @ (109 235 225 536) 0.856
bus @ (99 136 552 455) 0.856
person @ (476 223 560 521) 0.848
person @ (80 326 116 513) 0.280
write_image path: out.png width=640 height=640 channel=3 data=0x3e2e9200

View on the PC side

adb pull /data/rknn_yolov8_demo/out.png ./

image-20251129221548372

Matches expectations

At this point, the RKNN environment has been set up successfully