1. 문제 정의 및 목표 설정
경기장 내 등장하는 인원은 주로 두 팀(파란색, 하얀색)과 심판으로 구성되며, 이 중 골키퍼는 제외하고 분류 대상으로 삼는다. 본 작업의 목적은 영상에서 각 인물을 정확히 식별하여 2차원 시각화 패널에 표시하는 것이다.
- 분류 대상:
- 파란 팀 선수
- 하얀 팀 선수
- 심판
2. 학습 방식 선택 및 구현 전략
2.1 Keras 기반 딥러닝 모델 설계
이전 단계에서 수집한 이미지 데이터를 바탕으로 자체 설계된 신경망을 학습시키는 방법을 우선적으로 적용한다.
2.1.1 환경 및 라이브러리 준비
pip install tensorflow==2.0
pip install opencv-python numpy scikit-learn matplotlib
pip install keras h5py onnx
2.1.2 데이터 로드 및 전처리
각 클래스별 이미지를 불러와 정규화하고, 레이블을 원-핫 인코딩 형태로 변환한다.
BLUE, WHITE, REFEREE = 0, 1, 2
def load_dataset():
data, labels = [], []
for path, label in [
("./blue/*.jpg", BLUE),
("./white/*.jpg", WHITE),
("./referee/*.jpg", REFEREE)
]:
files = glob.glob(path)
for f in files:
img = cv2.imread(f)
img = cv2.resize(img, (80, 80))
data.append(img)
lbl = np.zeros(3)
lbl[label] = 1
labels.append(lbl)
return np.array(data), np.array(labels)
2.1.3 모델 아키텍처 설계
간단한 인셉션 모듈을 사용해 복합적인 특징 추출 능력을 갖춘 네트워크를 구현한다.
def create_inception_block(x, filters):
conv1x1 = layers.Conv2D(filters[0], (1,1), activation='relu', padding='same')(x)
conv3x3 = layers.Conv2D(filters[1], (3,3), activation='relu', padding='same')(x)
conv5x5 = layers.Conv2D(filters[2], (5,5), activation='relu', padding='same')(x)
pool = layers.MaxPooling2D((3,3), strides=(1,1), padding='same')(x)
return layers.Concatenate(axis=-1)([conv1x1, conv3x3, conv5x5, pool])
input_layer = layers.Input(shape=(80, 80, 3))
layer = layers.Conv2D(20, 5, padding='same', activation='relu')(input_layer)
layer = create_inception_block(layer, [2, 4, 6])
layer = create_inception_block(layer, [4, 8, 12])
layer = layers.MaxPooling2D(pool_size=(2,2))(layer)
layer = layers.Flatten()(layer)
layer = layers.Dense(256, kernel_initializer='he_normal', kernel_regularizer=regularizers.l1(0.01))(layer)
layer = layers.ReLU()(layer)
layer = layers.Dropout(0.5)(layer)
output = layers.Dense(3, activation='softmax')(layer)
model = models.Model(input_layer, output)
2.1.4 학습 및 평가
이미지 증강을 통해 일반화 성능을 향상시키고, 검증 세트를 활용해 학습 진행 상황을 시각화한다.
datagen = ImageDataGenerator(
rotation_range=30,
width_shift_range=0.2,
height_shift_range=0.2,
zoom_range=0.2,
fill_mode='nearest'
)
model.compile(optimizer='adam',
loss='categorical_crossentropy',
metrics=['accuracy'])
history = model.fit(
datagen.flow(X_train, Y_train, batch_size=128),
validation_data=(X_val, Y_val),
epochs=50,
steps_per_epoch=len(Y_train)//128
)
2.1.5 모델 저장
학습 완료 후 모델 구조와 가중치를 파일로 저장한다.
model.save_weights("classifier.h5")
model_json = model.to_json()
with open("classifier.json", "w") as f:
f.write(model_json)
2.2 H5 모델 ONNX 형식 변환
Keras 모델을 ONNX 포맷으로 변환하여 다양한 플랫폼에서 추론 가능하게 한다.
import tf2onnx
import tensorflow as tf
model = tf.keras.models.load_model("classifier.h5")
model.save("temp_model")
# ONNX 변환
tf2onnx.convert.from_keras(model, output_path="classifier.onnx")
3. YOLOv7 기반 모델 학습 준비
더 정교한 객체 감지 성능을 위해 YOLOv7를 활용한 분류 모델도 병행 개발한다.
3.1 설정 파일 수정
predefined_classes.txt:BLUE,WHITE,REFEREEcoco.yaml:nc: 3,names: ['BLUE', 'WHITE', 'REFEREE']yolov7.yaml:nc: 3
3.2 데이터 레이블링 지침
모든 프레임에서 출현하는 선수와 심판을 포함해 정확하게 박스를 그려야 하며, 일부 생략 시 성능 저하 발생 가능.
4. 실시간 시스템 통합
4.1 추론 인터페이스 정의
public interface IClassifier
{
bool UseOnnx { get; set; }
void LoadModel();
NDarray Predict(NDarray? inputs);
}
4.2 ONNX 기반 추론 구현
public class OnnxClassifier : IClassifier
{
private InferenceSession session;
public bool UseOnnx { get; set; } = true;
public void LoadModel()
{
session = new InferenceSession(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assets/classifier.onnx"));
}
public NDarray Predict(NDarray? inputs)
{
var inputTensor = inputs?.ToDenseTensor();
var input = NamedOnnxValue.CreateFromTensor<float>("input_11", inputTensor);
var results = session.Run(new List<NamedOnnxValue> { input });
var output = results.Last().AsEnumerable<float>().ToArray();
var resultArray = np.array(output).reshape(inputs!.shape.Dimensions[0], 3);
return np.argmax(resultArray, axis: 1);
}
}
4.3 전체 워크플로우 예시
먼저 경기자 위치를 감지한 다음, 감지된 영역을 자르고 분류 모델에 전달한다.
[Fact]
public void TestPlayerClassification()
{
var detector = new YoloDetector();
var classifier = new OnnxClassifier();
using var frame = LoadImage("field_2.jpg");
var predictions = detector.Detect(frame);
NDarray rois = null;
foreach (var pred in predictions)
{
var roi = frame.GetSubRect(pred.BoundingBox).Resize(80, 80);
var ndarray = roi.ToNDarray();
rois = rois == null ? ndarray : np.concatenate(rois, ndarray);
}
var labels = classifier.Predict(rois);
Assert.True(labels.item<int>(0) >= 0);
}