이번 글을 끝으로 종합설계 AI Turret을 제작 및 Test를 마무리하였다. 이전 일지에서 각 기능별 동작 확인을 하였는데, 매우 성공적이였고 이를 기반으로 최종 구성 및 동작 확인을 진행하였다.
지금까지 문제는 많았지만, 각 부분마다 정상 동작을 하였기에 성공적으로 AI Turret을 제작할 수 있었다.
AI Turret 설계에 대한 전반적인 흐름을 정리해보았으며, Object Detection과 Tracking하는 것을 볼 수 있다.
초기 AI Turret System의 Target Object는 유해동물이었으며, 특히 그중에서도 비둘기로 정하였다. 하지만 Turret이 근본적으로 사용되는 방위산업의 틀에 맞게 주제를 변경하여, Target Object을 유해동물에서 “Pistol”로 수정하였다.
AI Turret은 Object를 YOLOv5를 통해 탐지하기에, 학습 대상만 수정하여 AI Turret System에 적용시킨다면 곧 바로 적용할 수 있다.
이는 다양한 Object를 인식하고 Target Object로 설정할 수 있다는 뜻이다. 이러한 점에서 AI Turret System의 활용도가 매우 폭 넓다는 것을 알 수 있다.
AI Turret System를 설계하기 전, Main Processor로는 Raspberry Pi 4B를 사용할 계획이었다.
정확히는 Cluster를 구축하여 분산 컴퓨팅을 시도할 계획이었지만, 효율성과 금전적 상황에 의해 무산되었다.
이후, 단 하나의 Raspberry Pi 4B를 사용하여 YOLOv3-Tiny를 활용할 계획이었다.
이 또한 Raspberry Pi 4B와 YOLOv3-Tiny 성능이 AI Turret System에 적용하기는 힘들 다는 실험 결과를 바탕으로 PC환경(Galaxy Book)으로 수정하였다.
하지만 최종적으로는 Processor의 성능이 더욱 좋은 M1칩을 사용하는 MacBook을 선택하였고, 이를 바탕으로 AI Turret System을 설계하였다.
Main Processor 뿐만 아니라, Object를 Detection하고 Tracking 하기 위한 모델을 기존의 YOLOv3-Tiny보다 더욱 향상된 성능을 지닌 YOLOv5를 사용하였다.
기본 라이브러리를 사용한 것이 아닌 “Pistol DataSet”을 활용하여 직접 YOLO 모델을 학습시켰다. 기본 YOLOv5 모델을 기반으로 Goolge의 "Colab"에서 학습을 하였으며, 아래의 학습 결과에 대한 분석으로 YOLOv5 Custom에 대한 성공 여부를 알 수 있었다.
위 그래프에서 box_loss와 obj_loss는 학습이 진행될수록 감소하는 그래프를 cls_loss는 일정한 값을 유지한다. 또한 mAp, Precison, Recall의 그래프는 1에 수렴하는 형태이다.
이처럼 학습이 진행될수록 낮은 손실률을 보이고 있으며 동시에 높은 Recall을 보인다는 것을 알 수 있다.
DataSet을 활용하여 학습을 마치면, 해당 Object에 대해 학습이 된 모델의 정보가 담긴 “best.pt”가 생성된다. 이 파일은 학습에 사용된 train, weight, cfg, names 등 여러 정보를 가진 파일이다.
# detect.py 중 일부(Object Detection 및 B.Box 각 모서리 좌표 출력)
for *xyxy, conf, cls in reversed(det):
c = int(cls)
label = names[c]
if hide_conf
else f'{names[c]}'
confidence = float(conf)
confidence_str = f'{confidence:.2f}'
print(f"Image: {p.name}, Class: {label}, Confidence: {confidence_str}, BBox: {xyxy}")
이후, YOLOv5를 통해 Object를 인식할 때, 기본 라이브러리 weight파일 대신 best.pt를 사용하게 된다.
Yolov5의 내부파일인 “detect.py”에 B.BOX의 좌표값을 출력 할 수 있도록 설계하여 좌표값을 추출했다.
# detect.py 중 일부(Object Detection 및 B.Box 중앙 좌표 출력)
for *xyxy, conf, cls in reversed(det):
# integer class
c = int(cls)
label = names[c]
if hide_conf
else f'{names[c]}'
confidence = float(conf)
confidence_str = f'{confidence:.2f}’
x_center = (xyxy[0] + xyxy[2]) / 2
y_center = (xyxy[1] + xyxy[3]) / 2
# Print bounding box information
print(f"Image: {p.name}, Class: {label}, Confidence: {confidence_str}, CenterCoordinates: ({x_center:.2f}, {y_center:.2f})\n")
더 나아가 B.Box의 각 모서리 좌표값을 이용하여, B.Box의 중앙값을 구할 수 있었다.
이는 위 코드에서 x_center와 y_center를 구하기 위해 각 모서리 좌표값을 활용한 것을 볼 수 있다.
WebCam을 통해 Object를 탐지한 YOLOv5의 B.Box 정보를 Turret 기능으로 활용하기 위해서는 Turret의 동작을 제어하는 ESP32로 정보를 전송해야 한다.
따라서 MacBook과 ESP32 간의 통신을 구축하였으며, 그 과정에서 “USB to UART Module” 을 사용했다.
MacBook과 ESP32 간의 통신을 위한 H/W 환경을 구축한 이후, S/W 환경을 구축해주 었다. YOLOv5의 출력값을 ESP32로 전송하기 위해서는 subprocess 함수를 사용하여 best.pt 호출하는 별도의 파일이 필요하다.
그래서 “ESP32.py”라는 파일을 만들어, pyserial을 활용한 ESP32와의 통신을 구현했다.
# ESP32.py(B.Box 각 모서리 좌표 출력)
import subprocess
import serial
command = "python detect.py --weights best.pt --conf 0.7 --img 416 --source 0"
process=subprocess.Popen(command,shell=True,stdout=subprocess.PIPE, stderr=subprocess.PIPE)
subprocess.run(command, shell=True)
ser = serial.Serial('/dev/cu.usbserial-1320', 115200)
output = process.stdout.readline()
if not output and process.poll() is not None:
break
if output: coordinates = "x1 y1 x2 y2"
ser.write(coordinates.encode())
ser.close()
이렇게 출력된 좌표값을 ESP32로 전송하여 모터를 제어한다.
하지만 데이터 전송 중, YOLOv5에서 추출된 B.Box의 중앙 좌표값이 ESP32.py를 거쳐 제대로 전송이 이루어지지 않았다.
이를 해결하기 위해 detect.py에서 추출된 좌표값을 ESP32.py에 한 번 더 저장한 후, ESP32.py를 사용하여 ESP32와 통신했다.
# ESP32.py 중 일부
while True:
output = process.stdout.readline().strip()
match = re.search('(\d+\.\d+), (\d+\.\d+)', output)
if match:
x = float(match.group(1))
y = float(match.group(2))
packet = f"{x:.2f},{y:.2f}\n"
ser.write(packet.encode())
ser.flush()
print(packet)
ser.close()
ESP32.py를 detect.py에서 나온 데이터를 잠시 저장해주는 버퍼로 사용하여 통신 문제를 해결하였다.
AI Turret에 있어서 Stabilizer는 총 2개의 축만 사용하며 동작 원리는 동일하기에, Turret을 지지해줄 2 axis Stabilizer를 구성하는 데에 있어 큰 어려움은 없었다.
앞서 말한 듯이, 2 axis Stabilizer의 동작 메커니즘은 동일하기에 1축만 제작하여, Gyro 축만 변경하여 동일한 알고리즘을 적용시켰다.
//ESP32_Stabilizer_Test.ino 중 일부(Roll 축에 해당하는 1st Stabilizer만 제어)
:
int target_angle_X = 90; // 목표 각도를 0으로 설정
int target_angle_Y = 90; // 목표 각도를 0으로 설정
:
void loop() {
mpu.update();
angle_X = mpu.getAngleX(); // -180 ~ 180
angle_Y = mpu.getAngleY(); // -90 ~ 90
int gryo_con = constrain(angle_X, -90, 90);
int error_X = target_angle_X - gryo_con;
int error_Y = target_angle_Y - angle_Y;
error_X = constrain(error_X, 65, 115);
error_Y = constrain(error_Y, 65, 115);
servo1stb1.write(error_X);
servo1stb2.write(abs(error_X - 180));
servo2stb1.write(error_Y);
servo2stb2.write(abs(error_Y - 180));
delay(10);
}
위 코드에서 볼 수 있듯이, Gyro를 통해 target_angle_X와 target_angle_y보다 틀어진 값과 방향만큼 Servo Motor가 동작을 하여, Stabilizer 기능을 구현하도록 설계하였다.
H/W에서 동작 가동 범위가 제한되어 있기에 이를 미리 시뮬레이션하여, constrain 함수를 사용하여 코드 상으로도 동작에 대한 제한을 두었다.
// AI_Turret_Final_Code.ino 중 일부
void Stabilizer_Setting(int mode_value, int cons_min, int cons_max) {
mpu.update();
angle_X = mpu.getAngleX(); // -180 ~ 180
angle_Y = mpu.getAngleY(); // -90 ~ 90
gryo_con = constrain(angle_X, -90, 90);
error_X = target_angle_X - gryo_con;
error_Y = target_angle_Y - angle_Y;
error_X = constrain(error_X, target_angle_X - cons_min, target_angle_X + cons_max);
error_Y = constrain(error_Y, target_angle_Y - cons_min, target_angle_Y + cons_max);
if (mode_value == 0) { // Support Mode
servo1stb1.write(target_angle_X);
servo1stb2.write(target_angle_X);
servo2stb1.write(target_angle_Y);
servo2stb2.write(target_angle_Y);
}
else if (mode_value == 1) { // Stabilizer Mode
servo1stb1.write(error_X);
servo1stb2.write(abs(error_X - 180));
servo2stb1.write(error_Y);
servo2stb2.write(abs(error_Y - 180));
}
}
위 실험 결과를 바탕으로 AI Turret System에 구현되는 최종 코드를 위와 같이 만들 수 있었다.
Stabilizer와 Support 기능을 쉽게 선택할 수 있고, Max-Min 값을 제한할 수 있게 만들기 위해, Stabilizer 부분만 분리하여 별도의 함수로 제작하였다.
Stabilizer를 H/W Frame을 제작함에 있어, 동작 원리가 같은 만큼 H/W도 1st axis과 2nd axis 모두 동일하게 설계하였다.
동일한 기능과 동작 원리를 가진 1st, 2nd axis Stabilizer이기에 비슷한 형상을 가진 것을 볼 수 있다. 또한 2nd axis Stabilizer는 AI Turret의 하단부를 아크릴 판과 고정시키기 위해 별도의 연결부를 만들어 설계하였다.
AI Turret에 있어 Turret 기능은 매우 중요한 요소이다. AI Turret이 Object를 탐지하고 추적하는 기능이 핵심이기에, Turret을 구성하는 데에 많은 시간과 여러 실험이 필요하 였다.
AI Turret을 구성하는 단계에서 Main Processor가 여러 번 바뀌기도 하였고 통신이 원활하게 동작하지 않아서, Turret 기능 중 가장 시간이 많이 소요된 파트이다.
Raspberry Pi 4B를 시작으로 Galaxy Book을 거쳐 최종적으로 M1칩을 사용하는 MacBook을 사용하기로 하였다. 다행히도 Main Processor가 바뀜에 따라 추가적인 수정 없이 기존 코드를 사용할 수 있었기에 원활히 Turret 기능을 구현할 수 있었다.
통신에 관한 코드는 위에서 설명했기에 생략하겠다.
YOLOv5에서 받은 B.Box 중앙 좌표값을 활용하여, Tilt와 Pan Servo Motor를 활용하여 Object를 Tracking하는 동작을 구현하였다. 여기서 모두 동일한 HS-311이라는 180°회전이 가능한 Servo Motor를 사용하였다.
Stabilizer와 동일하게 최종 동작 코드에서는 Tilt - Pan 기능만 분리하여, 코드의 가독성과 부가적인 설정이 쉽도록 작성하였다.
// AI_Turret_Final_Code.ino 중 일부
void Tracking_Setting(int mode_value, int left_area, int right_area, int cons_min, int cons_max) {
// Pan Setting Mode
if (mode_value == 0) {
if (filteredX < left_area) {
Pan_Servo_Position -= 2;
Pan_Servo_Position = constrain(Pan_Servo_Position, cons_min, cons_max);
servopan.write(Pan_Servo_Position);
Pan_Relay_State = 0;
delay(10);
}
else if (filteredX >= left_area && filteredX < right_area) {
Pan_Relay_State = 1;
}
else if (filteredX >= right_area) {
:
}
}
// Tilt Setting Mode
else if (mode_value == 1) {
if (filteredY < left_area) {
Tilt_Servo_Position_1 --;
Tilt_Servo_Position_2 ++;
Tilt_Servo_Position_1 = constrain(Tilt_Servo_Position_1, cons_min, cons_max);
Tilt_Servo_Position_2 = constrain(Tilt_Servo_Position_2, cons_min - 6, cons_max - 6);
servotilt1.write(Tilt_Servo_Position_1);
servotilt2.write(Tilt_Servo_Position_2);
Tilt_Relay_State = 0;
delay(10);
}
else if (filteredY >= left_area && filteredY < right_area) {
Tilt_Relay_State = 1;
}
else if (filteredY >= right_area) {
:
}
}
}
Tracking_Setting이라는 이름의 함수를 만들어 Tilt와 Pan 기능을 제어하였는데, 이 함수는 Stabilizer보다 더 많은 매개변수를 사용하고 있다.
Tracking_Setting 함수 하나로 Tilt와 Pan 기능을 제어하면서, 화면 픽셀 크기 및 Max-Min 값을 쉽게 제어할 수 있도록 만들었기 때문이다.
원활한 Tracking을 위해, UART로 받아온 좌표값을 이동평균필터를 설계하여 이를 이용하였다.
// AI_Turret_Final_Code.ino 중 일부
void Data_Filter_Setting() {
xValues[xIndex] = x;
yValues[yIndex] = y;
xSum = 0;
ySum = 0;
for (int i = 0; i < filterSize; i++) {
xSum += xValues[i];
ySum += yValues[i];
}
filteredX = xSum / filterSize;
filteredY = ySum / filterSize;
xIndex = (xIndex + 1) % filterSize;
yIndex = (yIndex + 1) % filterSize;
}
Tracking_Setting 함수를 통한 Servo Motor를 제어하기 위해 위의 Frame을 설계하였고, 가급적 Stabilizer에 실리는 하중을 줄이기 위해 큰 여백을 만들어 두었다.
또한 Airsoft Gun이 동작하며 발생하는 반동을 줄이기 위해 2개의 Servo Motor를 사용하였으며, Tilt의 최대 가동 범위를 위해 Tilt Module에 경사를 만든 것을 볼 수 있다.
Airsoft Gun을 활용하기 위한 기존 계획은 1.5A 건전지 6개를 사용한 별도의 배터리를 사용하여 사격하는 것이었지만, 5V 7A Power Supply를 사용하여도 문제가 없어 기존 회로를 수정하였다.
Airsoft Gun Module 및 각종 Part 배치
Airsoft Gun Module 피스톤 동작
Airsoft Gun Module 탄창 장전
Airsoft Gun Module 사격 동작
시중에서 판매하는 Airsoft Gun을 구매하여, 직접 Modeling 작업을 거쳐 제작한 만큼 치수에 대한 오차도 있었고, 동작 메커니즘의 이해에도 문제가 있었다.
따라서 이를 실험하고 Frame을 출력하기까지 많은 시간이 소요되었다.
// AI_Turret_Final_Code.ino 중 일부
void Airsoft_Gun_Setting() {
if (Pan_Relay_State == 1 && Tilt_Relay_State == 1) {
digitalWrite(relay, HIGH);
delay(130);
digitalWrite(relay, LOW);
delay(200);
}
else {
digitalWrite(relay, LOW);
}
}
S/W를 살펴보면 Airsoft Gun도 마찬가지로 별도의 함수로 사격 기능을 만들어, 코드 상에서의 가독성과 제어의 편리함을 추구한 것을 볼 수 있다.
Airsoft_Gun_Setting 함수를 살펴보면 Pan_Relay_State와 Tilt_Relay_State라는 일종의 Flag가 존재한다.
이는 Tracking_Setting 함수에서 볼 수 있는데, 탐지한 Object를 Tracking하여 미리 지정한 사격 범위에 위치하게 되면 사격을 하는 기능이다. 이때, Tilt와 Pan이 각각의 Flag를 가지게 되는데, 이것이 Pan_Relay_State와 Tilt_Relay_State이다.
2개의 Flag가 1로 활성화 되어야지만 사격을 하는 것을 볼 수 있으며, Airsoft Gun의 Motor를 Relay로 연결하여 직접 제어하였다.
AI Turret은 Tracking이 가능한 Active Mode를 기본값으로 동작을 한다. 해당 Mode에 서는 반동을 최소화하기 위해 Stabilizer는 90°로 고정되어있고, Target Object를 Detection을 하게 된다.
만약 Object가 WebCam에 3초 이상 인식되지 않는다면, Object를 찾기 전까지 주변을 경계하게 된다. 뿐만 아니라 Object가 인식되었을 경우, Object의 B.Box의 Pan과 Tilt를 WebCam 중앙으로 위치시켜 Airsoft Gun으로 사격을 진행한다.
추가로 Display Mode를 만들었는데, 이는 AI Turret System을 종합설계 전시회에 두었을 때를 가정하고 설계하였다. 해당 Mode에서는 Stabilizer가 시연을 위해 동작을 하며, Turret 기능을 제거하여 전시에 적합하도록 설계하였다.
이때, YOLOv5로부터 어떠 한 값을 받아도 Tracking 기능을 하지 않으며, PC와의 연결이 이루어지지 않아도 사용 이 가능하다.
1 학기 중 작성한 회로도와 큰 차이는 없지만, 앞서 말한 듯이 Main Processor를 MacBook을 사용함에 따라 PL2303 Uart Module을 활용하여 ESP32와의 통신을 구성하였다.
뿐만 아니라, Airsoft Gun의 전원을 별도의 외부 전원에서 Power Supply으로 변경하였다. 기존 회로에서 소요되는 전력을 계산해보니 약 6A보다 약간 초과되는수치였다.
따라서 최대 7A가 공급이 가능한 Power Supply가 추가로 Airsoft Gun에도 전력을 공급할 수 있다는 판단으로 위와 같은 회로를 구성하였다.
지금까지 설계 및 제작하였던 Turret 부분과 Stabilizer 부분의 H/W를 종합하여 AI Turret System을 구성하였다. 220 VAC를 사용하는 Power Supply를 그냥 노출시키기엔 위험하기에, Power Supply와 10p 단자대, Kill Switch를 Power 관련 부품을 모두 수납할 수 있는 Case를 만들었다.
또한 Kill Switch를 사용하여, 혹시 모를 전기 관련 사고를 막을 수 있는 수단으로 사용하였다.
위 Power Supply Case를 살펴보면 장시간 사용 시 발생하는 발열을 해결하기 위해, 육각형으로 Frame이 구성된 것을 볼 수 있다.
최종 구성 전에, 어떠한 위치에 각 부품을 배치할지, 미리 SketchUP을 사용하여 임시 배치를 해보았다.
위 그림을 통해 각 부품의 부착 위치를 선정하였고, 각종 배선들을 잘 정리하여 아래의 AI Turret System을 최종 구성하였다.
AI Turret System을 구성한 후, 가장 핵심이 되는 Tracking 및 경계 기능을 Test 해보았다. 당시 조명 환경이 좋지 않아 Pistol을 인식하는 데에 있어 잦은 오류가 있었다. 이를 해결하기 위해 Pistol 후면에 흰 물체를 두어 Pistol의 인식률을 높였다.
AI Turret System의 Pistol Display Test
AI Turret System의 Pistol Tracking Test
실험 결과 정상적으로 Target Object인 Pistol을 잘 추적하는 것을 볼 수 있었다. 뿐만 아니라, Pistol이 사라지고 3초가 지나면 곧 바로 경계 기능이 구동되는 것 또한 확인할 수 있었다.
2 학기 동안 AI Turret System을 만들기 위해 많은 시간과 실험들을 시도하였다. 중간 과정에서 Cluster부터 MacBook으로 Main Processor가 바뀌는 등의 몇몇 사건들이 있었다. 하지만 결과적으로는 H/W부터 YOLOv5와 ESP32의 구동 S/W를 잘 설계하여, 초기 목표였던 Object Tracking과 Stabilizer를 잘 구현한 것을 확인하였다.
앞서 말했듯이, AI Turret System는 YOLOv5를 기반으로 Object를 Detection 및 Tracking을 하기에 어떠한 사물이여도, 학습을 위한 DataSet만 만든다면 충분히 정상 기능을 보여줄 수 있다. 따라서 초기 Target이었던 유해동물부터 현재의 Pistol, 이외에도 여러 사물에 적용할 수 있기에 활용 범위의 스펙트럼이 넓다는 장점이 있다.
아쉬운 점으로는 예산 문제로 더욱 높은 Torque를 낼 수 있는 Servo Motor가 없다는 점이다. 만약 이를 구매하여 Stabilizer를 구현할 수 있었다면, 위 실험에서도 완벽한 반동제어를 하며 연사가 가능했을 것으로 판단된다.