Wego LIMO는 카메라 영상과 딥러닝 모델을 이용해 주행 경로를 판단하거나 도로 위의 표지판과 신호등을 인식할 수 있습니다.
이번 글에서는 Wego LIMO에서 사용하는 딥러닝 모델을 직접 학습하고, 학습이 완료된 모델을 NVIDIA TensorRT 엔진으로 변환하는 과정을 설명합니다.
본 내용은 2026년 3월 3일 이후 배포된 최신 딥러닝 패키지를 기준으로 작성했습니다. 이전 버전의 Wego LIMO 소프트웨어를 사용하고 있다면 먼저 패키지를 업데이트한 뒤 진행하는 것이 좋습니다.
1. Wego LIMO에서 사용하는 딥러닝 모델
Wego LIMO의 딥러닝 기능은 목적에 따라 크게 2가지로 나눌 수 있습니다.
1) TensorFlow 주행 방향 모델
TensorFlow 기반 모델은 카메라에서 입력된 전체 이미지를 분석하고, 차량이 따라가야 할 주행 방향을 좌표 형태로 출력합니다.
예를 들어 차선이나 트랙의 형태를 분석한 뒤 다음과 같은 값을 예측할 수 있습니다.
- 차량과 가까운 지점의 주행 목표 좌표
- 차량에서 먼 지점의 주행 목표 좌표
출력된 좌표는 조향 제어에 사용됩니다. 따라서 이 모델은 객체의 종류를 분류하는 모델이라기보다 이미지에서 주행 경로를 직접 추정하는 회귀 모델에 가깝습니다.
2) YOLO 객체 인식 모델
YOLO 모델은 이미지 안에 존재하는 여러 객체의 위치와 종류를 동시에 탐지합니다.
Wego LIMO에서는 다음과 같은 객체를 인식하도록 학습할 수 있습니다.
- 가속 표지판
- 감속 표지판
- 우회전 표지판
- 적색 신호등
- 황색 신호등
- 녹색 신호등
- 보행자
YOLO 모델은 각 객체에 대해 클래스 이름, 신뢰도, 바운딩 박스 위치를 반환합니다. 따라서 표지판 인식, 신호등 판단, 보행자 감지와 같은 기능에 적합합니다.
2. 학습 전 준비 사항
딥러닝 학습은 Google Colab을 이용하면 별도의 고성능 GPU 컴퓨터 없이도 진행할 수 있습니다.
학습 전에 다음 항목을 준비합니다.
- Wego LIMO에서 수집한 데이터셋
- TensorFlow 또는 YOLO 학습용 Jupyter Notebook
- Google 계정
- Google Drive 저장 공간
- 안정적인 인터넷 연결
Wego LIMO에서 데이터셋은 일반적으로 다음 위치에 생성됩니다.
/home/wego/wego_ws/src/dataset

학습용 Notebook 파일은 다음 경로에서 확인할 수 있습니다.
/home/wego/dl_dep/script

TensorFlow 학습에는 다음 파일을 사용합니다.
train_tensorflow_track_detection_on_custom_dataset.ipynb

YOLO 학습에는 다음 파일을 사용합니다.
train_yolo11n_object_detection_on_custom_dataset.ipynb

3. 데이터셋 압축하기
Google Drive에 업로드하기 전에 데이터셋 폴더를 ZIP 파일로 압축합니다.
터미널에서 다음 명령을 실행합니다.
cd /home/wego/wego_ws/src
zip -r dataset.zip dataset


압축이 완료되면 현재 디렉터리에 dataset.zip 파일이 생성됩니다.
다음 2개 파일을 Google Drive의 동일한 폴더에 업로드하면 관리가 편리합니다.
dataset.zip- 학습에 사용할
*.ipynb파일
데이터셋의 이미지와 라벨이 정상적으로 생성되었는지도 확인해야 합니다. 손상된 이미지, 빈 라벨 파일, 잘못된 클래스 번호가 포함되어 있으면 학습 성능이 크게 떨어질 수 있습니다.
4. TensorFlow 주행 방향 모델 학습
TensorFlow 모델은 카메라 영상으로부터 주행 목표 좌표를 예측하도록 학습합니다.
1) Google Colab이란?
Google Colab의 정식 명칭은 Google Colaboratory입니다.
쉽게 설명하면 다음과 같습니다.
인터넷 브라우저에서 실행되는 Python 개발환경입니다.
일반적인 Python 개발에서는 PC에 Python을 설치하고 필요한 라이브러리를 설치해야 합니다.
예를 들어 다음과 같은 작업이 필요합니다.
- Python 설치
- Visual Studio Code 또는 PyCharm 설치
- 가상환경 구성
- NumPy 설치
- Pandas 설치
- TensorFlow 또는 PyTorch 설치
- CUDA 설치
- GPU 드라이버 설정
딥러닝 환경까지 구성하려면 초보자에게는 상당히 복잡할 수 있습니다.
Google Colab을 이용하면 이런 환경 설정의 상당 부분을 생략할 수 있습니다.
웹브라우저에서 Colab을 열고 바로 Python 코드를 실행하면 됩니다.
2) Google Colab의 장점
Google Colab의 대표적인 장점은 다음과 같습니다.
- 별도의 Python 설치가 필요하지 않습니다.
- 웹브라우저만 있으면 사용할 수 있습니다.
- Jupyter Notebook 방식으로 코드를 실행할 수 있습니다.
- GPU와 TPU를 사용할 수 있습니다.
- Google Drive와 연동할 수 있습니다.
- 다른 사람과 노트북을 공유할 수 있습니다.
- GitHub에 있는 Jupyter Notebook을 열 수 있습니다.
- TensorFlow, PyTorch, NumPy, Pandas 등 다양한 패키지가 기본적으로 설치되어 있습니다.
- Windows, macOS, Linux 환경과 관계없이 사용할 수 있습니다.
- PC 성능이 낮아도 클라우드 컴퓨팅 환경을 활용할 수 있습니다.
특히 딥러닝을 처음 공부하는 경우 비싼 GPU를 바로 구입하지 않고도 실습 환경을 구성할 수 있다는 장점이 있습니다.
다만 무료 Colab의 GPU, TPU, 메모리 및 실행시간은 고정적으로 보장되는 자원이 아닙니다. Google은 사용량 제한과 제공되는 GPU 종류 등이 상황에 따라 달라질 수 있다고 안내하고 있습니다.
3) Google Colab 시작하기
Google 계정이 있다면 바로 사용할 수 있습니다.
Google 검색창에서 다음을 검색합니다.
Google Colab
Google Colab 사이트에 접속한 후 새 노트북을 생성합니다.
일반적으로 다음과 같은 방법으로 시작할 수 있습니다.
- Google Colab에 접속합니다.
- Google 계정으로 로그인합니다.
- 새 노트북을 선택합니다.
- 새로운
.ipynb파일이 생성됩니다. - 코드 셀에 Python 코드를 입력합니다.
- 실행 버튼을 눌러 코드를 실행합니다.

새 노트 버튼을 클릭합니다.
Colab에서 사용하는 기본 파일 형식은 다음과 같습니다.
.ipynb
이는 Jupyter Notebook에서 사용하는 파일 형식입니다.
4) Colab 화면 구성 이해하기
Colab 화면은 크게 다음 영역으로 나눌 수 있습니다.
- 상단 메뉴
- 코드 셀
- 텍스트 셀
- 왼쪽 사이드바
- 파일 탐색기
- 변수 및 목차 영역
- 런타임 연결 상태 영역

가장 많이 사용하는 것은 코드 셀입니다.
예를 들어 다음 코드를 입력합니다.
print("Hello Google Colab")
코드 셀 왼쪽의 실행 버튼을 누르면 결과가 출력됩니다.
Hello Google Colab

이렇게 하나의 코드 블록 단위로 프로그램을 실행하는 것이 Notebook 환경의 특징입니다.
5) Google Drive 연결하기
Google Colab Notebook을 실행한 뒤 다음 코드를 이용해 Google Drive를 마운트합니다.
from google.colab import drive
drive.mount('/content/drive')
실행하면 Google 계정 인증 화면이 나타납니다. 인증이 완료되면 /content/drive/MyDrive 경로를 통해 Drive 파일에 접근할 수 있습니다.





6) 데이터셋 압축 해제하기
Google Drive에 업로드한 dataset.zip 파일을 Colab 작업 공간에 압축 해제합니다.
!unzip -o /content/drive/MyDrive/dataset.zip -d /content

압축이 정상적으로 해제되었는지 확인합니다.
!find /content/dataset -maxdepth 2 -type d

주행 방향 학습용 이미지는 일반적으로 다음 경로에서 불러옵니다.
/content/dataset/dl_images
Google Drive에서 별도의 폴더를 사용했다면 명령의 경로를 실제 위치에 맞게 수정해야 합니다.
7) TensorFlow 학습 환경 구성하기
라이브러리 버전 충돌을 줄이기 위해 Colab에 Conda 환경을 설치합니다.
%pip install -q --no-cache-dir \
"https://github.com/conda-incubator/condacolab/archive/main.zip"
import condacolab
condacolab.install(
python_version="3.10",
# Conda/Pixi 단계에서 NumPy를 먼저 고정
dependencies={
"numpy": "==1.26.4",
},
# PyPI 패키지들도 전부 버전 고정
pypi_dependencies={
"tensorflow": "==2.16.1",
"ml-dtypes": "==0.3.2",
"opencv-python": "==4.11.0.86",
}
)

Condacolab 설치 과정에서 런타임이 자동으로 재시작될 수 있습니다. 오류가 아니라 정상적인 과정입니다. 재시작 후 다음 셀을 실행합니다.
import sys
print(sys.version)

학습에 필요한 주요 패키지를 설치합니다.
!pip uninstall -y tensorflow jax tensorstore thinc grpcio-status ml_dtypes dopamine-rl flax orbax-checkpoint optax chex spacy protobuf numpy
!pip install tensorflow==2.20.0 # Install a supported tensorflow version
!pip install ml_dtypes==0.3.2 # Install compatible ml_dtypes
!pip uninstall -y ml_dtypes numpy
!pip install ml_dtypes==0.3.2 numpy==1.26.4 # Install compatible ml_dtypes and numpy
!pip install jax==0.4.23 # Make sure jax is installed
!pip install tensorstore==0.1.45
!pip install thinc==8.3.4
!pip install spacy<4
!pip install grpcio-status
!pip install protobuf #Install protobuf without version constraint
!pip install jaxlib==0.4.23


설치가 끝나면 런타임을 한 번 재시작하고, 실제 버전을 확인하는 것이 안전합니다.
import sys
import numpy as np
import tensorflow as tf
print("Python:", sys.version)
print("TensorFlow:", tf.__version__)
print("NumPy:", np.__version__)
print("GPU:", tf.config.list_physical_devices('GPU'))

위의 결과처럼 에러가 발생한 겨우 아래의 설치과정을 반복합니다.
import importlib.metadata as md
for p in ["tensorflow", "numpy", "ml-dtypes", "jax", "jaxlib", "keras"]:
try:
print(f"{p:12} : {md.version(p)}")
except:
print(f"{p:12} : not installed")
%pip uninstall -y jax jaxlib

%pip install --force-reinstall --no-cache-dir \
"numpy==2.2.6" \
"ml-dtypes==0.6.0"

충돌을 확인합니다.
%pip check

특별한 충돌이 없으면 세션을 다시 실행하고 아래의 코드를 싱행하여 현재 설치환경을 확인합니다.
import sys
import numpy as np
print("Python:", sys.version)
print("Executable:", sys.executable)
print("NumPy:", np.__version__)
print("NumPy path:", np.__file__)
import ml_dtypes
print("ml_dtypes:", ml_dtypes.__version__)
print("ml_dtypes path:", ml_dtypes.__file__)
import tensorflow as tf
print("TensorFlow:", tf.__version__)
print(tf.constant([1.0, 2.0, 3.0]))

import sys
import importlib.metadata as md
import importlib.util
print("Python exe:")
print(sys.executable)
print("\nNumPy:")
print(md.version("numpy"))
print(importlib.util.find_spec("numpy").origin)
print("\nml_dtypes:")
print(md.version("ml-dtypes"))
print(importlib.util.find_spec("ml_dtypes").origin)
print("\nTensorFlow:")
print(md.version("tensorflow"))
print(importlib.util.find_spec("tensorflow").origin)

런타임을 재 시작하여 아래의 내용을 실행합니다.
import sys
import numpy as np
import tensorflow as tf
import pandas as pd
import ml_dtypes
print("Python:", sys.version)
print("NumPy:", np.__version__)
print("TensorFlow:", tf.__version__)
print("Pandas:", pd.__version__)
print("ml_dtypes:", ml_dtypes.__version__)

초기 실행한 환경 정보 확인을 다시 실행합니다.
import sys
import numpy as np
import tensorflow as tf
print("Python:", sys.version)
print("TensorFlow:", tf.__version__)
print("NumPy:", np.__version__)
print("GPU:", tf.config.list_physical_devices('GPU'))

정상적인 출력을 확인할 수 있습니다.
GPU 목록이 비어 있다면 Colab 메뉴에서 다음 항목을 확인합니다.
런타임 → 런타임 유형 변경 → 하드웨어 가속기 → GPU

8) 학습 라이브러리 불러오기
TensorFlow 모델 생성과 이미지 처리를 위해 필요한 모듈을 불러옵니다.
import glob
import os
import random
import time
import cv2
import numpy as np
import tensorflow as tf
from tqdm import tqdm
from tensorflow import keras
from tensorflow.keras.callbacks import (
EarlyStopping,
LearningRateScheduler,
ModelCheckpoint
)
from tensorflow.keras.layers import (
Add,
BatchNormalization,
Conv2D,
Dense,
Dropout,
Flatten,
Input,
MaxPooling2D
)
from tensorflow.keras.models import Model
from tensorflow.keras.optimizers import Adam

재현 가능한 실험을 위해 난수 시드를 고정할 수 있습니다.
SEED = 42
random.seed(SEED)
np.random.seed(SEED)
tf.random.set_seed(SEED)
9) TensorFlow 데이터셋 불러오기
학습 이미지 목록을 검색합니다.
file_list = glob.glob('/content/dataset/dl_images/*.jpg')
print("이미지 개수:", len(file_list))
if file_list:
print("첫 번째 이미지:", file_list[0])
print("마지막 이미지:", file_list[-1])

이미지 개수가 0이라면 압축 해제 위치나 폴더 이름이 잘못된 것입니다.
이미지 수가 너무 적으면 모델이 특정 트랙이나 조명 환경만 기억하는 과적합이 발생할 수 있습니다. 가능하면 직선, 좌회전, 우회전, 밝은 구간, 어두운 구간 등 다양한 상황에서 데이터를 수집해야 합니다.
10) 이미지와 좌표 라벨 전처리
Wego LIMO의 주행 데이터는 파일 이름에 목표 좌표가 포함되는 방식으로 저장될 수 있습니다. 실제 파일 이름 규칙은 설치된 패키지와 데이터 수집 노드의 설정에 따라 확인해야 합니다.
다음 예시는 이미지 하단 영역을 잘라 사용하고, 좌표를 이미지 너비 기준으로 정규화하는 방식입니다.
images = []
labels = []
invalid_files = []
for file_path in tqdm(file_list):
try:
# 예:
# /content/dataset/dl_images/img_2026-08-16_14-55-49-298198.jpg
filename = os.path.basename(file_path)
# img_2026-08-16_14-55-49-298198.jpg
stem = os.path.splitext(filename)[0]
# img_2026-08-16_14-55-49-298198
parts = stem.split('_')
# ['img', '2026-08-16', '14-55-49-298198']
if len(parts) != 3 or parts[0] != 'img':
raise ValueError(
f"잘못된 파일명 형식: {filename}"
)
# 날짜
date_str = parts[1]
# '2026-08-16'
# 좌표 + 시간 부분
value_parts = parts[2].split('-')
# ['14', '55', '49', '298198']
if len(value_parts) != 4:
raise ValueError(
f"좌표/시간 형식 오류: {parts[2]}"
)
# 좌표
x1 = float(value_parts[0]) # 14
x2 = float(value_parts[1]) # 55
# 디버깅
print(
f"{filename} -> "
f"x1={x1}, x2={x2}"
)
# 이미지 읽기
image = cv2.imread(file_path)
if image is None:
raise ValueError("이미지를 읽을 수 없습니다.")
if image.shape[0] < 300 or image.shape[1] < 300:
raise ValueError(
f"이미지 크기가 부족합니다: {image.shape}"
)
# Crop
cropped = image[190:300, 0:300]
# BGR -> RGB
cropped = cv2.cvtColor(
cropped,
cv2.COLOR_BGR2RGB
)
# 정규화
cropped = cropped.astype(np.float32) / 255.0
images.append(cropped)
# 좌표 + 시간
labels.append([
x1,
x2
])
except Exception as error:
invalid_files.append(
(file_path, str(error))
)
print(
os.path.basename(file_path),
"->",
error
)
images = np.asarray(
images,
dtype=np.float32
)
labels = np.asarray(
labels,
dtype=np.float32
)
print("\n입력 이미지:", images.shape)
print("라벨:", labels.shape)
print("제외된 파일:", len(invalid_files))
if len(labels) > 0:
print("첫 번째 라벨:", labels[0])


정상적으로 전처리되었다면 입력 배열의 형태는 대략 다음과 같습니다.
이미지: 데이터 개수 × 110 × 300 × 3
라벨: 데이터 개수 × 2
학습 전에 좌표 범위를 확인합니다.
print("라벨 최솟값:", labels.min(axis=0))
print("라벨 최댓값:", labels.max(axis=0))
print("라벨 평균값:", labels.mean(axis=0))

일부 좌표가 비정상적으로 크거나 작은 경우 데이터 수집 오류일 가능성이 있습니다.
11) TensorFlow 모델 구성하기
주행 방향 모델은 합성곱 계층으로 이미지 특징을 추출하고, 마지막 Dense 계층에서 2개의 좌표를 출력하도록 구성할 수 있습니다.
input_layer = Input(shape=(110, 300, 3), name='camera_image')
x = Conv2D(
filters=16,
kernel_size=(3, 3),
strides=(2, 2),
padding='same',
activation='swish'
)(input_layer)
x = BatchNormalization()(x)
x = MaxPooling2D(pool_size=(3, 3), strides=(2, 2))(x)
x = Conv2D(
filters=32,
kernel_size=(3, 3),
padding='same',
activation='swish'
)(x)
x = BatchNormalization()(x)
x = MaxPooling2D(pool_size=(2, 2))(x)
x = Conv2D(
filters=64,
kernel_size=(3, 3),
padding='same',
activation='swish'
)(x)
x = BatchNormalization()(x)
x = MaxPooling2D(pool_size=(2, 2))(x)
x = Conv2D(
filters=128,
kernel_size=(3, 3),
padding='same',
activation='swish'
)(x)
x = BatchNormalization()(x)
x = Flatten()(x)
x = Dense(128, activation='swish')(x)
x = Dropout(0.3)(x)
x = Dense(64, activation='swish')(x)
output_layer = Dense(
2,
activation='tanh',
name='target_points'
)(x)
model = Model(
inputs=input_layer,
outputs=output_layer,
name='wego_limo_track_model'
)
model.summary()


tanh 활성화 함수는 출력값을 -1에서 1 사이로 제한합니다.
라벨을 0에서 1 사이로만 정규화한 경우에는 출력 계층에 sigmoid를 사용하는 방법도 있습니다. 기존 Wego LIMO 실행 노드가 어떤 출력 범위를 기대하는지 확인한 뒤 학습 모델과 추론 코드를 동일하게 맞춰야 합니다.
12) TensorFlow 모델 학습 및 저장
학습률 조정, 조기 종료, 최적 모델 저장 기능을 설정합니다.
def scheduler(epoch, learning_rate):
if epoch < 100:
return learning_rate
return learning_rate * tf.math.exp(-0.1)
조기 종료는 검증 손실이 일정 기간 개선되지 않을 때 학습을 중단합니다.
early_stopping = EarlyStopping(
monitor='val_loss',
mode='min',
patience=15,
min_delta=1e-4,
restore_best_weights=True,
verbose=1
)
가장 성능이 좋은 모델을 Google Drive에 저장합니다.
checkpoint = ModelCheckpoint(
filepath='/content/drive/MyDrive/dl_drive.h5',
monitor='val_loss',
mode='min',
save_best_only=True,
verbose=1
)
학습률 스케줄러를 등록합니다.
lr_scheduler = LearningRateScheduler(
scheduler,
verbose=1
)

모델을 컴파일합니다.
optimizer = Adam(learning_rate=0.001)
model.compile(
optimizer=optimizer,
loss='mae',
metrics=['mae']
)
print("초기 학습률:", float(model.optimizer.learning_rate.numpy()))

최대 1000에포크 동안 학습합니다.
history = model.fit(
x=images,
y=labels,
epochs=1000,
batch_size=32,
validation_split=0.1,
shuffle=True,
callbacks=[
checkpoint,
early_stopping,
lr_scheduler
]
)


에포크를 1000으로 설정해도 조기 종료 기능이 적용되어 있다면 검증 성능이 더 이상 좋아지지 않는 시점에 학습이 자동으로 종료됩니다.
학습 결과는 다음 경로에 저장됩니다.
/content/drive/MyDrive/dl_drive.h5

13) TensorFlow 학습 결과 확인하기
학습 손실과 검증 손실을 그래프로 확인합니다.
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 5))
plt.plot(history.history['loss'], label='train_loss')
plt.plot(history.history['val_loss'], label='val_loss')
plt.xlabel('Epoch')
plt.ylabel('MAE Loss')
plt.legend()
plt.grid()
plt.show()

다음과 같은 현상이 나타난다면 데이터나 학습 설정을 점검해야 합니다.
- 학습 손실과 검증 손실이 모두 높은 경우
데이터 품질이 낮거나 모델이 충분히 학습되지 않은 상태일 수 있습니다. - 학습 손실만 계속 감소하고 검증 손실이 증가하는 경우
과적합 가능성이 큽니다. 데이터를 추가하거나 Dropout, 데이터 증강을 적용해야 합니다. - 손실값이 갑자기
NaN으로 바뀌는 경우
라벨에 잘못된 값이 있거나 학습률이 지나치게 높을 수 있습니다. - 예측 좌표가 한쪽으로만 치우치는 경우
좌회전 또는 우회전 데이터가 부족한 불균형 데이터셋일 수 있습니다.
저장된 모델을 다시 불러오는 테스트도 진행합니다.
loaded_model = tf.keras.models.load_model(
'/content/drive/MyDrive/dl_drive.h5',
compile=False
)
sample_output = loaded_model.predict(
images[:1],
verbose=0
)
print("예측 결과:", sample_output)
