0. 들어가며 — 온디바이스 AI와 PEAK-i
클라우드에 이미지를 보내지 않고 기기 안에서 직접 추론하는 것이 온디바이스 AI다. 이 글은 K210 SoC 기반 실습장치 PEAK-i로 진행한 인공지능 비전인식 교육을 정리한 것이다. K210에는 KPU라는 신경망 가속기가 내장되어 있어, 손바닥만 한 보드에서 YOLO 객체 인식까지 돌아간다. 개발 언어는 MicroPython(MaixPy)이다. LED 제어부터 시작해 카메라, 색상 인식, QR 인식, 커스텀 모델 학습, 그리고 Node-RED 대시보드 연동까지 단계적으로 올라간다.
인공지능 비전인식 실습장치
hyenc.net
1. 개발 환경 준비
- Maixpy IDE — 코드 작성·실행과 카메라 프리뷰를 제공하는 개발 환경
- CP210x VCP 드라이버 — 보드가 가상 COM 포트로 인식되도록 하는 USB-UART 브리지 드라이버 (필수 선행 설치)
- Kflash GUI — K210 펌웨어를 플래시/erase 하는 도구
CP210x USB to UART Bridge VCP Drivers - Silicon Labs
The CP210x USB to UART Bridge Virtual COM Port (VCP) drivers are required for device operation as a Virtual COM Port to facilitate host communication with CP210x products. These devices can also interface to a host using the direct access driver.
www.silabs.com
GitHub - sipeed/kflash_gui: Cross platform GUI wrapper for kflash.py (download(/burn) tool for k210)
Cross platform GUI wrapper for kflash.py (download(/burn) tool for k210) - sipeed/kflash_gui
github.com
Release v1.8.1 · sipeed/kflash_gui
Sorry, something went wrong. No results found
github.com
2. MicroPython으로 하드웨어 다루기
K210은 fpioa(Field Programmable IO Array)로 물리 핀과 기능(GPIO, UART 등)을 자유롭게 매핑한다. 모든 예제가 fm.register(핀, 기능)으로 시작하는 이유다. 먼저 LED 하나를 켜 보자.
from Maix import GPIO
from fpioa_manager import fm
fm.register(6, fm.fpioa.GPIO0)
LED = GPIO(GPIO.GPIO0, GPIO.OUT)
LED.value(0)
while True:
pass
다음은 R·G·B 세 LED를 배열로 묶어 1초 간격으로 순회하는 예제다. 핀 9·10·11이 각각 LED에 매핑되고, 값 1이 OFF·0이 ON(액티브 로우)이라는 점에 주의한다.
from Maix import GPIO
from fpioa_manager import fm
import utime
# io pin 을 gpio에 mapping
fm.register(9, fm.fpioa.GPIO0)
fm.register(10, fm.fpioa.GPIO1)
fm.register(11, fm.fpioa.GPIO2)
# gpio 초기
LED_B = GPIO(GPIO.GPIO0, GPIO.OUT,value=1)
LED_G = GPIO(GPIO.GPIO1, GPIO.OUT,value=1)
LED_R = GPIO(GPIO.GPIO2, GPIO.OUT,value=1)
# 효율적인 반복을 위해 배열 선언
LED=[LED_B, LED_G, LED_R]
while True:
for i in range(0,3):
LED[i].value(0)
utime.sleep(1)
LED[i].value(1)
utime.sleep(1)
단순 ON/OFF를 넘어 밝기를 제어하려면 PWM을 쓴다. 타이머 채널에 PWM을 연결하고 duty를 0→100으로 올리면 LED가 서서히 밝아진다.
from machine import Timer, PWM
import time
tim = Timer(Timer.TIMER0, Timer.CHANNEL0, mode=Timer.MODE_PWM)
LED_R = PWM(tim, freq=100, duty=0, pin=11)
# RED = 9 RED , 10 GREEN , 11 BLUE
while True:
i=range(0, 100, 1)
for n in i:
LED_R.duty(n)
time.sleep(0.01)
print(n)
3. 카메라와 LCD — 비전의 출발점
비전 실습의 뼈대는 "카메라 스냅샷 → 처리 → LCD 표시" 루프다. set_vflip()은 상하 반전, set_hmirror()는 좌우 반전으로, 보드 장착 방향에 맞게 조정한다.
import sensor, image, lcd
sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA)
sensor.skip_frames()
sensor.run(1)
sensor.set_vflip(True) # set_vflip() >> 상하반전 // set_hmirror() >> 좌우반전
lcd.init()
lcd.mirror(True)
while(True):
lcd.display(sensor.snapshot())
4. 색상 인식 — Blob Tracking
find_blobs()는 지정한 색 범위에 해당하는 덩어리(blob)를 찾아 준다. 여기서 색 범위는 RGB가 아니라 LAB 색공간의 (L, A, B) 최소·최대 6튜플로 지정한다 — 조명 변화에 상대적으로 강하기 때문이다. 임계값은 LAB 변환 툴로 구해서 넣는다.
import sensor,lcd,time
sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA)
sensor.set_hmirror(0)
lcd.init(freq=15000000)
sensor.skip_frames(time = 2000)
clock=time.clock()
# https://ko.rakko.tools/tools/30/ <lab 컬러 변환 툴>
thresholds = [(60, 90, 40, 90, 5, 70), # 빨간색 임계값
(30, 100, -60, -20, -30, 50), # 녹색 임계값
(0, 30, 0, 64, -128, -20)] # 파란색 임계값
while True:
img=sensor.snapshot()
blobs = img.find_blobs([thresholds[1]]) # 0, 1, 2는 각각 빨강, 초록, 파랑
if blobs:
for b in blobs:
tmp=img.draw_rectangle(b[0:4])
#tmp=img.draw_cross(b[5], b[6])
lcd.display(img)
print(clock.fps())
5. QR 코드 인식
QR 인식은 내장 함수 find_qrcodes() 한 줄이면 된다. 인식된 payload를 화면에 그대로 출력한다.
import sensor,lcd,time
sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA)
sensor.set_vflip(1)
sensor.skip_frames(30)
lcd.init()
clock = time.clock()
while True:
clock.tick()
img = sensor.snapshot()
res = img.find_qrcodes()
if len(res) > 0:
img.draw_rectangle(res[0].rect())
img.draw_string(2,2, res[0].payload(), color=(255,255,255), scale=2)
print(res[0].payload())
lcd.display(img)
print(clock.fps())
6. 스냅샷을 SD 카드에 저장
물리 버튼(IO21)을 입력으로 등록하고, 버튼이 눌리면 스냅샷을 /sd/에 저장한다. 뒤에서 다룰 커스텀 모델 학습용 데이터셋을 모을 때 그대로 활용할 수 있는 패턴이다.
import sensor, image, time, lcd
from Maix import GPIO
from fpioa_manager import fm
fm.register(21, fm.fpioa.GPIO0)
btn = GPIO(GPIO.GPIO0, GPIO.IN)
lcd.init()
sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA)
sensor.set_vflip(1)
sensor.skip_frames(time=2000)
sensor.run(1)
cnt = 1
while True:
img = sensor.snapshot()
if btn.value() == 0:
img = sensor.snapshot()
fileName = "/sd/test_{:02d}.jpg".format(cnt) # 수정됨
img.save(fileName)
lcd.draw_string(50, 70, "Save snapshot to SDcard", lcd.WHITE)
print("Save snapshot to SDcard:", fileName)
cnt += 1
time.sleep(1)
lcd.display(img)
7. MaixHub로 커스텀 모델 학습 — YOLO2 온디바이스 추론
MaixHub는 이미지를 업로드하면 K210용 kmodel을 학습·변환해 주는 플랫폼이다. 학습된 모델 파일을 TF 카드에 넣고 아래 스크립트를 실행하면, KPU가 YOLO2 추론을 수행해 bird/cat을 실시간 인식한다. kpu.init_yolo2()의 threshold·NMS 값과 anchors가 모델 학습 시 값과 일치해야 한다는 점, 그리고 인식 결과를 UART로도 내보내 외부 시스템과 연동할 수 있게 한 구조에 주목하자.
MaixHub
maixhub.com
# generated by maixhub, tested on maixpy3 v0.4.8
# copy files to TF card and plug into board and power on
import sensor, image, lcd, time
import KPU as kpu
from machine import UART
import gc, sys
from fpioa_manager import fm
input_size = (224, 224)
labels = ['bird', 'cat']
anchors = [1.5, 1.34, 2.19, 3.25, 3.19, 2.47, 1.63, 2.19, 1.97, 2.0]
def lcd_show_except(e):
import uio
err_str = uio.StringIO()
sys.print_exception(e, err_str)
err_str = err_str.getvalue()
img = image.Image(size=input_size)
img.draw_string(0, 10, err_str, scale=1, color=(0xff,0x00,0x00))
lcd.display(img)
class Comm:
def __init__(self, uart):
self.uart = uart
def send_detect_result(self, objects, labels):
msg = ""
for obj in objects:
pos = obj.rect()
p = obj.value()
idx = obj.classid()
label = labels[idx]
msg += "{}:{}:{}:{}:{}:{:.2f}:{}, ".format(pos[0], pos[1], pos[2], pos[3], idx, p, label)
if msg:
msg = msg[:-2] + "\n"
self.uart.write(msg.encode())
def init_uart():
fm.register(10, fm.fpioa.UART1_TX, force=True)
fm.register(11, fm.fpioa.UART1_RX, force=True)
uart = UART(UART.UART1, 115200, 8, 0, 0, timeout=1000, read_buf_len=256)
return uart
def main(anchors, labels = None, model_addr="/sd/model-262409.kmodel", sensor_window=input_size, lcd_rotation=0, sensor_hmirror=False, sensor_vflip=True):
sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA)
sensor.set_windowing(sensor_window)
sensor.set_hmirror(sensor_hmirror)
sensor.set_vflip(sensor_vflip)
sensor.run(1)
lcd.init(type=1)
lcd.rotation(lcd_rotation)
lcd.clear(lcd.WHITE)
if not labels:
with open('labels.txt','r') as f:
exec(f.read())
if not labels:
print("no labels.txt")
img = image.Image(size=(320, 240))
img.draw_string(90, 110, "no labels.txt", color=(255, 0, 0), scale=2)
lcd.display(img)
return 1
try:
img = image.Image("startup.jpg")
lcd.display(img)
except Exception:
img = image.Image(size=(320, 240))
img.draw_string(90, 110, "loading model...", color=(255, 255, 255), scale=2)
lcd.display(img)
uart = init_uart()
comm = Comm(uart)
try:
task = None
task = kpu.load(model_addr)
kpu.init_yolo2(task, 0.5, 0.3, 5, anchors) # threshold:[0,1], nms_value: [0, 1]
while(True):
img = sensor.snapshot()
t = time.ticks_ms()
objects = kpu.run_yolo2(task, img)
t = time.ticks_ms() - t
if objects:
for obj in objects:
pos = obj.rect()
img.draw_rectangle(pos)
img.draw_string(pos[0], pos[1], "%s : %.2f" %(labels[obj.classid()], obj.value()), scale=2, color=(255, 0, 0))
comm.send_detect_result(objects, labels)
img.draw_string(0, 200, "t:%dms" %(t), scale=2, color=(255, 0, 0))
img.draw_string(0, 2, "Upgrade to MaixCAM to use YOLOv8", scale=1.2, color=(255, 0, 0))
img.draw_string(0, 30, "wiki.sipeed.com/maixcam", scale=1.2, color=(255, 0, 0))
lcd.display(img)
except Exception as e:
raise e
finally:
if not task is None:
kpu.deinit(task)
if __name__ == "__main__":
try:
# main(anchors = anchors, labels=labels, model_addr=0x300000, lcd_rotation=0)
main(anchors = anchors, labels=labels, model_addr="/sd/model-262409.kmodel")
except Exception as e:
sys.print_exception(e)
lcd_show_except(e)
finally:
gc.collect()
8. Node-RED 연동 — 시리얼 양방향 제어
여기서부터가 이 교육의 하이라이트다. 로우코드 도구 Node-RED로 PC 대시보드를 만들고, PEAK-i와 USB 시리얼로 양방향 통신한다.
Low-code programming for event-driven applications : Node-RED
Node-RED's goal is to enable anyone to build applications that collect, transform and visualize their data; building flows that can automate their world. Its low-code nature makes it accessible to users of any background, whether for home automation, indus
nodered.org
npm install -g node-red
node-red # http://127.0.0.1:1880 (대시보드는 /ui)
보드 쪽 스크립트는 두 방향을 모두 처리한다 — [방향 1] 물리 버튼 입력을 print()로 Node-RED에 알리고, [방향 2] Node-RED에서 내려온 문자("R"/"G"/"B"/"OFF")를 읽어 LED를 제어한다. PEAK-i의 USB 시리얼을 쓰려면 핀 4·5를 UART1 RX/TX로 강제 할당(force=True)해야 한다는 것이 핵심 포인트다.
from Maix import GPIO
from fpioa_manager import fm
from machine import UART
import time
# 1. 하드웨어 핀 등록 (교재 39p 기반 및 UART 핀 추가)
fm.register(9, fm.fpioa.GPIO0) # Red LED
fm.register(10, fm.fpioa.GPIO1) # Green LED
fm.register(11, fm.fpioa.GPIO2) # Blue LED
fm.register(18, fm.fpioa.GPIO3) # 물리 버튼 R (IO18)
fm.register(19, fm.fpioa.GPIO4) # 물리 버튼 G (IO19)
fm.register(20, fm.fpioa.GPIO5) # 물리 버튼 B (IO20)
fm.register(21, fm.fpioa.GPIO6) # 물리 버튼 Y (IO21)
# PEAK-i의 USB 시리얼 통신을 위한 RX/TX 핀 강제 할당 (중요)
fm.register(4, fm.fpioa.UART1_RX, force=True)
fm.register(5, fm.fpioa.UART1_TX, force=True)
# 2. GPIO 객체 초기화 (1: OFF, 0: ON)
LED_R = GPIO(GPIO.GPIO0, GPIO.OUT, value=1)
LED_G = GPIO(GPIO.GPIO1, GPIO.OUT, value=1)
LED_B = GPIO(GPIO.GPIO2, GPIO.OUT, value=1)
KEY_R = GPIO(GPIO.GPIO3, GPIO.IN)
KEY_G = GPIO(GPIO.GPIO4, GPIO.IN)
KEY_B = GPIO(GPIO.GPIO5, GPIO.IN)
KEY_OFF = GPIO(GPIO.GPIO6, GPIO.IN)
# 3. UART 초기화 (기본 USB 포트 통로 사용, 속도 115200)
uart = UART(UART.UART1, 115200, 8, 1, 0, timeout=1000, read_buf_len=4096)
def all_off():
LED_R.value(1); LED_G.value(1); LED_B.value(1)
print("Serial System Ready")
while True:
# [방향 1] 기기 물리 버튼 입력 -> Node-RED로 알림 전송
if KEY_R.value() == 0:
all_off(); LED_R.value(0)
print("BTN_RED")
time.sleep(0.3)
elif KEY_G.value() == 0:
all_off(); LED_G.value(0)
print("BTN_GREEN")
time.sleep(0.3)
elif KEY_B.value() == 0:
all_off(); LED_B.value(0)
print("BTN_BLUE")
time.sleep(0.3)
elif KEY_OFF.value() == 0:
all_off();
time.sleep(0.3)
# [방향 2] Node-RED 명령 수신 처리
if uart.any():
# 들어온 데이터를 읽고 문자열로 변환
data = uart.read().decode()
# 'in' 연산자를 사용하여 데이터에 해당 문자가 포함되어 있는지 확인 (오동작 방지)
if "R" in data:
all_off(); LED_R.value(0)
elif "G" in data:
all_off(); LED_G.value(0)
elif "B" in data:
all_off(); LED_B.value(0)
elif "OFF" in data:
all_off()
time.sleep(0.05)
통신이 불안정하다면 UART 파라미터를 점검한다. 위 버전은 UART(UART.UART1, 115200, 8, 1, 0)이었지만, 실습 환경에서는 패리티·스톱비트 조합을 8, 0, 1로 바꾼 아래 버전이 안정적으로 동작했다.
from Maix import GPIO
from fpioa_manager import fm
from machine import UART
import time
# 1. 하드웨어 핀 등록 (교재 39p 기반 및 UART 핀 추가)
fm.register(9, fm.fpioa.GPIO0) # Red LED
fm.register(10, fm.fpioa.GPIO1) # Green LED
fm.register(11, fm.fpioa.GPIO2) # Blue LED
fm.register(18, fm.fpioa.GPIO3) # 물리 버튼 R (IO18)
fm.register(19, fm.fpioa.GPIO4) # 물리 버튼 G (IO19)
fm.register(20, fm.fpioa.GPIO5) # 물리 버튼 B (IO20)
fm.register(21, fm.fpioa.GPIO6) # 물리 버튼 Y (IO21)
# PEAK-i의 USB 시리얼 통신을 위한 RX/TX 핀 강제 할당 (중요)
fm.register(4, fm.fpioa.UART1_RX, force=True)
fm.register(5, fm.fpioa.UART1_TX, force=True)
# 2. GPIO 객체 초기화 (1: OFF, 0: ON)
LED_R = GPIO(GPIO.GPIO0, GPIO.OUT, value=1)
LED_G = GPIO(GPIO.GPIO1, GPIO.OUT, value=1)
LED_B = GPIO(GPIO.GPIO2, GPIO.OUT, value=1)
KEY_R = GPIO(GPIO.GPIO3, GPIO.IN)
KEY_G = GPIO(GPIO.GPIO4, GPIO.IN)
KEY_B = GPIO(GPIO.GPIO5, GPIO.IN)
KEY_OFF = GPIO(GPIO.GPIO6, GPIO.IN)
# 3. UART 초기화 (기본 USB 포트 통로 사용, 속도 115200)
# uart = UART(UART.UART1, 115200, 8, 1, 0, timeout=1000, read_buf_len=4096)
uart = UART(UART.UART1, 115200, 8, 0, 1, timeout=1000, read_buf_len=4096)
def all_off():
LED_R.value(1); LED_G.value(1); LED_B.value(1)
print("Serial System Ready")
while True:
# [방향 1] 기기 물리 버튼 입력 -> Node-RED로 알림 전송
if KEY_R.value() == 0:
all_off(); LED_R.value(0)
print("BTN_RED")
time.sleep(0.3)
elif KEY_G.value() == 0:
all_off(); LED_G.value(0)
print("BTN_GREEN")
time.sleep(0.3)
elif KEY_B.value() == 0:
all_off(); LED_B.value(0)
print("BTN_BLUE")
time.sleep(0.3)
elif KEY_OFF.value() == 0:
all_off();
time.sleep(0.3)
# [방향 2] Node-RED 명령 수신 처리
if uart.any():
# 들어온 데이터를 읽고 문자열로 변환
data = uart.read().decode()
# 'in' 연산자를 사용하여 데이터에 해당 문자가 포함되어 있는지 확인 (오동작 방지)
if "R" in data:
all_off(); LED_R.value(0)
elif "G" in data:
all_off(); LED_G.value(0)
elif "B" in data:
all_off(); LED_B.value(0)
elif "OFF" in data:
all_off()
time.sleep(0.05)
9. 카메라 영상을 Node-RED 대시보드로 스트리밍
시리얼로 영상까지 보낼 수 있다. 프레임을 JPEG로 압축(quality가 높을수록 화질↑ 속도↓)하고 Base64로 인코딩한 뒤, START_IMG/END_IMG 태그로 감싸 전송한다. 전송 후 짧은 sleep이 없으면 버퍼가 터지므로 주의.
import sensor, image, time, lcd
import ubinascii
# 1. LCD 및 카메라 초기화
lcd.init()
sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA)
sensor.skip_frames(30)
sensor.set_vflip(True)
print("Camera System Ready")
while True:
img = sensor.snapshot()
lcd.display(img)
# 2. JPEG 압축 (속도 중요) >> quality 값이 높아질수록 화질 good + 속도 slow
img_buf = img.compress(quality=30)
# 3. Base64 변환
img_64 = ubinascii.b2a_base64(img_buf)
# 4. USB 시리얼로 전송 (핵심)
print("START_IMG")
print(img_64.decode()) # decode 필수
print("END_IMG")
time.sleep(0.2) # 없으면 데이터 터짐
Node-RED 쪽에서는 function 노드가 시리얼 스트림을 버퍼에 누적하다가, 시작/끝 태그 사이의 Base64를 잘라 data:image/jpeg;base64,... 형식으로 Dashboard 이미지 위젯에 넘긴다.
// ----------------------
// 설정
// ----------------------
let START_TAG = "START_IMG";
let END_TAG = "END_IMG";
// ----------------------
// 버퍼 누적
// ----------------------
let buffer = context.get("buffer") || "";
buffer += msg.payload.toString();
// ----------------------
// 이미지 추출 루프 (여러 프레임 대응)
// ----------------------
let start = buffer.indexOf(START_TAG);
let end = buffer.indexOf(END_TAG);
if (start !== -1 && end !== -1 && end > start) {
// 이미지 데이터만 추출
let base64 = buffer.substring(
start + START_TAG.length,
end
).replace(/\s/g, ""); // 줄바꿈 제거
// 사용한 데이터 제거 (다음 프레임 대비)
buffer = buffer.substring(end + END_TAG.length);
context.set("buffer", buffer);
// Dashboard 이미지 형식으로 변환
msg.payload = "data:image/jpeg;base64," + base64;
return msg;
}
// ----------------------
// 버퍼 저장
// ----------------------
context.set("buffer", buffer);
// ----------------------
// 아직 데이터 부족
// ----------------------
return null;
10. 실시간 객체 인식 대시보드 — 최종 통합
마지막 통합 예제는 20클래스 YOLO2 모델(20class.kmodel)로 객체를 인식하면서, 인식 결과 텍스트([TXT_START]~[TXT_END])와 프레임 이미지([IMG_START]~[IMG_END])를 각각 태그로 프레이밍해 시리얼로 내보낸다. 인식이 없어도 "No Detect"를 항상 출력해 대시보드가 멈춘 것처럼 보이지 않게 한 것, 이미지는 3프레임에 1번만 보내 대역폭을 아낀 것이 실전 요령이다.
import sensor, image, lcd, time
import KPU as kpu
import ubinascii
try:
print("BOOT START")
# -------------------------------
# 1. 초기화
# -------------------------------
lcd.init()
sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA)
sensor.set_vflip(1)
sensor.set_hmirror(1)
sensor.skip_frames(30)
clock = time.clock()
print("Camera OK")
# -------------------------------
# 2. YOLO 설정
# -------------------------------
classes = ['aeroplane','bicycle','bird','boat','bottle','bus','car',
'cat','chair','cow','diningtable','dog','horse','motorbike',
'person','pottedplant','sheep','sofa','train','tvmonitor']
try:
task = kpu.load("/sd/20class.kmodel")
except:
task = kpu.load(0x500000)
anchor = (1.889, 2.5245, 2.9465, 3.94056, 3.99987,
5.3658, 5.155437, 6.92275, 6.718375, 9.01025)
kpu.init_yolo2(task, 0.5, 0.3, 5, anchor)
print("YOLO INIT OK")
frame_count = 0
# -------------------------------
# 3. 메인 루프
# -------------------------------
while True:
clock.tick()
frame_count += 1
img = sensor.snapshot()
# YOLO 실행
code = kpu.run_yolo2(task, img)
detect_results = []
if code:
for i in code:
img.draw_rectangle(i.rect(), color=(255, 0, 0))
label = classes[i.classid()]
confidence = i.value()
img.draw_string(i.x(), i.y(), label, color=(255,0,0), scale=1)
img.draw_string(i.x(), i.y()+10, "%.2f" % confidence, color=(255,0,0), scale=1)
detect_results.append("{}:{:.2f}".format(label, confidence))
lcd.display(img)
# -------------------------------
# 🔥 핵심: 텍스트 항상 출력
# -------------------------------
print("[TXT_START]")
if detect_results:
print(",".join(detect_results))
else:
print("No Detect") # 👈 이거 때문에 이제 항상 뜸
print("[TXT_END]")
# -------------------------------
# 이미지 전송
# -------------------------------
if frame_count % 3 == 0:
try:
img_buf = img.compress(quality=10)
img_64 = ubinascii.b2a_base64(img_buf)
print("[IMG_START]")
print(img_64.decode())
print("[IMG_END]")
except:
print("IMG ERROR")
time.sleep(0.5)
except Exception as e:
print("FATAL ERROR:", e)
Node-RED에서는 두 개의 function 노드로 파싱한다 — 태그 사이의 텍스트를 잘라내는 함수와, "cat:0.85" 형태를 이름/점수 객체로 분리하는 함수다.
[결과값 가져오기 함수]
let buffer = context.get("buffer") || "";
buffer += msg.payload.toString();
let start = buffer.indexOf("[TXT_START]");
let end = buffer.indexOf("[TXT_END]");
if (start !== -1 && end !== -1 && end > start) {
let text = buffer.substring(start + 11, end).trim();
buffer = buffer.substring(end + 9);
context.set("buffer", buffer);
return { payload: text };
}
context.set("buffer", buffer);
return null;
[name-score split 함수]
let text = msg.payload;
// 예외 처리
if (!text || text === "No Detect") {
msg.payload = {
name: "No Detect",
score: "-"
};
return msg;
}
// 첫 번째 객체만 사용
let first = text.split(",")[0];
let parts = first.split(":");
let name = parts[0];
let score = Math.round(parseFloat(parts[1]) * 100) + "%";
//payload를 객체로 변경
msg.payload = {
name: name,
score: score
};
return msg;
11. 마치며
LED 한 개에서 시작해 실시간 객체 인식 대시보드까지 — 이 과정의 진짜 교훈은 온디바이스 추론과 외부 시스템 연동은 세트라는 것이다. K210 같은 저가 보드도 KPU 덕분에 추론은 충분히 해내고, 결과를 시리얼 프로토콜(태그 프레이밍)로 내보내는 순간 Node-RED든 무엇이든 붙일 수 있다. 데이터 수집(6장) → 학습(7장) → 추론·연동(8~10장)의 사이클을 한 번 완주해 보면, 규모가 커져도 구조는 같다.
'푸닥거리' 카테고리의 다른 글
| 피지컬AI시대를 위한 산업용로봇 어플리케이션 이해 및 활용 (0) | 2026.04.26 |
|---|---|
| LLM 기반 자율 에이전트(Agent) 개발 및 워크플로우 자동화 (0) | 2026.04.19 |
| Google ADK 기반 AI Agent 개발 (0) | 2026.03.29 |
| 바이브 코딩을 넘어 Github Spec Kit으로 구현하는 SDD(Spec Driven Development) and hermes, harness (0) | 2026.01.25 |
| Spring AI 임베딩과 RAG 구현 (0) | 2026.01.11 |
댓글