ProcessBuilder를 통해 Python파일 실행하기

0. 사용 이유

  • 프로젝트에 얼굴인식을 수행해 대상의 신원, 나이, 성별을 인식하는 기능을 sk open api를 사용하여 구현이 완료된 상태입니다.
  • 하지만 api 호출을 최소화 하기위해 이미지의 유효성검사를 자체적으로 실행하여 개선 목적으로 ProcessBuilder로 유효성 검사 실행을 도전했습니다.
  • 성능 비교를위해 다음과 같이  임시 test를 진행해보겠습니다.

1. ProcessBuilder란?

  • Process 클래스의 보조 클래스로 start 메서드를 호출하면서ProcessBuilder 클래스의 인스턴스에 정의 된 속성으로 새 프로세스를 만들 수 있습니다. 
  • ProcessBuilder는 동기화 된 클래스가 아니며, 명시적으로 동기화되지 않으면 멀티 스레드로부터 안전하지 않습니다. 
  • cmd로 명령을 수행하거나 외부 파일을 실행할 수 있습니다.
  • 이번 글에서는 python을 실행하는것을 예제로 글을 작성하겠습니다.

2. ProcessBuilder 사용

Process process = null;
try {
    ProcessBuilder pb = new ProcessBuilder(
        "python.exe_path",
        System.getProperty("user.dir")
            + "\\src\\main\\resources\\python\\FaceValidation.py", fileName);
    process  = pb.start();
}catch (Exception e) {
    throw new RuntimeException("프로세스 실행 에러");
}

ProcessBuilder의 매개변수 하나씩 살펴보겠습니다.

  • python.exe_path = "python 인터프리터 실행파일의 위치/python" 즉 실행파일까지 포함하여 작성해야합니다. 폴더까지만 적었다가 개고생..
  • System.getProperty("user.dir")+ "\\src\\main\\resources\\python\\FaceValidation.py" : 실행할 python파일의 위치
  • fileName: python파일로 넘겨줄 매개변수

이렇게 설정하고 실행합니다.

 

try {
        process.waitFor();
    } catch (InterruptedException e) {
        throw new RuntimeException("대기 에러");
    }
    try{
        BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), "euc-kr"));
        String line = "";
        String output = "";

        while((line = reader.readLine()) != null) {
            output += line;
            output += "\n";
        }

        if(!output.equals("")){
            log.info("output= {}", output);
            return FaceValidation.builder()
                .valid(true)
                .build();
        }else{
            throw new InvalidImageException();
        }

    } catch (Exception e) {
        throw new InvalidImageException();
    }

BufferdReader를 이용하여 Python 실행 결과를 가져올 수 있습니다.

output에 encoding 값을 가져오도록 했는데 얼굴이 인식되지 않는다면 InvalidImageException을 던지게됩니다.

인식된다면 FaceValidation이라는 객체에 true값을 담아 메서드가 종료됩니다.
전체 코드는 다음과 같습니다

 

public class FaceValidation {

    private boolean valid;

    public static FaceValidation validation(String fileName){
        Process process = null;
        try {
            ProcessBuilder pb = new ProcessBuilder(
                "python.exe_path",
                System.getProperty("user.dir")
                    + "\\src\\main\\resources\\python\\FaceValidation.py", fileName);
            process  = pb.start();
        }catch (Exception e) {
            throw new RuntimeException("프로세스 실행 에러");
        }
        try {
            process.waitFor();
        } catch (InterruptedException e) {
            throw new RuntimeException("대기 에러");
        }
        try{
            BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), "euc-kr"));
            String line = "";
            String output = "";

            while((line = reader.readLine()) != null) {
                output += line;
                output += "\n";
            }

            if(!output.equals("")){
                System.out.println("output="+output);
                log.info("output= {}", output);
                return FaceValidation.builder()
                    .valid(true)
                    .build();
            }else{
                throw new InvalidImageException();
            }

        } catch (Exception e) {
            throw new InvalidImageException();
        }
    }
}

3. Python 코드

import cv2
import face_recognition
import sys

model_method = 'cnn'

input_string = sys.argv[1]

# image injection
file_name = input_string

# read image
image = cv2.cvtColor(cv2.imread(file_name), cv2.COLOR_BGR2RGB)

# set face boxes
boxes = face_recognition.face_locations(image, model=model_method)

# get encoding file
encodings = face_recognition.face_encodings(image, boxes)

for encoding in encodings:
    print(encoding)

자바의 PeocessBuilder로 넘겨준 매개변수는 sys.argv 배열에 담깁니다
현재 배열에 담겨있는값은 다음과 같습니다
sys.argv[0] : 실행할 python의 경로

sys.argv[1] : fileName

따라서 넘겨준 fileName을받아 python에서도 file_name으로 저장해줍니다.

이후 opencv와 face_recognition을 사용하여 face detection을 진행하고 encoding값을 출력합니다.

 

4. 결과

위 방법을 통해 sk  open api 호출은 확실히 줄일 수 있습니다. 하지만 성능상의 문제가 있었습니다. 제 로컬 스펙으로 위 로직 유무에따른 실행시간 비교가 다음과 같이 차이가 났습니다.
자체적 valid 없는 경우 : 약 0.5초

자체적 valid 있는 경우 : 약 1.9초

 

어느정도 예상했지만 생각보다 성능이 매우 떨어지는걸 확인하여 위 방법은 프로젝트에 사용하기 어렵다고 생각됩니다.

cnn방식이 아닌 hog방식이나 haarcascading 방식등 좀 더 빠르고 가벼운 방식은 안경이나 모자를 쓴 사진은 인식률이 매우떨어지는 등 인식률에 문제가 있어 api 호출이 야기될것이므로 다른 방식의 유효성 검사가 필요할것 같습니다.

'Java > SpringBoot' 카테고리의 다른 글

[SpringBoot] @Transactional을 알아보자  (0) 2024.02.01
[SpringBoot] SSE알림과 비동기  (0) 2023.12.16
스프링 부트 환경변수 설정하기  (0) 2023.09.19