이번 포스팅은 deepstream 6.0을 이용한 PYTHON 예제를 실행 할 수 있는 환경을 docker에서 구성하는 과정을 담는다.

분명 하라는 instruction에서 하라는 대로 했지만 실패 해서 이 문서 저문서를 참고해 환경 구성에 성공했다.

 

deepstream 이 뭔지가 궁금한 분들은 아래 페이지를 읽어 보도록 하자. 내가 설명 하는 것 보다는 nvidia의 설명을 그대로 읽는게 더 도움이 될것 같다.

deepstream: https://developer.nvidia.com/deepstream-sdk

 

NVIDIA DeepStream SDK

Build and deploy AI-powered Intelligent Video Analytics apps and services. DeepStream offers a multi-platform scalable framework to deploy on the edge or connect to any cloud.

developer.nvidia.com

 

실패의 경험으로 부터 찾은 성공 방법이 누군가에게 도움이 되길 바란다.

 

목표:

deepstream 6.0 docker 이미지에 python 예제를 실행 할수 있도록 deepstream-python-apps을 바인딩

deepstream 6.0부터는 python binding이 기본 제공이 안된다고 한다.

그래서 python api 를 바인딩하고 예제를 돌려 봄으로서 정상적으로 binding 된것을 확인하는게 목표이다.

환경

나는 jetson nano에 도커를 이용해 환경을 구성했다.

 

host 환경

hardware: jetson nano

jetpack: 4.6(r32.6.1)

cudnn:8.2.1

cuda: 10.2

 

위 호스트 환경에서 deepstream-l4t docker이미지를 다운 받아 python 예제 실행을 위해 python binding을 진행했다.

 

docker version: 20.10.2, build 20.10.2-0ubuntu1~18.0.2

base image: nvcr.io/nvidia/deepstream-l4t:6.0-samples

아래 명령으로 받을 수있다. 

docker pull nvcr.io/nvidia/deepstream-l4t:6.0-samples

 

성공적으로 python 바인딩을 빌드 하기 위해 나는 호스트와 컨테이너(docker container)내부에서 필요 라이브러리나 패키지를 설치 했다.

 

따라서 호스트 사이드에서한 작업과 컨테이너 내부에서 한 작업을 각 섹션에서 설명한다. 

 

Host side

호스트에서 가장 먼저 한일은 nvidia-l4t-gstreamer를 설치 하는 것이었다.

(사실 이과정이 정확히 필요한지는 모르겠다.  아래 실패 사례의 기록에서 보면 gst-python 빌드 과정에서 gst/gst.h를 찾을수 없다는 에러가 발생해 그걸 해결하는 과정에서 이걸 설치 하게되었다. 혹 이게 필요 없는 과정이라면 댓글로 알려주시면 참 고맙겠습니다.)

우선 vi나 vim으로 아래 파일을 열고

 vi /etc/apt/sources.list.d/nvidia-l4t-apt-source.list

아래 두 문장을 파일에 쓰고 저장한다. 

이때 두번째 줄에 'jetson/t210'이라고 적은 부분이 있는데  't210' 은 jetson nano를 지칭하는 platform 구분자이다. tx2나 xavier는 다른 플렛폼 구분자를 써줘야 한다.

deb https://repo.download.nvidia.com/jetson/common r32.6 main
deb https://repo.download.nvidia.com/jetson/t210 r32.6 main

그리고 나서 아래 명령 실행한다.

apt update

실행 시 아래와 같은 에러를 마주 치는 경우가 있다.

Fig 1. NO_PUBKEY Error

Err:1 https://repo.download.nvidia.com/jetson/common r32.6 InRelease
  The following signatures couldn't be verified because the public key is not available: NO_PUBKEY 0D296FFB880FB004
Err:2 https://repo.download.nvidia.com/jetson/t210 r32.6 InRelease
  The following signatures couldn't be verified because the public key is not available: NO_PUBKEY 0D296FFB880FB004
Hit:4 http://ports.ubuntu.com/ubuntu-ports bionic-updates InRelease
Hit:5 http://ports.ubuntu.com/ubuntu-ports bionic-backports InRelease
Hit:6 http://ports.ubuntu.com/ubuntu-ports bionic-security InRelease
Reading package lists... Done                      
W: GPG error: https://repo.download.nvidia.com/jetson/common r32.6 InRelease: The following signatures couldn't be verified because the public key is not available: NO_PUBKEY 0D296FFB880FB004
E: The repository 'https://repo.download.nvidia.com/jetson/common r32.6 InRelease' is not signed.
N: Updating from such a repository can't be done securely, and is therefore disabled by default.
N: See apt-secure(8) manpage for repository creation and user configuration details.
W: GPG error: https://repo.download.nvidia.com/jetson/t210 r32.6 InRelease: The following signatures couldn't be verified because the public key is not available: NO_PUBKEY 0D296FFB880FB004
E: The repository 'https://repo.download.nvidia.com/jetson/t210 r32.6 InRelease' is not signed.
N: Updating from such a repository can't be done securely, and is therefore disabled by default.
N: See apt-secure(8) manpage for repository creation and user configuration details.

위 에러를 해결 하기 위해 나는 아래와 같은 명령을 입력했다.  repo에 접근 하기 위한 public key를 받아 오라는 명령이다.

 apt-key adv --fetch-key https://repo.download.nvidia.com/jetson/jetson-ota-public.asc

위 명령으로 에러를 해결하고 난후 'apt update' 명령은 아래 처럼 에러 없이 수행된다.

Hit:1 http://ports.ubuntu.com/ubuntu-ports bionic InRelease                                                 
Get:2 https://repo.download.nvidia.com/jetson/common r32.6 InRelease [2555 B]                               
Hit:3 http://ports.ubuntu.com/ubuntu-ports bionic-updates InRelease                
Get:4 https://repo.download.nvidia.com/jetson/t210 r32.6 InRelease [2547 B]
Hit:5 http://ports.ubuntu.com/ubuntu-ports bionic-backports InRelease              
Hit:6 http://ports.ubuntu.com/ubuntu-ports bionic-security InRelease    
Get:7 https://repo.download.nvidia.com/jetson/common r32.6/main arm64 Packages [17.4 kB]
Get:8 https://repo.download.nvidia.com/jetson/t210 r32.6/main arm64 Packages [9493 B]

업데이트가 끝나면 목표로 처음 목표 였던 nvidia-l4t-gstreamer를 설치 한다.

sudo apt install --reinstall nvidia-l4t-gstreamer

 

 

Inside docker

도커를 실행 하기 위해 내가 사용한 명령 및 옵션은 아래와 같다.

docker

 

컨테이너 내부에서도 가장 먼저 한일은 gstreamer관련 툴, 라이브러리, 패키지 등을 설치하는 것이다.

host side에서 nvidia-l4t-gstreamer를 설치 한 것과 같은 이유로 gst-python을 빌드 하는 과정에서

gst/gst.h 와 관련된 에러가 나오는데 이 문제를 해결하기 위한 방법이다.

 

검색을 한참 해보고 nvidia측에서 한 답변과 인터넷의 똑똑한 분들이 추천한 방법들을 많이 시도해 봤지만

내 경우에 그 제안들은 해결방법이 아니었기 때문에 아래 문서를 보고 gstreamer와 관련된 걸 전부다 설치 했다.

 

최소한의 설치로 이 문제를 해결하면 edge device의 특성상 disk space를 아낄 수 있겠지만 시간 관계상 급한데로 전부 설치 했다.

 

GStreamer-1.0 Installation and Setup

 

generic-no-api_r2

Your browser has DOM storage disabled. Make sure DOM storage is enabled and try again.

docs.nvidia.com

 

위와 같은 이유로 나는 아래 명령을 컨테이너 내부에서 실행해 gstreamer와 관련된 패키지들을 설치 했다.

apt-get update
apt-get install gstreamer1.0-tools gstreamer1.0-alsa \
  gstreamer1.0-plugins-base gstreamer1.0-plugins-good \
  gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly \
  gstreamer1.0-libav
apt-get install libgstreamer1.0-dev \
  libgstreamer-plugins-base1.0-dev \
  libgstreamer-plugins-good1.0-dev \
  libgstreamer-plugins-bad1.0-dev

그리고 아래 패키지들도 추가로 설치 했다. (나는 이걸 컨테이너 내부에서 설치했는데 nvidia 측에서는 이건 host에 설치해야 하는 거고 docker 옵션 중 --runtime nvidia 를 사용 하면 아래 명령으로 설치한 것들이 container에 자동으로 마운트 되는 구조라고 한다. )

apt install libssl1.0.0 libgstreamer1.0-0 libgstrtspserver-1.0-0 libjansson4=2.11-1

그리고 나서 아래 link의 안내를 따라 진행했다.

link:https://github.com/NVIDIA-AI-IOT/deepstream_python_apps/tree/master/bindings

 

GitHub - NVIDIA-AI-IOT/deepstream_python_apps: DeepStream SDK Python bindings and sample applications

DeepStream SDK Python bindings and sample applications - GitHub - NVIDIA-AI-IOT/deepstream_python_apps: DeepStream SDK Python bindings and sample applications

github.com

apt install -y git python-dev python3 python3-pip python3.6-dev python3.8-dev cmake g++ build-essential \
    libglib2.0-dev libglib2.0-dev-bin python-gi-dev libtool m4 autoconf automake
    
cd /opt/nvidia/deepstream/deepstream-6.0/sources
git clone https://github.com/NVIDIA-AI-IOT/deepstream_python_apps.git
cd deepstream_python_apps
git submodule update --init

Installing Gst-python

cd 3rdparty/gst-python/
./autogen.sh
make
make install

 Building the bindings

cd deepstream_python_apps/bindings
mkdir build
cd build
cmake ..
make

이러고 나니 이렇게 에러 없이 빌드를 성공할 수 있었다.

 

Fig 2. build bindings 성공 화면

 

예제 실행

위 설명에서 build bindings 까지 성공하고 나면 아래 그림 처럼 build 폴더내에 'pyds.so' 파일이 생성된다.

Fig 3. pyds.so 생성된 모습

생성된 pyds.so 파일은  /opt/nvidia/deepstream/deepstream-6.0/lib 에 복사 해줘야 한다.

아래 명령에서 나는 상대 경로를 사용했다. pyds.so 파일이 최종적으로 위치 해야 하는 곳에 복사만 해주면 된다.

cp pyds.so ../../../../lib/
# 또는
# cp pyds.so /opt/nvidia/deepstream/deepstream-6.0/lib

만약 pyds.so 파일을 저 위치에 복사 하지 않으면 아래와 같은 에러 메시지를 보게 될 것이다.

내가 실제로 봤다...  저거 복사하라는 말은 instruction에서 못봤는데...

 

Traceback (most recent call last):
File “deepstream_test_1_usb.py”, line 29, in
import pyds
ModuleNotFoundError: No module named ‘pyds’

 

pyds.so 복사 하고 난 후 python 예제가 실행 되는 지 확인 하기 위해 아래 파일을 실행했다.

example path : /opt/nvidia/deepstream/deepstream-6.0/sources/deepstream_python_apps/apps/deepstream-test1-usbcam

cd /opt/nvidia/deepstream/deepstream-6.0/sources/deepstream_python_apps/apps/deepstream-test1-usbcam
python3 deepstream_test_1_usb.py /dev/video0

Fig 4. 예제 실행 후 warning이 뜨는 화면

예제를 실행 하고 나면 gstreamer warning이 마구 발생하며 꼭 실행이 안될 것 처럼 log가 나온다.

하지만 실행은 된다.

아래는 실행 화면 인다. 아이패드로 유재석 씨 사진을 띄워 카메라 앞에 두니 사람으로 인식하는 모습을 볼 수 있다.

Fig 5. 예제 실행 후 사람 인식 모습

이렇게 deepstream 6.0 python예제를 실행 시키는 방법을 알아 봤다.

이제 예제 코드를 분석해 sdk 사용법을 파악 한 후 본 프로젝트 개발을 시작해야겠다.

 

 

아래는 첫 시도에서 실패 했던 과정에 대한 기록이다.

더보기

down load deepstream 6.0-l4t sample container:

docker pull nvcr.io/nvidia/deepstream-l4t:6.0-samples

앞으로의 내용은 거의 아래 link를 따라 한것 인데 나는 이걸 따라 해서 실패 했다. 그래서 이 문서 저문서를 참고해 성공한 방법이 위 포스팅 내용이었다.

 

GitHub - NVIDIA-AI-IOT/deepstream_python_apps: DeepStream SDK Python bindings and sample applications

DeepStream SDK Python bindings and sample applications - GitHub - NVIDIA-AI-IOT/deepstream_python_apps: DeepStream SDK Python bindings and sample applications

github.com

python binding installation

apt-get install python-gi-dev
export GST_LIBS="-lgstreamer-1.0 -lgobject-2.0 -lglib-2.0"
export GST_CFLAGS="-pthread -I/usr/include/gstreamer-1.0 -I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include"

apt-get install python3-dev libpython3-dev
apt-get install libtool m4 automake 
apt install libgstreamer1.0-dev

git clone https://github.com/GStreamer/gst-python.git
cd gst-python
git checkout 1a8f48a
./autogen.sh PYTHON=python3
./configure PYTHON=python3
make
make install

위 명령을 실행 시키다 보면 아래와 같은 에러가 발생한다.

libtoolize:   error: One of these is required:
libtoolize:                 gm4 gnum4 m4
libtoolize:   error: Please install GNU M4, or 'export M4=/path/to/gnu/m4'.

그래서 아래 패키지를 설치해줬다.

apt-get install libtool m4 automake

 

설치후 다시 gst-python 을 빌드하면 다시 아래와 같은 error가 발생한다.

정확히는 './autogen.sh PYTHON=python3' 명령 실행 후 발생한다.

checking for headers required to compile python extensions... not found
configure: error: could not find Python headers
  configure failed

그래서 아래 명령을 통해 필요한 패키지들을 설치

apt-get install python3-dev libpython3-dev

그리고 나면 './autogen.sh PYTHON=python3' 명령은 무사히 넘어 갈수 있다.

 

그러나 './configure PYTHON=python3' 실행시 문제의 아래 error가 발생한다.

make[3]: Entering directory '/opt/nvidia/gst-python/gi/overrides'
  CC       _gi_gst_la-gstmodule.lo
gstmodule.c:31:10: fatal error: gst/gst.h: No such file or directory
 #include <gst/gst.h>
          ^~~~~~~~~~~
compilation terminated.

위 에러에 대한 해결 방법을 검색 하면 아래 페이지를 볼 수 있다. 하지만 시는데로 해도 문제가 해결되지 않아 나는 위에서 열거한 그 많은 gstreamer 관련 패키지를 모두 설치 한것이다....

link:https://forums.developer.nvidia.com/t/no-such-file-or-directory-include-gst-gst-h/156423/6

 

No such file or directory #include <gst/gst.h>

cc -c -o deepstream_app.o -DPLATFORM_TEGRA -I./ -I…/…/apps-common/includes -I…/…/…/includes -DDS_VERSION_MINOR=0 -DDS_VERSION_MAJOR=5 pkg-config --cflags gstreamer-1.0 gstreamer-video-1.0 x11 json-glib-1.0 deepstream_app.c Package gstreamer-video

forums.developer.nvidia.com

apt-get install libgstreamer1.0-dev libjson-glib-dev

여기 까지가 실패했던 첫번째 시도의 내용이다.

그리고 container를 종료 하고 인터넷을 열심히 검색해 이문서 저문서의 내용을 종합해 python bindings 빌드에 성공한 내용이 이번 포스팅의 내용이다.

 

결론 부터 말하면 이 시도는 실패에 대한 기록을 담고 있다.

목적은 fastMot를 jetson nano에서 실행해 응용 프로그램에서 사용 하는 것이었는데 jetson nano의 performance를 잘못 파악했고 내 실수 인지 버그인지 inference 수행 시 메모리 부족 에러가 발생하고 정상 실행되지 않는다.

 

차후 이건 다시 시도 해보고 성공하면 성공 기록을 정리해 남겨야겠다.

 

결과

(이 포스팅 따라하시면 같은 사유로 inference 시 저와 같은 문제가 발생 할 수 있음을 분명히 미리 말씀드립니다. )

실행 할 수있게 각종 dependencies들을 설치 하고 빌드는 성공했으나 inference 수행히 메모리 에러 발생하며 실행 종료됨

 

목표:

아래 리포지토리의 모델 inference가 가능한 환경을 도커로 세팅해 jetson nano에서 사용 할수 있게 배포

target:https://github.com/GeekAlexis/FastMOT

 

GitHub - GeekAlexis/FastMOT: High-performance multiple object tracking based on YOLO, Deep SORT, and KLT 🚀

High-performance multiple object tracking based on YOLO, Deep SORT, and KLT 🚀 - GitHub - GeekAlexis/FastMOT: High-performance multiple object tracking based on YOLO, Deep SORT, and KLT 🚀

github.com

환경

 

host 환경

hardware: jetson nano

jetpack: 4.6(r32.6.1)

cudnn:8.2.1

cuda: 10.2

 

위 호스트 환경에서 deepstream-l4t docker이미지를 다운 받아 python 예제 실행을 위해 python binding을 진행했다.

 

docker version: 20.10.2, build 20.10.2-0ubuntu1~18.0.2

base image: nvcr.io/nvidia/l4t-ml:r32.5.0-py3

docker pull nvcr.io/nvidia/l4t-ml:r32.5.0-py3

base image에는 다음과 같은 패키지가 설치되어있다.

더보기
  • TensorFlow 1.15
  • PyTorch v1.7.0
  • torchvision v0.8.0
  • torchaudio v0.7.0
  • onnx 1.8.0
  • CuPy 8.0.0
  • numpy 1.19.4
  • numba 0.52.0
  • OpenCV 4.1.1
  • pandas 1.1.5
  • scipy 1.5.4
  • scikit-learn 0.23.2
  • JupyterLab 2.2.9

디펜던시 설치

1. cupy 9.2 설치

installed version:8.0.0b4

required version: 9.2

fastMot 를 실행 하는데 필요한 cupy로 버전 업데이트 진행

pip3 install --upgrade cython
pip3 install cupy==9.2

2. numba 버전 변경

installed version: '0.52.0'

required version: '0.48'

컨테이너에 기본 설치 된 numba 버전이 필요 버전보다 상위 버전이므로 다운 그레이드

아래 첫번째 명령에서 'apt install llvm-**X** ' 이걸 실행했는데 이러면 현 os와 호환가능한 등록된 모든 버전이 설치된다.

버전을 명시해서 설치하기 바란다...

# llvmlite requires below libraries
apt install llvm-**X** 

#set LLVM_CONFIG path
export LLVM_CONFIG=/usr/bin/llvm-config-7 

#install llvmlite
pip3 install llvmlite==0.31.0
pip3 install numba==0.48

 

Inference 시도

 

[ WARN:0] global /home/nvidia/host/build_opencv/nv_opencv/modules/videoio/src/cap_gstreamer.cpp (1757) handleMessage OpenCV | GStreamer warning: Embedded video playback halted; module v4l2src0 reported: Internal data stream error.
[ WARN:0] global /home/nvidia/host/build_opencv/nv_opencv/modules/videoio/src/cap_gstreamer.cpp (886) open OpenCV | GStreamer warning: unable to start pipeline
[ WARN:0] global /home/nvidia/host/build_opencv/nv_opencv/modules/videoio/src/cap_gstreamer.cpp (480) isPipelinePlaying OpenCV | GStreamer warning: GStreamer: pipeline have not been created
Traceback (most recent call last):
  File "app.py", line 120, in <module>
    main()
  File "app.py", line 66, in main
    stream = fastmot.VideoIO(config.resize_to, args.input_uri, args.output_uri, **vars(config.stream_cfg))
  File "/home/FastMOT/fastmot/videoio.py", line 84, in __init__
    raise RuntimeError('Unable to read video stream')
RuntimeError: Unable to read video stream

디펜던시를 필요 버전으로 설치 하고 inference를 실행 하면 위와 같은 에러가 발생했다.

혹시나 카메라 문제인가 하고  코드에서 카메라 입력을 받아 오는 부분만 따로 때서 아래와 같이 테스트 코드를 만들어 돌려 봤다.

 

import cv2

cam = cv2.VideoCapture("v4l2src device=/dev/video0 ! video/x-raw, width=1920, height=1080, format=YUY2, framerate=5/1 ! videoscale ! video/x-raw, width=1280, height=720 ! videoconvert ! appsink sync=false")

for i in range(2000):
    ret, img = cam.read()
    cv2.imshow('test', img)
    if cv2.waitKey(1) == 27:
        break
~

이것도 에러가 발생한다.

[ WARN:0] global /home/nvidia/host/build_opencv/nv_opencv/modules/videoio/src/cap_gstreamer.cpp (711) open OpenCV | GStreamer warning: Error opening bin: no element "4l2src"
[ WARN:0] global /home/nvidia/host/build_opencv/nv_opencv/modules/videoio/src/cap_gstreamer.cpp (480) isPipelinePlaying OpenCV | GStreamer warning: GStreamer: pipeline have not been created
Traceback (most recent call last):
  File "../show.py", line 10, in <module>
    cv2.imshow('test', img)
cv2.error: OpenCV(4.1.1) /home/nvidia/host/build_opencv/nv_opencv/modules/highgui/src/window.cpp:352: error: (-215:Assertion failed) size.width>0 && size.height>0 in function 'imshow'

첫 번째 에러 솔루션

웬지 카메라 입력 받아 오는 부분이 문제인거 같아  아래 처럼 카메라 스펙을 확인해 봤다.

$v4l2-ctl -d /dev/video0 --list-formats-ext

	Index       : 1
	Type        : Video Capture
	Pixel Format: 'YUYV'
	Name        : YUYV 4:2:2
		Size: Discrete 1280x720
			Interval: Discrete 0.100s (10.000 fps)
			Interval: Discrete 0.200s (5.000 fps)
		Size: Discrete 640x480
			Interval: Discrete 0.033s (30.000 fps)
			Interval: Discrete 0.040s (25.000 fps)
			Interval: Discrete 0.050s (20.000 fps)
			Interval: Discrete 0.067s (15.000 fps)
			Interval: Discrete 0.100s (10.000 fps)
			Interval: Discrete 0.200s (5.000 fps)
		Size: Discrete 640x360
			Interval: Discrete 0.033s (30.000 fps)
			Interval: Discrete 0.040s (25.000 fps)
			Interval: Discrete 0.050s (20.000 fps)
			Interval: Discrete 0.067s (15.000 fps)
			Interval: Discrete 0.100s (10.000 fps)
			Interval: Discrete 0.200s (5.000 fps)

코드에서 요구하는 건 1920x1080 resolution인데 내 카메라는 그 사이즈를 지원하지 않는다.

그래서 아래 처럼 resolution을 1280x720, framerate 5/1 로 바꿧더니 에러가 해결 됐다.

cam = cv2.VideoCapture("v4l2src device=/dev/video0 ! video/x-raw, width=1280, height=720, format=YUY2, framerate=5/1 ! videoscale ! video/x-raw, width=1280, height=720 ! videoconvert ! appsink sync=false")

 

그래서 cfg/mot.json 파일에서 input stream resolution을 변경했다.

아래는 변경된 mot.json (configuration file)이다.

 

 

이후 아래와 같이 래퍼런스를 돌렸다.

python3 app.py --input-uri /dev/video0 --mot

아래와 같은 에러가 발생했다.

하지만 이건 문제가 아니다. chrome을 켜놓고 있었는데 tab을 전부 다 닫으니 이 문제는 안생긴다.

크롬을 닫고 실행 하면 에러 및 워닝 메시지가 출력되고

4분 쯤 모델 로딩 하는 시간이 걸리지만 뭔가 실행되고 있는 것같은 메시지가 아래 화면 처럼 나온다.

하지만 결과가 시각화 되지 않는다.

시각 화를 위해 아래와 같이 option을 추가 했다.

python3 app.py --input-uri /dev/video0 --mot --show

그랬더니 아래 화면 처럼 이라는 에러가 발생한다.

원인 파악 하고 고칠 시간이 없어 여기 까지만 확인하고 아직 고쳐서 돌려 보진 못했다.

 

LLVM ERROR: Unable to allocate memory for common symbols!

이전에 만든 도커 이미지에서 usbcamera로 영상을 받아 처리하는 프로그램을 만든뒤 실행 파일로 배포 하기 위해 

pyinstaller를 이용하기로 했다. 

 

최종 목표는 '[Jetson nano] docker를 이용한 개발 환경 세팅'에서 만든 컨테이너에서 실행 파일을 만들어 

개발에 필요한 pakage들이 없는 환경에서 프로그램이 실행되도록 만드는 것이다. 

 

실제 배포할 프로젝트의 코드는 좀 크니 여기선 간단히 opencv만 있는 단일 파일을 기준으로 실행파일을 만들어 본다. 

 

아래는 연습용으로 빌드할 스크립트이다. 

import cv2

cam = cv2.VideoCapture(0)
cam.set(3, 640)
cam.set(4, 480)

for i in range(2000):
    ret, img = cam.read()
    cv2.imshow('test', img)
    if cv2.waitKey(1) == 27:
        break

 

빌드

위의 스크립트를 main_cam.py로 저장한 후 아래 명령으로 빌드를 시도 했다.

pyinstaller -w -F main_cam.py

만들어진 파일을 실행 시켜 보니 아래와 같은 opencv 에러가 발생했다. 이런 이슈가 생길거 같아 미리 opencv만 있는 작은 

연습 스크립트로 빌드를 테스트 해본것이다. 

에러의 내용은 cv2를 반복적으로 로드 하는 현상이 발생한다는 것이다. 

Fig 1. Error 캡쳐

디버깅

사실 위 문제가 왜 발생하는지 알아내는데 2일이나 시간을 허비했지만 스스로 원인은 파악하지 못했고

nvidia에 l4t container에서 opencv 응용 프로그램을 pyinstaller로 빌드하려고 하니 위 에러가 발생했다고 

문의 하니 이것은 잘 알려진 버전 문제라고 응답했다. (잘 알려진 문제 치곤 검색해도 해결법이 잘 안나온다..)

 

원인은 pyinstaller 버전과 프로그램에서 사용하는 opencv 버전이 호환 가능하지 않다는 것이다. 

 

솔루션

처음 빌드 하는데 사용한 내 환경은

pyinstaller version: 4.8

opencv 버전은 :4.1.1 (이전 포스트에선 3.2.0을 설치 했는데 문제가 좀 있어서 업그레이드 했다.)

 

아래 링크는 이 문제를 해결하는 방법이 기록된 영문 페이지 이다. 

link:https://elinux.org/Jetson/L4T/TRT_Customized_Example#PyInstaller_with_OpenCV

 

Jetson/L4T/TRT Customized Example - eLinux.org

This page collects information to deploy customized models with TensorRT and some common questions for Jetson. TensorRT Python OpenCV with ONNX model Below is an example to deploy TensorRT from an ONNX model with OpenCV images. Verified environment: import

elinux.org

 

nvidia 측에 따르면 두가지 해결 방법이 있다.

 

 

1. opencv 버전을 업그레이드.  

아래 명령을 이용하라고 한다. (하지만 이건 좀 애매하다 나는 4.5.* 버전의 opencv도 설치해서 이 문제를 해결하려고 시도했지만 같은 문제가 지속적으로 발생해 포기했다.)

 pip3 install opencv-python

2. pyinstaller 와 pyinstaller-hooks-contrib 버전 다운 그레이드

아래 명령을 이용하면 된다. 

pip3 install pyinstaller==4.2
pip3 install pyinstaller-hooks-contrib==2021.2
pyinstaller --onefile --paths="/usr/lib/python3.6/dist-packages/cv2/python-3.6" target.py

나는 두번째 옵션인 pyinstaller 버전 다운 그레이드를 선택했다.

실행 결과 아래와 같이 위 스크립트를 onefile로 빌드해 성공했다. 

Fig 2.실행 성공 화면

 

* pyinstaller 버전마다 자동으로 package import 를 문제 없이 할수 있는 package 버전이 다른 것으로 보인다. 

나도 pyinstaller를 자주 사용하진 않아 이 문제를 정확히 파악하진 못했다. 

다만 혹시 비슷한 문제를 격고 있는 분들은 pyinstaller 와 프로젝트에서 사용하는 package 버전들 간의 호환성 문제는 아닌지 고려해 보고 호환 되는 버전의 pyinstaller를 사용 하면 도움이 될것 같다. 

 

이번엔 jetson nano의 docker container 에 opencv-python 4.5.1 버전을 설치해 보고 설치 과정 중 내가 격은 에러 사항과
해결 방법을 기록한다.

 

참고로 nvidia에서 제공하는 l4t-ml docker image에는 opencv가 이미 설치되어있다. pytorch, tensorflow도 설치 되어있다. 

빌드하느라 고생하기 싫은 분들은 저런 도커 이미지를 사용하는게 훠얼씬 시간절약 및 정신 건강에 좋다.

 

 
설치 과정은 아래 link되어있는 페이지에 소개된 내용을 따라 했다. 이런 좋은 가이드를 올려주신 개발자 분에게 감사드리고 싶다.
참조 페이지: link

 

로그인 또는 가입하여 보기

Facebook에서 게시물, 사진 등을 확인하세요.

ko-kr.facebook.com


유일한 다른 점은 나는 host에 직접 설치 한게 아니라 이전 포스팅([jetson nano] docker를 이용한 개발 환경 세팅) 에서 다운 받은 'l4t-pytorch:r32.6.1-pth1.8-py3 ' docker image 에 opencv 4.5.1 을 설치 했다는 것이다.

패키지 업데이트 및 설치

apt update a
pt install -y python3-pip python3-dev python3-numpy
apt install -y libcanberra-gtk* libgtk2.0-dev 
apt install -y libtbb2 libtbb-dev libavresample-dev libvorbis-dev libxine2-dev 
apt install -y curl

나의 경우 위의 명령을 실행하면 아래와 같이 libopencv와 관련된 log가 무수히 많이 발생했다.

간단한 opencv 동작 테스트 때는 문제가 되지 않았다. 

/sbin/ldconfig.real: File '/usr/loib/aarch64-linux-gnu/libopencv_imgproc.so is emptry, not checked'

 

 

math, video, image format 관련 library 설치

apt install -y libxvidcore-dev libx264-dev libgtk-3-dev 
apt install -y libjpeg-dev libpng-dev libtiff-dev 
apt install -y libmp3lame-dev libtheora-dev libfaac-dev libopencore-amrnb-dev 
apt install -y libopencore-amrwb-dev libopenblas-dev libatlas-base-dev 
apt install -y libblas-dev liblapack-dev libeigen3-dev libgflags-dev 
apt install -y protobuf-compiler libprotobuf-dev libgoogle-glog-dev 
apt install -y libavcodec-dev libavformat-dev gfortran libhdf5-dev 
apt install -y libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev 
apt install -y libv4l-dev v4l-utils qv4l2 v4l2ucp libdc1394-22-dev

 

opencv 다운로드 및 압축해제

curl -L https://github.com/opencv/opencv/archive/4.5.1.zip -o opencv-4.5.1.zip curl -L https://github.com/opencv/opencv_contrib/archive/4.5.1.zip -o opencv_contrib-4.5.1.zip unzip opencv-4.5.1.zip unzip opencv_contrib-4.5.1.zip cd opencv-4.5.1/ mkdir build cd build cmake -D WITH_CUDA=ON \ -D ENABLE_PRECOMPILED_HEADERS=OFF \ -D OPENCV_EXTRA_MODULES_PATH=../../opencv_contrib-4.5.1/modules \ -D WITH_GSTREAMER=ON \ -D WITH_LIBV4L=ON \ -D BUILD_opencv_python2=ON \ -D BUILD_opencv_python3=ON \ -D BUILD_TESTS=OFF \ -D BUILD_PERF_TESTS=OFF \ -D BUILD_EXAMPLES=OFF \ -D CMAKE_BUILD_TYPE=RELEASE \ -D CMAKE_INSTALL_PREFIX=/usr/local \ -D OPENCV_EXTRA_MODULES_PATH=../../opencv_contrib-4.5.1/modules \ -D EIGEN_INCLUDE_PATH=/usr/include/eigen3 \ -D CUDA_ARCH_BIN="5.3" \ -D CUDA_ARCH_PTX="" \ -D WITH_CUDNN=ON \ -D WITH_CUBLAS=ON \ -D ENABLE_FAST_MATH=ON \ -D CUDA_FAST_MATH=ON \ -D OPENCV_DNN_CUDA=ON \ -D ENABLE_NEON=ON \ -D WITH_QT=OFF \ -D WITH_OPENMP=ON \ -D WITH_OPENGL=ON \ -D BUILD_TIFF=ON \ -D WITH_FFMPEG=ON \ -D WITH_TBB=ON \ -D BUILD_TBB=ON \ -D WITH_EIGEN=ON \ -D WITH_V4L=ON \ -D OPENCV_ENABLE_NONFREE=ON \ -D INSTALL_C_EXAMPLES=ON \ -D INSTALL_PYTHON_EXAMPLES=ON \ -D BUILD_NEW_PYTHON_SUPPORT=ON \ -D BUILD_opencv_python3=TRUE \ -D OPENCV_GENERATE_PKGCONFIG=ON \ -D BUILD_EXAMPLES=OFF .. make -j4 sudo make install


여기 까지 하고 나면 cv2==4.5.1 버전의 설치는 완료 된다. 에러도 없이 아주 잘 설치 된다.
cv2==3.2.0 버전에서 잘동작 하던 카메라 영상을 받아오는 스크립트를 실행 시키면 아래와 같은 에러가 발생한다.

No protocol specified nvbuf_utils: Could not get EGL display connection
(Argus) Error FileOperationFailed: Connecting to nvargus-daemon failed: No such file or directory (in src/rpc/socket/client/SocketClientDispatch.cpp, function openSocketConnection(), line 205) 
(Argus) Error FileOperationFailed: Cannot create camera provider (in src/rpc/socket/client/SocketClientDispatch.cpp, function createCameraProvider(), line 106) 
[ WARN:0] global /home/download/opencv-4.5.1/modules/videoio/src/cap_gstreamer.cpp (961) open OpenCV | GStreamer warning: Cannot query video position: status=0, value=-1, duration=-1 
No protocol specified Unable to init server: Could not connect: Connection refused Traceback (most recent call last): 
File "show.py", line 10, in <module> cv2.imshow('img', img)
cv2.error: OpenCV(4.5.1) 
/home/download/opencv-4.5.1/modules/highgui/src/window_gtk.cpp:624: 
error: (-2:Unspecified error) 
Can't initialize GTK backend in function 'cvInitSystem'

카메라가 문제인가 하고 gst-launch-1.0으로 영상 출력을 확인했더니 xvimagesink 가 없어 패키지를 설치했다.

gstreamer1.0-x 를 설치 하면 된다.

$ gst-launch-1.0 v4l2src device=/dev/video0 ! videoconvert ! xvimagesink 
error occcur: no element xvimagesink 
$ apt install gstreamer1.0-x

xvimagesink설치 후 다시 gst-launch-1.0으로 카메라 영상 출력을 시도해보았다.

역시 안된다. 

이때 든생각이 docker에서 display 넘겼었나...? 

컨테이너를 종료하고 

도커를 다시 실행했다. 이번엔 아래 처럼 xhost + 를 입력해 

모든 호스트에 대해 그래픽 요청을 허용 하도록 하고 (접근제어 안한다는 뜻)

DISPLAY도 잊지 않고 넘겼다.

xhost +
docker run -it --rm --runtime nvidia --network host --priviledged -e DISPLAY=$DISPLAY
~~~ nvcr.io/nvidia/l4t-ml:r32.5.0-py3

그리고 다시 gst-launch-1.0으로 카메라를 테스트 하고 영상 출력이 정상인것을 확인후 opencv 테스트 프로그램을 실행했다.

잘된다. 

gst-launch-1.0 v4l2src device=/dev/video0 ! videoconvert ! xvimagesink 
#run show.py again 
python3 show.py


#complete

 

이전 포스팅에서 젯슨 나노 조립을 완료 했고 이번에 할일은 usb 웹캠을 통해 영상을 입력 받고 pytorch를 설치 하는 것이다.
 
내 최종 목표는 만든 응용 프로그램을 도커라이즈 해서 자동 배포 하는 것 이므로
웹캠 영상을 도커 컨태이너 내부에서 받아 사용해야 하고 pytorch 도 컨테이너 내부에 설치 해야 한다.
 
위의 문제를 가장 간단히 해결할 수 있는 방법은 바로 nvidia 에서 제공하는 L4T-pytorch를 사용 하는 것이다.
적어도 내 목적을 이루는 대에는 가장 손쉬운 방법이며 더 복잡한 것을 해야 하는 사람들에게 좋은 출발점이 될것 이다.

 

sudo 없이 도커사용

가장 먼저 한일은 sudo 명령 없이 도커를 사용하기 위해 docker group에 user를 포함시키는 일이다.
nvidia에서 제공하는 jetson nano os 를 설치 하고 나면 기본적으로 docker는 설치 되어있지만
user를 자동으로 docker그룹에 포함시지는 않는다.
 
그래서 아래 명령을 이용해 사용자를 docker group에 할당한다.

 

 sudo usermod -aG docker $USER

그리고 로그아웃 후 재 로그인 해주면 docker 명령을 sudo 명령 없이 사용할 수 있다.
 
다음으로 swap memory를 4GB 늘리는 작업이다.(일단 4GB로 했는데 opencv 4.5 설치시 6GB이상으로 늘려야 한다고 한다...)
 아래의 swap 메모리 설정은 nvidia에서 제공하는 tutorial을 참고 해 작성했다.
 
link : nvidia jetson nano tutorial

Jetson AI Courses and Certification

Jetson AI Courses and Certifications NVIDIA’s Deep Learning Institute (DLI) delivers practical, hands-on training and certification in AI at the edge for developers, educators, students, and lifelong learners. This is a great way to get the critical AI s

developer.nvidia.com

우선 swap 메모리를 4GB 할당하는 파일을 만든다. (혹 swap 메모리가 뭔지 모르는 독자는 os 와 관련된 내용을 아주 약간이나마 공부해 두면 이런거 이해하기가 편하다...)

sudo systemctl disable nvzramconfig
sudo fallocate -l 4G /mnt/4GB.swap
sudo chmod 600 /mnt/4GB.swap
sudo mkswap /mnt/4GB.swap

그리고 만들어진 swap 설정을 boot시 로드하기 위해 /etc/fstab에 적용한다.

sudo vim /etc/fstab

vim으로 연 /etc/fstab 파일의 맨 아래 줄에 다음을 추가 한 후 저장한다.

/mnt/4GB.swap            swap                     swap           defaults                                     0 0

바뀐 설정을 로드 하고 (그냥 재부팅 해도 된다.) swap 용량을 확인하기 위해 아래 명령을 이용한다.

free -m

아래와 같이 swap size가 바뀐걸 확인할 수 있다.

swap 메모리 용량 확인

적절한 docker image 다운 받기

다음으로 할 일은 자신의 목적에 맞는 적절한 도커 이미지를 다운 받는 일이다.
나는 torch1.8, torchvision, opencv가 기본적으로 필요하다. docker file을 만들어 필요한걸 일일이 설치 할 수도 있지만
시간 절약과 효율성을 위해서 nvidia에서 제공하는 L4T image 를 사용하기로 했다.
아래 링크를 타고 들어가면 jetson nano에서 사용가능한 pytorch, torchvision등을 포함해 생성된 이미지를 다운받을 수 있다.
 
link:https://catalog.ngc.nvidia.com/orgs/nvidia/containers/l4t-pytorch

NVIDIA L4T PyTorch | NVIDIA NGC

PyTorch is a GPU accelerated tensor computational framework with a Python front end. This container contains PyTorch and torchvision pre-installed in a Python 3.6 environment to get up & running quickly with PyTorch on Jetson.

catalog.ngc.nvidia.com

내가 다운 받은 버전은 아래 버전이다. 내 목적에 부합해서 받은 것 뿐 각자 자기의 목적에 맞는 버전을 받으면 된다.
 

  • l4t-pytorch:r32.6.1-pth1.8-py3
    • PyTorch v1.8.0
    • torchvision v0.9.0
    • torchaudio v0.8.0

아래 명령을 통해 이미지를 다운 받을 수 있다.

docker pull nvcr.io/nvidia/l4t-pytorch:r32.6.1-pth1.8-py3

 

이미지를 다운 받고 컨테이너를 실행해 잘 동작하는지 확인해 보자.
container 실행 명령은 다음과 같다. 

docker run -it --rm --runtime nvidia --network host --privileged -e DISPLAY=$DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix -v /dev:/dev nvcr.io/nvidia/l4t-pytorch:r32.6.1-pth1.8-py3

 
 

docker conatiner 에서 usb cam 영상 읽기

다운 받은 컨테이너에서 cv2.VideoCapture()를 이용해 usb영상을 읽으려 했으나 아래와 같은 문제가 발생했다.

AttributeError: module 'cv2' has no attribute 'VideoCapture'

그래서 이게 뭔일인가 하고 cv2.__path__ 로 모듈 경로를 확인해 해당 폴더를 확인해 보니 거기엔 아무 것도 없었다.
왜 없는 건지 모르겠지만 깨끗하게 단하나의 파일도 없었다. 그래서  아래 명령을 통해 opencv 를 설치 했다.
 

apt update 
apt-get install python3-opencv

상기 명시된 컨테이너에서 위 명령으로 설치한 opencv 버전은 '3.2.0'이다.
 
그리고 미리 만든 show.py란 파일을 실행시켜 카메라 동작을 확인했다.
아래는 show.py의 내용이다.

import cv2

if __name__=='__main__':
	cam =cv2.VideoCapture(0)
    if cam.isOpened():
        cam.set(cv2.CAP_PROP_FRAME_WIDTH, 640) #영상 width 설정
        cam.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) #영상 height 설정
        while True:
        	ret, img =cam.read() #영상 읽기
            cv2.imshow('img', img)
            if cv2.waitKey(1) == 27 : #esc 누르면 종료
            	break

아래 사진은 위 스크립트 실행시 나타나는 usb camera 영상이다.
영상은 잘 나오지만 맘에 안드는 것은 터미널에 지속적으로 뜨는 'currupt JPEG data:~~' 라는 warning 들이다.
다음 검색을 좀 해보니 opencv설치 시 함께 설치 되는 필요 libary와 관련된 버그 라고 하는데
다음엔 최신 버전인 opencv 4.5 설치 해보고 이문제 가 사라지는지 확인해봐야겠다. (opencv4.5 는 필요해서 설치하는 것이다... 이 문제를 해결할 기 위해 설치하는건 아니다..)

 

container 내에서 usb camera 입력 확인 영상

 

jetson nano를 edge device로 사용해 deeplearning inference모델을 돌리고
모델 재학습(또는 업그레이드)시 jetson nano에 자동 배포하는 파이프 라인을 만들어 보고 이에 관련된 내용을 정리하고자 한다.

최종 목표는 모델 개발 및 학습을 서버에서 하고 학습한 모델을 pyinstaller 또는 다른 배포 형태로 만든 후 jetson nano에 자동으로 배포해 jetson nano에 연결된 카메라를 이용해 실시간으로 inference를 돌리는 것이다.

일정은 약 한달은 잡고 있다. (대한민국 직장인 화이팅..)

오늘은 첫번째 작업으로 jetson nano 조립 및 세팅을 하고자 한다.

Jetson Nano 구성품

내가 구매한 제품은 jetson nano developer kit 이다. 자세한 스펙은 여기를 참조 하면 된다.

cpu, gpu, memory만 언급 하면

CPU Quad-core ARM A57 @ 1.43 GHz
GPU 128-core Maxwell
Memory 4 GB 64-bit LPDDR4 25.6 GB/s

CPU가 arm core인것에 주목하자. Intel cpu와 달리 arm core가 target일땐 종종 예상치 못한 문제와 마주칠때가 많다.
내가 받은 패키지는 아래와 같은 모양이었다.

Fig 1 Jetson nano box 구성

박스를 다 열어 보면 jetson nano dev kit의 구성품은 아래와 같이 케이스, 무선 랜카드와 안테나, 전원, 리셋선, 팬, micro sd card(내가 산거엔 128gb가 패키지로 들어있었다.), 조립할때 유용한 핀셋 이 들어있다.

Fig 2 Jetson nano 구성품

 

OS 설치

내가 가장 먼저 한것은 OS 설치 이다.
os는 구성품에 있는 SD 카드에 설치를 했고 이를 보드에 꽂아 사용 하면 된다. sd 삽입 위치는
nvidia 에서 친절하게 jetson nano 용 os를 배포 한다. 아래 링크에서 Jetson Nano Developer Kits을 선택하면 된다.
os download page: https://developer.nvidia.com/embedded/downloads

 

Jetson Download Center

Get downloadable documentation, software, and other resources for the NVIDIA Jetson ecosystem.

developer.nvidia.com

os 설치를 위한 가이드도 여기에 잘 설명 되어있으니 잘 따라 하면 된다.
나도 가이드를 따라 Etcher를 이용해 SD 카드에 os를 설치 했다.
Etcher를 실행 하면 다음과 같은 화면이 나오는데 이때 Flash from File을 선택해서 search window가 나오면 위의 os download page에서 받은 os파일을 선택한다.

Fig 3 Etcher 실행화면

이 후 target device 선택에서 os를 구울 SD 카드를 선택하고 Flash! 를 누르면 아래와 같이 os 설치가 시작된다.

Fig 4. OS 설치 중인 화면

설치가 다되면 Fig 7 의 붉은 색 동그라미 부분에 sd를 삽입한다.

조립



1. 우선 케이스에 메인 전원/ 리셋 버튼을 아래와 같이 연결한다. LED 선이 있는 큰 버튼이 메인 전원선이고 작은게 리셋 버튼인다.

Fig 5. 전원 및 리셋 선 버튼 연결 모습

2. 다음으로 팬을 팬을 연결한다. 팬의 전원은 아래 그림 Fig 6 처럼 랜선 연결 소켓과 방열판 사이의 4핀에 연결 하면 된다. 다음으로 구성품에 있는 나사와 너트를 이용해 방연판위에 팬을 설치 한다. 그냥 나사만 꽂으면 고정이 절대 안되고 나사 구멍이 잇는 방연판 밑에서 너트로 고정 시켜 줘야 한다. 이때 구성품의 필셋이 유용하게 씌인다. 팬을 단 모습은 Fig 7에 있다.

 

Fig 6 팬 전원 연결 모습
Fig 7 팬 고정 모습

3. 다음으로 무선 랜카드 및 안테나는 연결 한다.  아래 그림 Fig 8이 안테나와 안테나-무선 랜카드 연결선을 보여 준다. 안테나 선을 케이스에 Fig 9처럼 연결하고 반대쪽 끝부분은 랜카드에 “똑” 소리가 나도록 확실히 연결 해 준다. 연결해야 하는 곳은 Fig 9의 붉은색 동그라이로 표시했다. 연결을 마쳤으면 보드에 해당 랜카드를 꽂아야 하는데 이 부분이 좀 불편하다.
Fig 7에 녹색으로 표시한 “고정나사”를 풀고 코어보드(방열판이 붙어 있는 보드)를 잡고 있는 해치를 풀면 Fig 10과 같은 모습이 된다. Fig 10의 표시 부분에 랜카드를 삽입후 나사로 고정하고 다시 코어 보드를 다시 연결하고 “고정나사”를 체결 한다.  (생각해보면 방열판에 팬을 연결하기 전에 이 작업 부터 했으면 편했을거 같다.)

Fig 8 wifi 안테나와 무선 랜카드 연결 선
Fig 9 안테나와 무선 랜 카드 연결 모습
Fig 10 랜 카드 연결 부위 및 순서, 전원, reset 버튼, LED 전원 연결 위치


4. 전원 및 리셋 선 메인보드에 연결.
Fig 10에 노란 색으로 전원, reset, 전원 LED라고 표시해 놓은 부분을 보자. 1단계 에서 케이스에 연결한 전원 버튼과 리셋 버튼에는 각각 4개, 2개의 커넥터가 있다.
이 커넥터 들을 각각 Fig 10에 노란색으로 표시한 부분에 연결 하면 된다. 좀더 설명하자면 전원 버튼의 붉은 선을 전원이라고 표시한 부분의 PWR BTN(메인 보드에 이렇게 써있다)에 연결하고 검은색 선을 GND에 연결한다. 다음으로 전원 버튼에 있는 흰색 선과 파란선은 LED 전원선인데 흰선을 LED +, 파란 선을 LED-에 연결한다.
리셋 버튼의 붉은색 선은 SYS RST에 검은 선은 GND에 연결 하면 된다. 모두 연결한 모습이 아래 그림 Fig 11에 있다.

Fig 11 전원 및 리셋 버튼 선 연결 모습


보드를 케이스에 고정시키고 조립을 마치면 아래와 같은 모습이 된다.

Fig 12 조립 완료 모습


참고:
연결을 하고 부팅하면 쿨러가 움직이지 않아 고민을 했는데 부팅 후 쿨러 control configuration 을 수정해 수동으로 쿨러를 조정 할 수 있다.
하지만 테스트 cuda프로그램을 돌려본 결과 gpu사용시 코어 온도가 올라가면 자동으로 쿨러가 동작하는 걸 봐선 그냥 둬도 필요시 알아서 동작하는거 같다.


 

+ Recent posts