4 분 소요

코드리뷰

7월28일까지 확보한 주요 코드에 대한 정리글이다.

1. Yolov5 코드

1.1) 코드분석

import cv2
import torch
import numpy as np
  • Pytorch는 Opencv에 대해서 하위의존성이 걸려있기에 파이토치를 독립적으로는 쓸 수 없다. 근데 yolov3까지는 cv2만 사용했으나, yolov5부터는 cv2뿐만 아니라 파이토치까지 의존해서 사용하게 된다.
# YOLOv5 모델 로드
def load_yolov5():
    model = torch.hub.load('ultralytics/yolov5', 'yolov5') # yolov5, yolov5m, yolov5l, yolov5x 중 선택 가능
	return model
  • YOLOv5 모델을 로드하는 함수이다.(해당 욜로모델이 있는 깃허브링크)
# 객체 탐지 수행
def detect_objects(model, frame):
	results = model(frame)
	return results
  • 객체를 탐지하는 함수이다.
def draw_labels(results, frame):
	label, cords = results, xyxyn[0][:, -1].numpy(), results.xyxyn[0][:, :-1].numpy()
	n = len(labels)
	x_shape, y_shape = frame.shape[1], frame.shape[0]
	
	for i in range(n):
		row = cords[i]
		if row[4] >= 0.5: # confidence threshold
			x1, y1, x2, y2 = int(row[0] * x_shape), int(row[1] * y_shape), int(row[2] * x_shape), int(row[3] * y_shape)
			bgr = (0, 255, 0)
			cv2.rectangle(frame, (x1, y1), (x2, y2), bgr, 2)
			cv2.putText(frame, f'{results.names[int(labels[i])]} {row[4]:.2f}', (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.9, bgr, 2)
	
	return frame
  • 라벨과 라운딩 박스를 그리는 함수이다.
def main():
	# 1.비디오 스트림 객체
	cap = cv2.VideoCapture("http://192.168.43.8090/?action=stream")
	if not cap.isOpened():
		print("카메라를 열 수 없습니다.")
		return -1
		
	# 2.욜로 모델 로드
	model = load_yolov5()
	
	# 3.메인루프
	while True:
		ret, frame = cap.read()
		if not ret:
			break
		
		# 4.객체탐지 및 라벨 그리기
		results = detect_objects(model, frame)
		frame = draw_labels(results, frame)
		
		# 5.화면에 프레임 표시
		cv2.imshow('video', frame)
		if cv2.waitkey('video', frame) # ESC 키를 누르면 종료
			break
	# 6.자원 해제 및 창 닫기
	cap.release()
	cv2.destroyAllWindows()

if __name__ == "__main__":
	main()
  1. 비디오 스트림 캡처 cv2파일에 있는 VideoCapture() 메소드를 써서 URL로부터 비디오 스트림을 캡처한다 (여기서는 캡처한다고 하지만, 이러면 맥락상 한번만 캡처하고 끝나는 걸로 착각할 수 있다. 따라서 스트리밍되고 있는 비디오를 참조해서 포인팅함으로써 밑에 포문을 통해연속적으로 이미지를 받아온다고 생각하는데 더 아다리가 맞다.)
  2. 욜로 모델 로드 load_yolov5() 메소드의 정의부를 보면 torch.hub.load() 를 통해 파이토치의 허브기능을 이용하여 사전 학습된 모델을 로드한다.
  3. 메인 루프 무한루프를 사용하여 연속적으로 프레임을 읽어들이는데, 이때 cap.read() 를 써서 프레임을 읽어들이는 것을 시도한다. 읽어들이는 것을 시도한다. 읽어들이기에 성공하면 ret에는 True가, frame에는 읽어들인 프레임이 저장되는데, 만약 읽어들이기에 실패하면, ret에는 False가, frame에는 none이 된다.
  4. 객체탐지 및 라벨 그리기 detect_objects() 는 프레임과 그 프레임을 처리할 모델을 입력으로 넣는다. 따라서 results 에는 탐지된 객체정보를 담고 있고, frame에는 draw_labels()에 의해 그 정보를 바탕으로 라운딩 박스와 라벨들을 그려넣는다.
  5. 화면에 프레임 표시 imshow() 를 통해 video 라는 window에 frame을 표시한다. 한 프레임이 지나갈때마다 1초씩 키입력을 기다리면서 만약 ESC가 눌리면 종료한다.
  6. 자원 해제 및 창 닫기 비디오 캡처 객체(리소스)를 해제한다. 이후 여기에 물려있던 OpenCV창을 close한다.

1.2) 전체코드

import cv2
import torch
import numpy as np

def load_yolov5():
    # YOLOv5 모델 로드
    model = torch.hub.load('ultralytics/yolov5', 'yolov5s')  # yolov5s, yolov5m, yolov5l, yolov5x 중 선택 가능
    return model

def detect_objects(model, frame):
    # 객체 탐지 수행
    results = model(frame)
    return results

def draw_labels(results, frame):
    labels, cords = results.xyxyn[0][:, -1].numpy(), results.xyxyn[0][:, :-1].numpy()
    n = len(labels)
    x_shape, y_shape = frame.shape[1], frame.shape[0]

    for i in range(n):
        row = cords[i]
        if row[4] >= 0.5:  # confidence threshold
            x1, y1, x2, y2 = int(row[0] * x_shape), int(row[1] * y_shape), int(row[2] * x_shape), int(row[3] * y_shape)
            bgr = (0, 255, 0)
            cv2.rectangle(frame, (x1, y1), (x2, y2), bgr, 2)
            cv2.putText(frame, f'{results.names[int(labels[i])]} {row[4]:.2f}', (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.9, bgr, 2)

    return frame

def main():
    cap = cv2.VideoCapture("http://192.168.43.142:8090/?action=stream")
    if not cap.isOpened():
        print("카메라를 열 수 없습니다.")
        return -1

    model = load_yolov5()

    while True:
        ret, frame = cap.read()
        if not ret:
            break

        results = detect_objects(model, frame)
        frame = draw_labels(results, frame)

        cv2.imshow('video', frame)
        if cv2.waitKey(1) == 27:  # ESC 키를 누르면 종료
            break

    cap.release()
    cv2.destroyAllWindows()

if __name__ == "__main__":
    main()

2. mjpg-streamer 코드

해당 코드는 단순히 스트리밍용 오픈소스코드라 따로 검토해보지 않았다.

만약 나중에 경량화를 목적으로 필요하다고 하면은 그떄 다시 코드리뷰를 진행할까 한다.

현재는 해당 코드를 어떻게 실행하는지만 정리해놓았다.(링크)

스트리밍용 오픈소스 코드로 mjpg-streamer가 유명하다.

그 사용방법은 다음과 같다.

2.1)사용방법

  1. 필수 패키지 설치
    sudo apt-get install libjpeg-dev imagemagick libv4l-dev cmake
    
  2. 소스코드 다운로드 홈디렉토리 아래에 깃클론을 해주었다.

    git clone https://github.com/jacksonliam/mjpg-streamer.git
    
  3. 빌드 및 설치 깃클론을 해주었다면, 빌드용 파일이 생성되었을 것이다. 해당 경로로 이동하여 빌드를 해주었다.

    cd mjpg-streamer/mjpg-streamer-experimental
    make CMAKE_BUILD_TYPE=Debug
    

    위에서 빌드를 진행했으므로 셋업파일이 생성되었다. 해당 셋업파일을 설치해주었다.

    sudo make install
    
  4. 실행 아래 명령어로 실시간 스트리밍 서버를 실행시킬 수 있었다.

    mjpg_streamer -i "input_uvc.so -d /dev/video0" -o "output_http.so -w /usr/local/share/mjpg-streamer/www/"
    

    위 명령어에서 별도로 설정옵션을 추가하지 않으면, 8080포트로 http서버가 열리게 된다.

2.2)결과

localhost:8080 또는 **<라즈베리파이의 ip="">:8080**를 통해 아래 서버에 접속할 수 있다.

brave_bARCa4lYaw

더 나아가 주소 끝에 ?action=stream 을 추가하여 비디오 스트림만 가져올 수 있었다. 추후에 해당 코드를 가져와서 node.js 서버에 실시간 스트리밍 할 수 있는 방법을 찾아보아야 할 것이다.

3. 휴봇 스텝모터 각도제어 코드

3.1)코드분석

#include <Servo.h>
  • 서보모터를 제어하기 위한 라이브러리
Servo myservo;
  • 서보모터 제어를 위한 객체 생성
void setup() {
	Serial.begin(9600); // start serial communication at 9600 bps
	myservo.attach(9);  // attaches the servo on pin 9 to the servo object
	Serial.println("Enter 1 for 0 degrees, 2 for 90 degrees, 3 for 180 degrees:");
}
  • 초기설정에 쓰이는 코드로, 9600bps의 속도로 시리얼 통신을 시작한다
  • 9번핀에 연결후, 0도부터 90도, 180도까지의 각도로 제어하는 멘트를 제공해주었다.
void loop() {
	if (Serial.available() > 0){
		int input = Serial.parseInt();	// read the input
		switch (input) {
			case 1:
				myservo.write(0); 		// move servo to 0 degrees
				Serial.println("Servo moved to 0 degrees");
				break;
			case 2:
				myservo.write(90); 		// move servo to 90 degrees
				Serial.println("Servo moved to 90 degrees");
				break;
			case 3:
				myservo.write(180);		// move servo to 180 degrees
				Seial.println("Servo moved to 180 degrees");
				break;
			default:
				Serial.println("Invalid input, please enter 1, 2, or 3.");
				break;
		}
		delay(500); 	// wait for half a second before the next read
	}
}
  • Serial.available() 의 반환값이 0이면 입력이 없고, 1 이상이면 입력이 있는 것으로 구분해서 볼 수 있다.
  • parseInt() 로 시리얼 입력을 정수로 받아들인다. (원래는 모든 값을 문자로 받아들이기 때문이다)
  • 이제 정수로 저장한 그 값이 1인지, 2인지, 3인지에 따라 알맞은 동작을 수행하도록 한다.
  • 해당 동작을 하고 바로 다음 동작을 받는 것이 아니다. 500ms 동안 딜레이를 주었는데, 이는 동작의 신뢰성을 보장하기 위함이다. (빠르게 두개의 동작이 들어오면 두 동작 중 하나는 묻혀버릴 가능성이 존재한다.)

3.2) 전체코드

#include <Servo.h>

/* 
휴봇 스텝모터 각도제어
*/ 

Servo myservo;  // create servo object to control a servo

void setup() {
  Serial.begin(9600);  // start serial communication at 9600 bps
  myservo.attach(9);   // attaches the servo on pin 9 to the servo object
  Serial.println("Enter 1 for 0 degrees, 2 for 90 degrees, 3 for 180 degrees:");
}

void loop() {
  if (Serial.available() > 0) {
    int input = Serial.parseInt();  // read the input
    switch (input) {
      case 1:
        myservo.write(0);  // move servo to 0 degrees
        Serial.println("Servo moved to 0 degrees");
        break;
      case 2:
        myservo.write(90);  // move servo to 90 degrees
        Serial.println("Servo moved to 90 degrees");
        break;
      case 3:
        myservo.write(180);  // move servo to 180 degrees
        Serial.println("Servo moved to 180 degrees");
        break;
      default:
        Serial.println("Invalid input, please enter 1, 2, or 3.");
        break;
    }
    delay(500);  // wait for half a second before the next read
  }
}

4. 컨베이어벨트 스테모터 각도제어 코드

댓글남기기