Ubuntu EdgeX 실습(4)

zeusqoi·2026년 9월 15일

개인공부

목록 보기
18/48

앞선 실습에서는 EdgeX의 Application Service를 이용하여 EdgeX 내부의 데이터를 HTTP와 MQTT 방식으로 외부 시스템에 Export하는 과정을 진행했다.

이번에는 그 다음 단계로 EdgeX에서 전달된 이미지 데이터를 TensorFlow Serving과 연결하여 Object Detection을 수행하는 Pipeline을 구성했다.

또한 마지막에는 COCO 2017 validation 이미지 10장을 이용하여 실제 데이터 크기와 Object Detection 시간을 측정해 보았다.


1. 실습 환경 확인

이번 실습의 기본 환경은 EdgeX 2.2 Kamakura를 기준으로 진행했다.

현재 사용 중인 EdgeX 이미지 버전을 확인했다.

sudo docker images | grep edgex

확인 결과 주요 EdgeX 서비스들이 모두 2.2.0 버전으로 구성되어 있었다.

edgexfoundry/app-service-configurable:2.2.0
edgexfoundry/core-command:2.2.0
edgexfoundry/core-data:2.2.0
edgexfoundry/core-metadata:2.2.0
edgexfoundry/device-rest:2.2.0
edgexfoundry/device-usb-camera:2.2.0
...

실습 자료에서는 Ubuntu 20.04 환경을 사용하지만, 현재 실습 환경은 Ubuntu 22.04.5 LTS이다.

lsb_release -a

이번 실습에서는 기존 EdgeX 2.2.0 환경을 그대로 유지하고 진행했다.


2. Python 3.9 환경 구성

실습 자료에서는 Python 3.9을 사용한다.

먼저 현재 환경에서 Python 3.9을 확인했다.

python3.9 --version

그런데 다음과 같이 Python 3.9을 찾을 수 없었다.

python3.9: command not found

현재 Ubuntu 환경의 Python을 직접 변경하는 대신 Conda를 이용하여 별도의 Python 3.9 환경을 만들었다.

conda create -n edgex-tfx python=3.9

생성한 환경을 활성화했다.

conda activate edgex-tfx

이후 Python 버전을 확인했다.

python --version

이렇게 해서 기존 Python 환경은 건드리지 않고 EdgeX + TensorFlow Serving 실습용 Python 3.9 환경을 별도로 구성했다.


3. GPU 확인

TensorFlow Serving Object Detection을 실행하기 전에 GPU 환경을 확인했다.

nvidia-smi

현재 시스템에서는 다음과 같이 NVIDIA GPU가 정상적으로 인식되었다.

NVIDIA GeForce RTX 4070

GPU 메모리도 정상적으로 인식되는 것을 확인했다.

이번 실습에서는 이 GPU 환경에서 TensorFlow Serving을 실행했다.


4. TensorFlow Serving 컨테이너 확인

기존에 만들어 둔 TensorFlow Serving 컨테이너가 있는지 확인했다.

sudo docker ps -a | grep tfserving

처음 확인했을 때 TensorFlow Serving 컨테이너가 다음과 같이 종료된 상태였다.

tfserving    Exited (255)

컨테이너의 로그를 확인했다.

sudo docker logs tfserving

로그에서는 다음과 같이 ssd-mobilenet 모델을 로드하려고 했던 것을 확인할 수 있었다.

model_name: ssd-mobilenet
model_base_path: /models/ssd-mobilenet

그리고 모델의 1 버전을 읽고 있었다.

Successfully loaded servable version
{name: ssd-mobilenet version: 1}

TensorFlow Serving 컨테이너를 다시 시작했다.

sudo docker start tfserving

이후 REST API를 통해 모델 상태를 확인했다.

curl -s http://localhost:8501/v1/models/ssd-mobilenet

결과:

{
  "model_version_status": [
    {
      "version": "1",
      "state": "AVAILABLE",
      "status": {
        "error_code": "OK",
        "error_message": ""
      }
    }
  ]
}

따라서 TensorFlow Serving과 ssd-mobilenet 모델이 정상적으로 실행되고 있는 것을 확인했다.


5. TensorFlow Serving 모델 Metadata 확인

TensorFlow Serving에 어떤 형태의 데이터를 전달해야 하는지 확인하기 위해 Metadata API를 사용했다.

curl -s http://localhost:8501/v1/models/ssd-mobilenet/metadata

확인 결과 serving_default signature를 사용하고 있었다.

입력 Tensor는 다음과 같았다.

input name : image_tensor:0
dtype      : UINT8
shape      : [-1, -1, -1, 3]

출력으로는 다음과 같은 Object Detection 결과가 제공된다.

num_detections
detection_scores
detection_boxes
detection_classes

즉, 이미지의 RGB 배열을 TensorFlow Serving REST Predict API에 전달하면 Object Detection 결과를 받을 수 있는 구조이다.


6. py_rest_server 구성

다음으로 EdgeX에서 전달된 데이터를 받아 TensorFlow Serving으로 전달해주는 Flask 서버를 구성했다.

실습 자료에서는 py_rest_server가 EdgeX에서 전달받은 objectValue를 추출하고 TensorFlow Serving REST API로 전달하는 역할을 한다.

환경변수를 설정했다.

export HOST_IP=203.237.142.21
export model_name=ssd-mobilenet
export THRESHOLD=0.6

각 환경변수의 의미는 다음과 같다.

HOST_IP
→ TensorFlow Serving이 실행되는 서버의 IP

model_name
→ TensorFlow Serving 모델 이름

THRESHOLD
→ Object Detection 결과에서 사용할 confidence threshold

7. py_rest_server 실행 중 발생한 오류

처음 Flask 서버를 실행했을 때 다음 오류가 발생했다.

ModuleNotFoundError: No module named 'flask'

따라서 Flask를 설치했다.

pip install flask

다시 실행했더니 이번에는 다음 오류가 발생했다.

ModuleNotFoundError: No module named 'requests'

Requests도 설치했다.

pip install requests

이후 Flask 서버가 정상적으로 실행되었다.

* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:7770
* Running on http://203.237.142.21:7770

이번 실습에서 py_rest_server는 7770 포트를 사용했다.


8. TensorFlow Serving 요청 형식 오류

처음에는 EdgeX에서 전달받은 objectValue를 TensorFlow Serving에 전달하는 과정에서 오류가 발생했다.

TensorFlow Serving이 요구하는 형식은 다음과 같다.

{
  "instances": [
    ...
  ]
}

그런데 이미 IMAGE to ARRAY for TFX에서 다음과 같은 형태로 만들어진 데이터를 다시 instances 안에 넣어버리는 문제가 있었다.

objectValue
  ↓
{"instances": [...]}
  ↓
{"instances": [{"instances": [...]}]}

이렇게 되면 TensorFlow Serving에서 올바른 입력 Tensor로 인식하지 못한다.

따라서 py_rest_server에서는 objectValue를 그대로 TensorFlow Serving 요청으로 사용하도록 수정했다.

tf_request = object_value

수정 후 TensorFlow Serving REST API 요청이 정상적으로 처리되었다.


9. IMAGE to ARRAY for TFX 구성

기존에 사용했던 Image to Array 프로그램을 TensorFlow Serving REST API 형식에 맞게 수정했다.

기존 형태는 단순히 다음과 같이 이미지 배열을 전달했다.

data = {
    "image": image_array.tolist()
}

이번에는 TensorFlow Serving의 REST Predict 형식에 맞추기 위해 다음과 같이 변경했다.

data = {
    "instances": [
        image_array.tolist()
    ]
}

또한 이미지가 RGB 형태가 되도록 다음과 같이 처리했다.

image = Image.open(filepath).convert("RGB")
image_array = np.array(image, dtype=np.uint8)

최종적으로 다음 구조의 데이터를 EdgeX Device REST로 전달한다.

image
 ↓
PIL Image
 ↓
RGB
 ↓
NumPy Array
 ↓
Python List
 ↓
instances
 ↓
EdgeX Device REST

10. IMAGE to ARRAY for TFX 실행 중 오류

처음 img_to_array_for_tfx 환경을 구성할 때 NumPy와 Pillow 패키지가 필요했다.

따라서 다음 패키지를 설치했다.

pip install numpy pillow

이후 실행했다.

cd ~/img_to_array_for_tfx
python main.py

정상적으로 실행되면 다음과 같이 EdgeX Device REST에서 200 응답을 받을 수 있다.

basic.png
status: 200
response:


11. HTTP Export Endpoint 오류

처음 asc-http-export의 URL은 다음과 같이 설정되어 있었다.

http://203.237.142.21:8080

하지만 현재 py_rest_server는 7770 포트에서 실행되고 있었다.

따라서 HTTP Export 로그에서 다음과 같은 오류가 발생했다.

connect: connection refused

확인 결과 HTTP Export가 존재하지 않는 8080 포트로 요청을 보내고 있었다.

따라서 다음과 같이 수정했다.

http://203.237.142.21:8080

↓

http://203.237.142.21:7770

환경 설정 파일과 Docker Compose 설정을 수정한 후 HTTP Export 서비스를 다시 생성했다.

sudo docker compose up -d --force-recreate app-service-http-export

그리고 실제 컨테이너의 환경변수도 확인했다.

sudo docker exec edgex-app-http-export env | grep WRITABLE_PIPELINE_FUNCTIONS_HTTPEXPORT_PARAMETERS_URL

결과:

WRITABLE_PIPELINE_FUNCTIONS_HTTPEXPORT_PARAMETERS_URL=http://203.237.142.21:7770

이후 HTTP Export가 정상적으로 py_rest_server로 데이터를 전달하기 시작했다.


12. EdgeX → TensorFlow Serving Pipeline 완성

모든 구성을 완료한 후 전체 Pipeline을 연결했다.

COCO Image
    ↓
IMAGE to ARRAY for TFX
    ↓
Device REST
    ↓
EdgeX Core Data
    ↓
MQTT Message Bus
    ↓
asc-http
    ↓
py_rest_server
    ↓
TensorFlow Serving
    ↓
SSD MobileNet
    ↓
Object Detection

첫 번째 실행보다 두 번째 실행이 훨씬 빨라졌다.

따라서 초기 실행과 이후 실행 사이에 추론 환경의 초기화 상태 등에 따라 시간이 달라질 수 있다는 것을 확인할 수 있었다.


13. COCO 2017 이미지 10장 준비

다음으로 실습 자료에서 사용한 COCO 2017 validation 이미지 10장을 이용하여 실험을 진행했다.

사용한 이미지는 다음과 같다.

285.jpg
802.jpg
872.jpg
1000.jpg
1503.jpg
2299.jpg
2685.jpg
5001.jpg
5503.jpg
5586.jpg

별도의 실험 폴더를 만들었다.

mkdir -p ~/coco10

14. COCO 이미지 다운로드 중 인증서 오류

COCO 이미지 서버에서 이미지를 다운로드하기 위해 wget을 사용했다.

wget "https://images.cocodataset.org/val2017/000000000285.jpg"

그런데 다음과 같은 인증서 오류가 발생했다.

오류: 요청한 호스트 이름 ‘images.cocodataset.org’에
일치하는 인증서의 주체 대체 이름이 없습니다.

따라서 해당 환경에서는 인증서 검증 문제로 다운로드가 차단되었다.

실습을 계속 진행하기 위해 다음 옵션을 사용했다.

wget --no-check-certificate \
"https://images.cocodataset.org/val2017/000000000285.jpg" \
-O ~/coco10/285.jpg

이후 정상적으로 다운로드되었다.

200 OK
길이: 335861 (328K)
저장함 [335861/335861]

나머지 9장도 같은 방법으로 다운로드했다.


15. 0바이트 이미지 오류

처음 다운로드 명령에서는 인증서 오류가 발생했지만 -q 옵션 때문에 오류 메시지가 보이지 않았다.

그 결과 파일 이름은 만들어졌지만 실제 내용이 없는 0바이트 파일이 생성되었다.

확인해 보니:

0 /home/namseungjoo/coco10/1000.jpg
...

인증서 검증을 건너뛰어 다시 다운로드한 후에는 모든 이미지가 정상적인 크기를 가지게 되었다.

추가로 테스트 과정에서 만들어진 test285.jpg라는 0바이트 파일이 하나 남아 있었다.

이 파일 때문에 IMAGE to ARRAY for TFX 실행 중 다음 오류가 발생했다.

PIL.UnidentifiedImageError:
cannot identify image file
'/home/namseungjoo/coco10/test285.jpg'

원인은 단순히 0바이트짜리 파일을 이미지로 읽으려고 했기 때문이었다.

따라서 해당 파일을 삭제했다.

rm ~/coco10/test285.jpg

이후 10장의 이미지만 남도록 정리했다.


16. COCO 이미지 원본 크기 측정

10장의 이미지가 정상적으로 다운로드되었는지 확인했다.

ls -lh ~/coco10/*.jpg

원본 파일 크기는 다음과 같았다.

이미지원본 크기
285.jpg328K
802.jpg61K
872.jpg311K
1000.jpg314K
1503.jpg15K
2299.jpg102K
2685.jpg267K
5001.jpg207K
5503.jpg58K
5586.jpg36K

또한 Python을 이용하여 이미지 해상도도 확인했다.


17. TFX 변환 후 데이터 크기 측정

이번 실험에서 가장 눈에 띄었던 부분은 이미지 크기가 크게 증가한다는 것이었다.

JPEG 이미지는 압축된 이미지이지만, TensorFlow Serving에 전달하기 위해 RGB 픽셀 배열로 변환한 뒤 JSON의 instances 형태로 만들었다.

실제 측정 결과는 다음과 같다.

이미지원본TFX 변환 후
285.jpg0.328 MB5.493 MB
802.jpg0.061 MB3.930 MB
872.jpg0.311 MB5.902 MB
1000.jpg0.314 MB4.534 MB
1503.jpg0.015 MB1.161 MB
2299.jpg0.102 MB2.204 MB
2685.jpg0.267 MB5.224 MB
5001.jpg0.207 MB3.951 MB
5503.jpg0.058 MB2.386 MB
5586.jpg0.036 MB1.156 MB

10장 평균으로 보면 원본 이미지는 약 0.174 MB였지만 TFX 형식으로 변환한 후에는 약 3.194 MB가 되었다.

즉 평균적으로 약 18배 이상 데이터가 증가했다.

JPEG처럼 압축된 이미지 파일을 그대로 보내는 것이 아니라 픽셀 하나하나를 배열로 풀어서 JSON으로 전달하기 때문에 데이터 크기가 크게 증가하는 것이다.


18. COCO 10장 Object Detection 실험

이제 준비한 10장의 이미지를 실제 EdgeX Pipeline에 넣었다.

export IMAGES_DIRECTORY=~/coco10

그리고 다음 명령으로 실행했다.

cd ~/img_to_array_for_tfx
python main.py

각 이미지가 EdgeX Device REST에 정상적으로 전달되었다.

status: 200

이후 EdgeX Message Bus와 HTTP Export를 거쳐 py_rest_server에서 TensorFlow Serving으로 전달되었다.


19. Object Detection 시간 측정 결과

각 이미지에 대해 TensorFlow Serving에서 실제 Object Detection을 수행하는 데 걸린 시간을 측정했다.

이미지TFX 크기Detection time
285.jpg5.493 MB0.250 s
802.jpg3.930 MB0.223 s
872.jpg5.902 MB0.317 s
1000.jpg4.534 MB0.200 s
1503.jpg1.161 MB0.252 s
2299.jpg2.204 MB0.239 s
2685.jpg5.224 MB0.277 s
5001.jpg3.951 MB0.114 s
5503.jpg2.386 MB0.396 s
5586.jpg1.156 MB0.165 s

평균 Detection time은 약 0.243초였다.

각 이미지마다 Detection 개수도 확인할 수 있었다.

예를 들어 다음과 같이 결과가 출력되었다.

size : 5.224 MB
time taken : 0.277 sec
throughput : 3.615 image/sec
num of detection default : 8.0
num of detection custom : 2

여기서 num of detection custom은 설정한 THRESHOLD=0.6을 기준으로 계산한 Detection 개수이다.


20. 실험 중 발생한 코드 수정 오류

실험 결과를 이미지별로 정리하기 위해 os.listdir() 순서를 정렬하려고 했다.

처음에는 다음 명령을 실행했지만:

sed -i 's/for filename in os.listdir(IMAGES_DIRECTORY):/for filename in sorted(os.listdir(IMAGES_DIRECTORY)):'

다음 오류가 발생했다.

sed: -e 표현식 1번째 행, 101번째 문자:
`s' 명령이 끝나지 않았습니다

원인은 sed의 치환 명령에서 구분자인 /가 제대로 닫히지 않았기 때문이었다.

다만 기존 main.py를 다시 수정할 필요성이 크지 않았기 때문에 최종 실험에서는 기존 동작을 유지했다.

실험 결과는 TFX 데이터 크기와 출력된 결과를 기준으로 이미지별로 정리했다.


21. 실험 결과 정리

이번 실험에서 실제로 확인한 결과는 다음과 같다.

원본 이미지

평균 약 0.174 MB

TFX 변환 후

평균 약 3.194 MB

평균 데이터 증가

약 18.4배

평균 TensorFlow Serving Detection time

약 0.243 sec

특히 이미지 파일 자체의 크기가 작더라도 TFX 형식으로 변환하면 데이터 크기가 상당히 커지는 것을 확인할 수 있었다.

예를 들어 1503.jpg는 원본이 약 0.015 MB였지만 TFX 형식으로 변환한 후에는 약 1.161 MB가 되었다.

반대로 872.jpg는 원본 약 0.311 MB에서 TFX 변환 후 약 5.902 MB가 되었다.

즉, JPEG 파일 크기만으로 실제 EdgeX에서 전달되는 데이터 크기를 판단할 수 없었다.


22. PDF 실험 결과와 비교

실습 자료에서도 동일한 COCO 2017 validation 이미지 10장을 이용하여 원본 이미지 크기와 TFX 변환 후 크기, Object Detection 시간 등을 측정했다.

예를 들어 실습 자료에서 285.jpg는 다음과 같았다.

Original : 0.336 MB
TFX      : 5.760 MB

이번 실습에서 실제 측정한 결과는:

Original : 0.328 MB
TFX      : 5.493 MB

802.jpg의 경우에도:

실습 자료
0.062 MB → 4.121 MB

이번 실습
0.061 MB → 3.930 MB

872.jpg 역시:

실습 자료
0.318 MB → 6.189 MB

이번 실습
0.311 MB → 5.902 MB

처럼 전체적인 경향은 상당히 유사했다.

다만 Object Detection 시간은 실습 자료와 동일하지 않았다.

실습 자료의 환경과 현재 환경의 GPU, 시스템 구성, TensorFlow Serving 상태 등이 다르기 때문에 동일한 숫자가 나오는 것은 아니다.

따라서 이번 실험에서는 실습 자료의 수치를 그대로 사용하는 것이 아니라 현재 환경에서 실제로 측정한 값을 결과로 기록했다.


마무리

이번 실습에서는 단순히 TensorFlow Serving을 실행하는 것에서 끝나는 것이 아니라, EdgeX와 TensorFlow Serving을 실제 Pipeline으로 연결해 보았다.

최종적으로

이미지
 ↓
TFX 형식 변환
 ↓
EdgeX
 ↓
Message Bus
 ↓
HTTP Export
 ↓
Flask Server
 ↓
TensorFlow Serving
 ↓
Object Detection

의 전체 흐름을 직접 구현했다.

특히 COCO 이미지 10장을 이용한 실험에서는 JPEG 이미지를 TensorFlow Serving에서 사용할 수 있는 배열 형태로 변환하면서 데이터 크기가 크게 증가하는 것을 확인했다.

이번 실습을 통해 EdgeX가 수집한 데이터를 Message Bus와 Application Service를 통해 외부 AI/ML 시스템으로 전달하여 Object Detection과 같은 AI 처리를 수행하는 Pipeline으로 확장될 수 있다는 것을 확인할 수 있었다.

0개의 댓글