반응형

이 과정을 진행한지 한참 지났지만 리마인드를 위해서 정리해본다.

1. 객체 인식 개요

영상인식은 크게 영상 식별(Image classification) 그리고 영상 인식(Image detection) 또는 객체 인식(Object detection)으로 분류되는데,

영상 식별은 특정한 이미지가 개에 가까운지 또는 고양이에 가까운지를 판단하는 것이라면

영상 인식은 이미지 안에 개도 있고 고양이가 있다면 고양이는 어디에 있는지, 개는 어디에 있는지를 판단하는 것이라고 할 수 있다.

어떻게 보면 영상 인식은 영상 식별의 하위 개념으로 볼 수 있는데

rCNN의 원리 또한 입력 이미지를 특정한 알고리즘으로 여러가지 영역(Region)으로 분류한 다음 CNN(합성곱 뉴럴넷)을 적용하여 부분 부분의 이미지를 식별하는 것과 유사하다. 

다만 이미지를 잘개 쪼개고 각각에 대해서 CNN알고리즘을 적용하다보면 매우 높은 연산 량이 필요하므로 실시간 처리에 적용하기에는 매우 불리하다. 

2. YOLO

실제로 인간의 시각은 들어온 영상에서 영역을 자르고 해당 영역을 식별하는 순서로 처리하지 않는다.

대신 한번에 들어온 전체 이미지에서 특징점을 얻어내고 기존에 학습된 특징점과 비교하여 확률이 높은 영역을 찾는 방식으로 동작한다. 

Joseph Redmon, Santosh Divvala, Ross Girshick, Ali Farhadi 은 인간 시각의 이러한 특징에 착안하여 영상 인식을 회귀(Regression)문제로 접근하였다. 그래서 제안된 방법이 YOLO(You Only Look Once)이다.

YOLO는 단일 신경망에서 한번의 예측 단계로 모든 객체를 동시에 탐지하므로 매우 빠르게 동작한다. 

논문의 원문은 다음에서 찾을 수 있다.

https://arxiv.org/pdf/1506.02640

그리고 이 논문에서 제안하는 알고리즘은 darknet이라는 이름으로 오픈소스로 공개되었다.

 

보다 자세한 설명은 다음 링크를 참조하기 바란다.

https://www.datacamp.com/blog/yolo-object-detection-explained

 

https://youtu.be/69Ii3HjUiTM?si=2RIyr9XaHH-1VS00

2025년 2월 현재 YOLO는 v11까지 발표되었다.

https://docs.ultralytics.com/models/yolo11/

 

YOLO11 🚀 NEW

Discover YOLO11, the latest advancement in state-of-the-art object detection, offering unmatched accuracy and efficiency for diverse computer vision tasks.

docs.ultralytics.com

본문에서는 YOLOv4를 기준으로 객체 인식하는 과정을 설명한다.

 

3. YOLO 인식 과정 요약

YOLO 알고리즘이 동작하기 위해서는 다음과 같은 과정이 필요하다.

1. 식별하고자 하는 이미지가 포함된 이미지 셋(Image set)을 준비한다.

2. 이미지셋에서 식별하고자 하는 이미지를 라벨링 한다.

3. 이미지셋을 훈련용시험용으로 분류한다.

4. 신경망 네트워크 설정 파일(cfg파일)을 YOLO 버전, 이미지 클래스, GPU사양에 맞게 수정한다. 

5. YOLO 버전에 맞는 pre-trained weight함수 파일을 다운로드 한다. 

6. darknet을 실행하여 트레이닝을 실행한다. 여기서 final weight 함수 파일이 생성된다.

7. 6에서 생성한 weight 함수 파일과 설정파일을 darknet_ros와 같은 runtime 프레임 워크에 설치한다.

 

4. 시스템 준비하기

참고로 본 글은 RTX3070이 설치된 ASUS의 Rog Strix Scar G533Z 랩탑에서 수행되었다.

자세한 사양은 다음과 같다.

- CPU: 14-Core Intel i9-12900H

- RAM: 16GB DDR5

- GPU: NVIDIA GeForce RTX 3070 Ti 8GB

- OS: Ubuntu 20.04 LTS

4. 1. darknet 설치

YOLO 알고리즘의 오픈소스 버전은 darknet이라는 이름으로 깃허브에 공개되었다.

본 글은 YOLOv4기준으로 작성된 Alexey의 https://github.com/AlexeyAB/darknet 의 darknet을 fork하여 수정한 버전으로 설치하였다. CUDA 버전12 이후에서 발생하는 오류를 해결한다.

아래 repository에서 다운받으면 된다.

https://github.com/kyuhyong/darknet

 

GitHub - kyuhyong/darknet: YOLOv4 / Scaled-YOLOv4 / YOLO - Neural Networks for Object Detection (Windows and Linux version of Da

YOLOv4 / Scaled-YOLOv4 / YOLO - Neural Networks for Object Detection (Windows and Linux version of Darknet ) - kyuhyong/darknet

github.com

 

4. 1. 1. CUDA 설치하기

darknet은 GPU의 성능을 최대한으로 끌어내기 위해 C를 기반으로 CUDA 아키텍쳐를 사용하여 작성되었다.

그런 이유로 darknet을 정상적으로 설치하기 위해서는 n사의 GPU와 함께 CUDA가 설치되어야 한다.

참고로 본 글은 다음 CUDA 버전에서 작성되었다.

$ nvcc --version
nvcc: NVIDIA (R) Cuda compiler driver
Copyright (c) 2005-2023 NVIDIA Corporation
Built on Tue_Jun_13_19:16:58_PDT_2023
Cuda compilation tools, release 12.2, V12.2.91
Build cuda_12.2.r12.2/compiler.32965470_0

CUDA의 자세한 설치 방법은 다음에 설명한다.

4. 1. 2. OpenCV 설치하기

영상 처리를 다루므로 opencv역시 설치되어야 한다.

CUDA를 지원해야 하므로 apt install로 설치하면 안되고 직접 opencv패키지를 다운로드해서 CUDA지원을 활성화한 상태로 cmake 하여 빌드 인스톨한다.

참고로 현재 설치된 opencv 버전은 다음과 같다.

$ pkg-config --modversion opencv
4.8.0

 

4. 1. 3. Makefile 수정하기

각자의 시스템에 맞춰서 Makefile을 수정한다.

- GPU=1 :CUDA 가속 기능을 켠다(/usr/local/cuda에 설치되어있을 것)

- CUDNN=1 :CUDA의 뉴럴넷 가속 기능(cuDNN)을 켠다(/usr/local/cudnn에 설치되어있을 것)

- CUDNN_HALF=1 : 검출과 트레이닝 속도를 가속하기 위한 Tensor Core를 빌드한다.

- OPENCV=1: 카메라 영상, 비디오 입력을 처리하기 위해 사용한다.

GPU가 지원하는 아키텍쳐에 맞는 ARCH 넘버를 지정한다.

ARCH= -gencode arch=compute_35,code=sm_35 \
      -gencode arch=compute_50,code=[sm_50,compute_50] \
      -gencode arch=compute_52,code=[sm_52,compute_52] \
	  -gencode arch=compute_61,code=[sm_61,compute_61]


 터미널에서 darknet 루트 폴더로 이동한 다음 make 명령으로 빌드한다.

 

5. 이미지셋 만들기

5. 1. 이미지 모으기

darknet 폴더 안에 인식하고자 하는 대상이 포함된 여러 이미지 데이터 폴더를 만든다.

예를 들어 "image_data" 라는 폴더 안에 "images"라는 폴더를 만들고 여러 이미지들을 image_##.jpg 등으로 정리하여 한곳에 넣는다.

폴더 구조는 다음과 같다.

darknet
├── image_data
│   ├── images
|   │   ├── image_1.jpg
│   |   ├── image_2.jpg
|   |   |   ...
│   |   └── image_n.jpg

- 다양한 배경과 조명 환경에서 촬영된 이미지가 필요하다.

 

 

5. 2. 이미지 라벨링 하기

이미지에 사각 박스를 그리고 각각의 class를 지정하는 과정이다.

5. 2. 1 프로그램 다운로드

이 작업을 위해 다음 프로그램이 도움이 된다. 
https://github.com/HumanSignal/labelImg

 

GitHub - HumanSignal/labelImg: LabelImg is now part of the Label Studio community. The popular image annotation tool created by

LabelImg is now part of the Label Studio community. The popular image annotation tool created by Tzutalin is no longer actively being developed, but you can check out Label Studio, the open source ...

github.com

5. 2. 2. classes.txt 파일 준비

먼저 앞서 만든 image_data 폴더에 classes.txt 파일을 하나 만들고 각 행마다 class 이름을 입력하고 저장한다.

만약 구별하고자 하는 항목이 clock, toy_car, mouse 라면 classes.txt파일은 다음과 같다.

clock
toy_car
mouse

 5.2.1 에서 다운로드한 labelimg 폴더로 이동하여 python3 labelimg.py 를 실행하면 다음과 같은 화면이 나타난다.

 

Open Dir을 눌러 위에서 만든 image_data 폴더를 연다.

해당 이미지에서 검출하고자 하는 물체 주변으로 박스를 그린다. 각 박스마다 해당하는 class를 지정한다.

이렇게 하면 각 image_##.jpg 마다 해당되는 class의 위치가 저장된 image_##.txt 파일이 생성된다.

 

5.3 이미지 분류하기 

라벨링된 이미지와 각 이미지의 class위치가 저장된 txt 파일이 모두 얻어졌다면,

해당 파일들을 각각 훈련용 데이터셋과 테스트용 데이터 셋으로 분류하고 각 파일의 경로를 지정해주어야 한다.

Train 데이터와 Test 데이터의 비율은 보통 7:3 정도가 적절하다고 한다.

darknet 폴더에서 다음명령을 실행한다. (이미지 데이터가 image_data폴더인 경우)

python split_images.py image_data 

이 프로그램은 자동으로 image_data 안의 images 폴더를 검색하여 train.txt 파일과 test.txt 파일을 생성한다.

이제 폴더 구조가 다음과 같이 변경되었다.

darknet
├── image_data
│   ├── images
|   |   ├── classes.txt
|   |   ├── image_n.jpg
|   |   ├── image_n.txt
|   |   └── 
│   ├── test.txt
│   └── train.txt

5. 4. yolo 설정파일 cfg 파일 만들기

image_data/cfg 폴더 안에 사용하고자 하는 YOLO 버전에 맞는 cfg 설정 파일을 만들어야 한다.

yolov4-tiny 버전을 사용할 예정이므로 darknet/cfg 폴더에서 yolov4-tiny.cfg 파일을 복사해서 

image_data/cfg 폴더에 넣는다.

인식할 이미지 클래스의 갯수, 사용하는 GPU 메모리등에 따라 수정이 필요하다.

수정할 부분은 다음과 같다. (https://eehoeskrap.tistory.com/370 글 참조)

  • batch
    • GPU성능에 따라 달라진다.
    • 기본 값은 64
    • 3070Ti의 경우 4보다 큰 값을 넣으면 실패한다.
  • subdivisions
    • 배치 사이즈인 64를 얼마나 쪼개서 학습을 할건지에 대한 설정 값
    • 기본 값은 8 이지만 Out of memory 에러가 날 경우 16 또는 32 또는 64 로 조절하여 학습을 시도함
  • width 및 height
    • 기본 값은 416 이지만 608로 변경하여 학습 할 수 있음
    • 608로 변경 시 정확도가 좋아질 수 있음
  • 입력되는 데이터로부터 다양한 데이터를 학습하기 위한 설정 값
    • angle
      • augmentation에 관련된 값으로서, 이미지를 - + 몇 도 돌릴 것인지에 대한 설정 값
      • 보통 0으로 두거나, 다양한 각도에서 학습하고 싶은 경우 30 ~ 45 정도, 경우에 따라 90도 가능함
    • saturation
      • augmentation에 관련된 값으로서 이미지에 채도를 추가하고자 할 때 설정하는 값
    • exposure
      • augmentation에 관련된 값으로서 노출 값을 추가하고자 할 때 설정하는 값
    • hue
      • augmentation에 관련된 값으로서 색상을 변경하고자 할 때 설정하는 값

 

  • learning_rate
    • 보통 0.001 
    • multi-gpu 사용 시 학습률을 0.001 / gpu 수 만큼 조절하기도 함
      • 예를 들어 GPU 2장을 이용한 학습 시 0.001 / 2 = 0.0005 값을 사용
  • burn_in
    • 보통 1000 
    • multi-gpu 사용 시 몇 만큼의 iteration 이 지날 때 마다 학습률을 조정 할 것인지에 대한 값
    • multi-gpu 사용 시 1000 * gpu 수 만큼 조절함
      • 예를 들어 GPU 2장의 경우 1000 * 2 = 2000 으로 설정
  • max_batches
    • 언제까지 iteration을 돌건지 설정하는 값
    • 보통 class 수 * 2000 으로 설정 (넉넉하게 4000을 곱하는 경우도 있음)
      • 1 class 의 경우 (1 * 2000) + 200 = 2200
      • 80 class 의 경우 (80 * 2000) + 200 = 160,200 
      • 뒤에 붙인 200 값은 전 후로 알맞은 가중치를 얻기 위함임 
  • policy
    • 보통 steps
  • steps
    • 위에서 설정한 max_batches 사이즈(200을 더하지 않은..)의 80% 와 90%를 설정
      • 예를 들어 80 class 의 경우 128000,144000 으로 설정
  • scales
    • 보통 .1,.1

출처: https://eehoeskrap.tistory.com/370 [Enough is not enough:티스토리]

 

수정한 부분은

batch=4 (GPU의 메모리 용량에 따라 16, 64등 더 큰 숫자를 할 수 있다. 참고로 3070Ti에서는 4보다 크면 실패함)

classes=2

 

[convolutional]
...
filters=21

...

filters= (4 + 1 + 클래스 수) * 3

 

참고로 2개의 클래스가 있는 경우의 cfg 파일이다.

[net]
# Testing
#batch=1
#subdivisions=1
# Training
batch=4
subdivisions=1
width=416
height=416
channels=3
momentum=0.9
decay=0.0005
angle=0
saturation = 1.5
exposure = 1.5
hue=.1

learning_rate=0.00261
burn_in=1000

max_batches = 4000
policy=steps
steps=3200,3600
scales=.1,.1


#weights_reject_freq=1001
#ema_alpha=0.9998
#equidistant_point=1000
#num_sigmas_reject_badlabels=3
#badlabels_rejection_percentage=0.2

[convolutional]
batch_normalize=1
filters=32
size=3
stride=2
pad=1
activation=leaky

[convolutional]
batch_normalize=1
filters=64
size=3
stride=2
pad=1
activation=leaky

[convolutional]
batch_normalize=1
filters=64
size=3
stride=1
pad=1
activation=leaky

[route]
layers=-1
groups=2
group_id=1

[convolutional]
batch_normalize=1
filters=32
size=3
stride=1
pad=1
activation=leaky

[convolutional]
batch_normalize=1
filters=32
size=3
stride=1
pad=1
activation=leaky

[route]
layers = -1,-2

[convolutional]
batch_normalize=1
filters=64
size=1
stride=1
pad=1
activation=leaky

[route]
layers = -6,-1

[maxpool]
size=2
stride=2

[convolutional]
batch_normalize=1
filters=128
size=3
stride=1
pad=1
activation=leaky

[route]
layers=-1
groups=2
group_id=1

[convolutional]
batch_normalize=1
filters=64
size=3
stride=1
pad=1
activation=leaky

[convolutional]
batch_normalize=1
filters=64
size=3
stride=1
pad=1
activation=leaky

[route]
layers = -1,-2

[convolutional]
batch_normalize=1
filters=128
size=1
stride=1
pad=1
activation=leaky

[route]
layers = -6,-1

[maxpool]
size=2
stride=2

[convolutional]
batch_normalize=1
filters=256
size=3
stride=1
pad=1
activation=leaky

[route]
layers=-1
groups=2
group_id=1

[convolutional]
batch_normalize=1
filters=128
size=3
stride=1
pad=1
activation=leaky

[convolutional]
batch_normalize=1
filters=128
size=3
stride=1
pad=1
activation=leaky

[route]
layers = -1,-2

[convolutional]
batch_normalize=1
filters=256
size=1
stride=1
pad=1
activation=leaky

[route]
layers = -6,-1

[maxpool]
size=2
stride=2

[convolutional]
batch_normalize=1
filters=512
size=3
stride=1
pad=1
activation=leaky

##################################

[convolutional]
batch_normalize=1
filters=256
size=1
stride=1
pad=1
activation=leaky

[convolutional]
batch_normalize=1
filters=512
size=3
stride=1
pad=1
activation=leaky

[convolutional]
size=1
stride=1
pad=1
filters=21
activation=linear

[yolo]
mask = 3,4,5
anchors = 10,14,  23,27,  37,58,  81,82,  135,169,  344,319
classes=2
num=6
jitter=.3
scale_x_y = 1.05
cls_normalizer=1.0
iou_normalizer=0.07
iou_loss=ciou
ignore_thresh = .7
truth_thresh = 1
random=0
resize=1.5
nms_kind=greedynms
beta_nms=0.6
#new_coords=1
#scale_x_y = 2.0

[route]
layers = -4

[convolutional]
batch_normalize=1
filters=128
size=1
stride=1
pad=1
activation=leaky

[upsample]
stride=2

[route]
layers = -1, 23

[convolutional]
batch_normalize=1
filters=256
size=3
stride=1
pad=1
activation=leaky

[convolutional]
size=1
stride=1
pad=1
filters=21
activation=linear

[yolo]
mask = 1,2,3
anchors = 10,14,  23,27,  37,58,  81,82,  135,169,  344,319
classes=2
num=6
jitter=.3
scale_x_y = 1.05
cls_normalizer=1.0
iou_normalizer=0.07
iou_loss=ciou
ignore_thresh = .7
truth_thresh = 1
random=0
resize=1.5
nms_kind=greedynms
beta_nms=0.6

 

image_data폴더에 "detector_data.txt"파일을 다음과 같이 만들어준다.

classes=3
train=image_data/train.txt
valid=image_data/test.txt
names=image_data/images/classes.txt
backup=image_data

 

이제 폴더 구조가 최종적으로 다음과 같이 만들어졌다.

darknet
├── image_data
│   ├── cfg
|   |   └── yolov4-tiny.cfg
│   ├── detector.data
│   ├── images
|   |   ├── classes.txt
|   |   ├── image_n.jpg
|   |   ├── image_n.txt
|   |   └── ...
│   ├── test.txt
│   └── train.txt

 

5. 5. 기본 weight함수 파일 다운로드 하기

weight함수를 생성하기 위해서는 yolo네트워크에 맞게 기본 설정된 weight함수 파일이 필요하다.

darknet/weight 폴더에 yolov4-tiny.conv.29 를 다운로드 하기 위해 https://github.com/AlexeyAB/darknet/releases/download/darknet_yolo_v4_pre/yolov4-tiny.conv.29 파일을 다운로드한다.

 

5. 6. 트레이닝 하기

다음과 같이 darknet 명령을 실행하여 weight 함수 파일을 생성한다.

./darknet detector train image_data/detector.data image_data/cfg/yolov4-tiny.cfg weights/yolov4-tiny.conv.29 -map >> log/image_data.log

-map 옵션을 주면 훈련 과정이 그래프로 표시된다.

 

 

 

클래스의 갯수, GPU의 성능에 따라 트레이닝이 수행된 후 final weight 파일이 생성된다.

 Tensor Cores are used.
v3 (iou loss, Normalizer: (iou: 0.07, obj: 1.00, cls: 1.00) Region 30 Avg (IOU: 0.803308), count: 14, class_loss = 0.416282, iou_loss = 0.929667, total_loss = 1.345949 
v3 (iou loss, Normalizer: (iou: 0.07, obj: 1.00, cls: 1.00) Region 37 Avg (IOU: 0.787318), count: 8, class_loss = 0.853544, iou_loss = 8.100082, total_loss = 8.953627 
 total_bbox = 161341, rewritten_bbox = 0.000000 % 

 Tensor Cores are used.
v3 (iou loss, Normalizer: (iou: 0.07, obj: 1.00, cls: 1.00) Region 30 Avg (IOU: 0.918996), count: 22, class_loss = 0.001562, iou_loss = 4.227271, total_loss = 4.228833 
v3 (iou loss, Normalizer: (iou: 0.07, obj: 1.00, cls: 1.00) Region 37 Avg (IOU: 0.891980), count: 24, class_loss = 0.041704, iou_loss = 6.550589, total_loss = 6.592293 
 total_bbox = 161387, rewritten_bbox = 0.000000 % 

 Tensor Cores are used.
12Total Detection Time: 0 Seconds
Saving weights to image_data/yolov4-tiny_4000.weights
Saving weights to image_data/yolov4-tiny_last.weights
Saving weights to image_data/yolov4-tiny_final.weights

 

이제 이 final.weight 파일을 가져다 쓰면 된다.

반응형
반응형

무안공항에서 제주항공 2216편 참사가 발생한지 정확히 1달만인 2025년 1월 29일, 미국에서 가장 붐비는 공역 중 하나인 수도 워싱턴DC 레이건 공항에 착륙중이던 아메리칸 항공 CRJ 여객기가 미 육군 UH-60 헬기와 충돌, 탑승객 67명 전원이 사망하는 참사가 발생하였다.

 미국의 수도 한복판에서 그것도 비행기 고장이나 비상상황이 아닌 정상적으로 운항중인 항공기간의 충돌로 빚어진 사고라는 점 때문에 매우 충격적으로 다가왔다. 실제로 미국의 주요 매체에서 가장 Top으로 다루는 뉴스이고 많은 미국인들의 관심도 짐중된 사건이다.

 주요 공항 타워는 물론 대부분의 지역 관제 ATC까지 실시간으로 공개되는 미국 답게 연방교통안전국(NTSB)에서 사고 브리핑을 하기도 전에 해당 사고 관련 관제 녹음이 공개되어 유투브는 물론 주요 뉴스에서 헤드라인으로 다뤄졌다.

 

 이 글에서는 자세한 사고 경과나 원인을 분석하기 보다는 한국과 미국 양쪽에서 거의 비슷한 시기에 발생한 사고를 어떻게 다루고 있는가에 대해 중점적으로 다뤄보도록 하겠다.

 사실 이번 포토맥 강 충돌사고는 제주항공 사고와 달리 민간항공뿐 아니라 미국 정부 자산인 육군에서 운용하는 항공기도 관여되어 있기에 사고 원인에 따라 정치적으로 매우 민감할 수 있는 사안이다. 따라서 경우에 따라  혹시 미국 정부에 불리한 증거나 사실관계를 숨기려고 하는 거 아닌가 하는 음모론적 시각으로 볼 수 있다. 실제로 무안공항의 콘크리트로 만들어진 로컬라이저 둔덕이 참사의 원인으로 지목되자 한국 정부 (국토부)에서 항공 안전 기준에 맞춰서 설계 했다며 발빰해는 모습을 보면  그런 의심이 들 수 도 있겠다.

 

그럼 사고 2일 후 NTSB에서 발표한 브리핑 내용을 보자.

https://www.youtube.com/live/6WzoEb0m8x4?si=RAFVpmfC7AQgeGs8

 

사고 발생 당시의 ATC가 언론을 통해 이미 공개되었지만 NTSB에서 다시 시간별로 교신 내용을 다시 하나하나 명확하게 확인해준다.

이 브리핑을 통해 관제사가 헬기 조종사에게 CRJ 여객기를 육안 하였는지 여부를 확인하는 부분이 드러났다.

그리고 당시 관제사와 여객기, 그리고 관제사와 헬기 조종사간의 통신 환경 등을 구체적으로 설명하는 부분도 보인다.

(민간 항공기들은 VHF 주파수로 통신하는 반면 군 항공기들은 UHF 주파수로 통신하기 때문에 서로 상대방의 존재를 명확하게 인식하기 어려운 점도 사고에 기여한 측면이 있다)

기자들에게 최대한 자세하게 사실관계에 기반하여 설명하고있다.

 

이에 반해 국토부의 브리핑 내용은 어떠한가?

이 브리핑도 사고 2일 후인 1월1일에 발표한 내용이다.

https://youtu.be/e1ibHBpSnFA?si=7AooAghWbrfgw007

 

시작부터 뭔가 밝히기 어렵다는 말만 한다.

교신 내용을 공개하지 않는것도 문제지만


교신 내용에 뭐가 불명확한지도 모르는건 더 문제다.

 

국토부 항공안전정책관이라는 사람이 나와서 저런말을 하는것은 대한민국 항공 안전을 책임지고 근무하는 항공교통 관련 근무자들을 명백히 우롱하는 행위다. 

 조종사, 관제사들은 항상 명확한 관제 지시를 주고받기 위해 시험도 보고 정기적으로 훈련을 한다.

이 모든 것은 바로 이러한 급박한 상황에서도 불명확한 교신이나 오해가 발생하지 않게끔 하기 위함이다.

대체 국토부에서는 무얼 근거로 공개 주파수인(누구나 들을 수 있는) 항공교통 관제 녹취를 발표하지 않고 숨기고 있는지 분명하게 밝혀야할 일이다. 그러면 사고 경위 파악이 훨씬 쉽고 누구나 이해할 수 있게 된다.

반응형
반응형

 

2024년 12월 29일 일요일, 방콕을 출발하여 무안공항으로 착륙하려던 제주항공2216편이 무안공항 1번 활주로를 향해 최종 접근 단계에서 조류충돌로 추정되는 기체 이상으로 인해 긴급하게 회항하여 반대편 방향에서 19번 활주로로 착륙하던중 감속에 실패하고 최종적으로는 비행장 남단 벽에 충돌하는 사고가 발생했다.

 

언론을 통해 알려진 사실은 다음과 같다.

해당 항공기는 현지 시각 8시 45분경 관제탑으로부터 01번 활주로로 착륙허가를 받은 후 약 57분에 "버드스트라이크"경고를 받은 지 2분뒤 59분에 "메이데이"를 호출한 다음 복행하여 반대방향(19번 활주로)으로 동체착륙을 시도했다.

https://www.hani.co.kr/arti/area/honam/1175381.html

 

“메이데이” 신호 뒤 방향 바꿨지만…착륙 허가부터 사고 보고까지 단 10분

29일(현지시각) 새벽 1시30분 타이(태국) 방콕에서 이륙한 제주항공 여객기(7C 2216)는 방콕 수완나품공항을 출발해 아침 8시30분 무안국제공항에 착륙할 예정이었다. 국토교통부 항공정보포털시스

www.hani.co.kr

재착륙하는 과정에서 사고 항공기는 랜딩기어를 내리지 않은 상태로 동체착륙을 시도하였으며

접지 후 우측 엔진의 역추력 장치가 펼쳐진 것이 확인되었으나 정상적으로 작동하는지 확인되지 않았다.

감속되지 않은 항공기는 활주로 남단의 항행안전시설을 포함한 옹벽에 추돌하여 폭발과 화재가 발생하였다.

 

 이 사고로 탑승한 승객과 승무원을 포함 181명 중 후방의 승무원2명을 제외한 179명 전원이 사망하게 되었다.

 

이번 사고의 특이점은 다음과 같다.

  1. 메이데이 선포 후 비상처리 절차 없이 곧바로 동체착륙을 시도하였음
  2. 착륙 중 플랩을 내리지 않았음
  3. 스포일러, 역추력 장치등이 동작하지 않음

FlightAware에 ADS-B데이터를 바탕으로 기록된 사고 항공기의 항적은 다음과 같다.

https://hi.flightaware.com/live/flight/HL8088/history/20241228/1840ZZ/VTBS/RKJB

이 기록에 의하면 사고기는 최종 접근 약 6~7마일 전까지 별다른 이상 없이 비행을 지속하였다.

마지막 항적은 고도 1900피트, 속도 167노트로 착륙을 위한 최종 단계에 진입하였다.

 

통상 2000피트 전후에서 랜딩기어를 내리므로 아마도 이 시점에 항공기는 랜딩기어를 내린 상태였을 것이고

167노트 속도라면 플랩도 30정도 까지 내린 상태였을 것이다.

동일한 루트를 비행한 다른 항적과 비교해도 크게 차이가 있지는 않다.

12월 27일에 동일한 방콕-무안 경로를 비행한 제주항공 여객기의 데이터

다시말하면 조류충돌로 인한 비상선언 직전까지 사고기는 아무런 이상이 없었으며

통상적인 비행 패턴을 따르고 있었다.

하지만 조류충돌 경고 그리고 메이데이 선언 후 상황은 급격하게 달라진다. 

1번 활주로를 향해 정대했던 사고 항공기는 곧바로 공항을 지나친 후 남쪽 방향의 활주로를 향해 180도 선회한다. (좌선회였는지 우선회였는지는 불분명하나 국토부 사고 개요도에는 Left downwind 후 우선회한 것으로 표시됨

국토부에서 사고 후 공개한 개요도

 

조류 충돌로 인하여 엔진을 포함한 항공기 손상이 발생하는 것은 드문일은 아니며 모든 조류충돌이 인명사고로 이어지지는 않는다.

이번 사고가 발생한 것과 같은 737을 포함하여 쌍발 엔진이 장착된 승객용 항공기는 이륙도중 한쪽 엔진이 꺼지더라도 다른 하나의 엔진만으로 정상적인 비행이 가능하다.

아래는 이륙 상승 중 버드스트라이크가 발생하여 엔진 페일이 발생하였지만 정상적으로 상승 후 무사히 착륙한 사례이다.

https://youtu.be/4-KhNH850qk?si=uN93YfweRLJApUFf

 

 그래서 싱글 엔진 페일에 대처하기 위한 절차들이 마련되어있으며 모든 항공 운송 조종사들의 훈련 과정에는 싱글 엔진 페일을 가정한 비상 대처 훈련이 반드시 포함되어있다. 

기존에는 조류 충돌로 인해 듀얼 엔진 페일이 발생할 가능성은 거의 zero (0)에 가깝다고 생각하여 해당 절차는 마련되지 않았으나 유명한 허드슨강에 착륙한 US1549편 사고 사례 이후 듀얼 엔진 페일을 가정한 훈련도 일부 이루어지고 있다.

 다만 상기 사례에서 보듯 듀얼엔진 페일의 경우 충분한 고도와 속도가 확보되지 않은경우 까딱 잘못하면 참사로 이어지기 쉽다.

 조종과실과 무리한 조종으로 동체 착륙 후 복행 과정에서 듀얼엔진 페일이 발생하여 추락한 PIA8303 사고는 그 위험성을 잘 보여준다.

 

듀얼엔진 페일 시나리오

왜 이렇게 서론이 길었냐 하면 그만큼 제주항공 2216편 사고가 매우 발생하기 어려운 비상상황을 내포하고 있기 때문이다.

 

무엇이 조종사로 하여금 비상조치할 여유도 없을만큼 급박하게 180도 선회비행을 해서 착륙하게끔 했는지는 아직 알 수 없다. 확실한 것은 1개의 엔진만 페일된 것이 확실하다면 어떠한 조종사도 그런 곡예비행에 가까운 기동을 해서 무리하게 착륙하는 도박을 하지는 않을거라는 점이다.

 

다만 몇가지 시나리오를 생각해볼 수 있는데, 그 경우는 다음과 같다.

1. 버드스트라이크로 2개의 엔진이 모두 Inop 된 경우

2. 버드스트라이크로 인해 엔진이 Inop되었는데 화재등이 발생 조종계통을 모두 고장낸 경우

3. 버드스트라이크로 인해 2번 우측엔진이 Inop 되었는데 잘못해서 정상적인 1번엔진을 꺼버려서 듀얼엔진 페일이 된 경우.

 

1번 시나리오의 경우 최종 접근 단계에서 2개의 엔진이 모두 Inop된 것을 알게 되었다면 즉시 복행할것이 아니라 랜딩기어를 접고 최대 글라이드 강하율을 확보하여 접근하는 활주로로 비행하는 것이 정상적이다. 

 

2번 시나리오는 복행을 수행하는 도중 갑작스러운 화재 등이 이어져서 미처 손쓸 틈 없이 선회 착륙을 시도하는 경우인데 불가능한 상황은 아니지만 제주항공 2216사고기와 관련된 어떠한 영상을 찾아봐도 활주로에 닿기 전까지 검은 연기라던지,  불꽃이 발생하는 장면이 없는것으로 보아 가능성이 매우 희박하다. 게다가 조종계통이 완전 먹통이 되었다면 아래 영상과 같이 활주로 한가운데에 정확하게 정대하는 기동 자체가 매우 어렵다.

https://youtu.be/XsaZWAALUkQ?si=WgPjj1w3G3c60FO2&t=54

 

 

3번 시나리오의 경우 이미 동일한 사례가 있으므로 불가능한 상황은 아니다. 대표적인 사례가 대만의 트랜스아시아 235편 사고인데 이륙 직후 2번 엔진의 autofeather 시스템 결함이 발생하여 추력에 문제가 생겼으나 잘못된 대처로 정상적인 1번엔진을 꺼버려서 이륙한지 3분만에 추락한 사례이다. 

 

하지만 복행 도중 다른 엔진을 꺼트릴만큼 급하게 처치할 이유가 있었을까? 

복행의 첫번째 단계는 기어를 올리고 엔진을 TO/GA 상태로 변경하는 것이다. 일단 고도와 속도를 확보하는것이 최우선이고 엔진 처치는 그 이후에 수행하는 것이 절차이다. 절대로 멀쩡한 엔진을 꺼서 긴급한 상황을 만들 이유는 없는것이다.

설령 조종사의 잘못된 기재취급으로 인해 듀얼엔진 페일이 된 경우라면 어떻게 될까?

활주로 오버런

이런 기막힌 확률을 뚫고 듀얼엔진 페일이라는 시나리오를 만들어냈다. 그리고 조종사에게 남은 선택지는 오직 180턴을 수행해서 반대쪽 활주로로 내리는 것이다.

보통 조종사들이 숏파이널 이라고 부르는 기동이다. 

모든 기재가 정상적인 상황에서도 실제로 수행하기에는 매우 어려운 조작이다. 특히나 두개의 엔진이 Inop된 매우 급박한 상황이라면 거의 도박에 가깝다.

 

사고기 동영상을 보면 조종사는 이 기동을 매우 정확하게 수행해냈다. 

 

다만 이후 과정이 문제이다.

플랩도 없는 상태로 거의 200노트에 가까운 속도로 동체착륙을 감행했다.

속도가 너무 빠르다보니 지면효과로 인해 침하율이 떨어지고 접지가 지연되었다. 영상에서 보면 활주로의 거의 절반정도에서 접지가 이루어진 것을 확인할 수 있다. 

 

그런데 영상을 보면 속도가 거의 줄어드는 것을 느끼기 어렵다. 마치 엔진이 다시 살아나기라도 한것처럼 기체의 속도가 유지되다가 거의 착륙속도에 가까운 속도로 벽을 때려박는것을 확인할 수 있다.

 

https://youtu.be/n65Yq1NKMzU?si=vZ3qGiHaJYMNwqJE

 

바퀴의 접지력과 브레이크 효과가 없어서 적절한 감속이 이뤄지지 않았다는 의견도 있는데

정말 동체착륙을 하면 감속이 안되는가?

 

아래 사례를 보면 꼭 그렇지만은 않은거 같다.

 

https://youtu.be/UC8ySY_GlUk?si=Q3Vza06z4j7qOtuL

 

결론은 이번 제주항공 사고가 항공사고에서 유례를 찾기 힘들정도로 매우 이상하다는 것.

어서 빨리 비행기록 데이터가 해석되어서 실마리를 찾을 수 있으면 좋겠다.

반응형

+ Recent posts