03. Docker 에서 Nvidia GPU 기반 TensorRT를 활용한 물체 추적 - pineland/object-tracking GitHub Wiki
- Nvidia 그래픽 드라이버 4.x 이상, CUDA 10.1, Docker 19.03 이상 설치
아래의 1 ~ 8번을 ./setup_environment.sh
가 모두 실행하지만, 4.1 실행 시 발생하는 에러를 수정할 필요가 있다.
$ git clone https://github.com/NVIDIA/object-detection-tensorrt-example
# GUI 사용위한 X11 forwarding
$ xhost +local:docker
$ XSOCK=/tmp/.X11-unix
# 액세스 권한이 있는 xauth 파일을 생성하여 사용자 자격 증명
$ cd ./object-detection-tensorrt-example
$ XAUTH=/tmp/.docker.xauth
$ xauth nlist $DISPLAY | sed -e 's/^..../ffff/' | xauth -f $XAUTH nmerge -
이 데이터 세트에는 일반적인 가정 용품 및 일상적인 물건의 이미지가 포함되어 있다.
$ wget http://host.robots.ox.ac.uk/pascal/VOC/voc2007/VOCtest_06-Nov-2007.tar
$ tar -xf VOCtest_06-Nov-2007.tar
전체 개발 환경이 구성되어있는 Dockerfile을 빌드한다. Dockerfile은 다음 구성 요소를 설치합니다. Docker container에 카메라에서 피드를 실행할 OpenCV, 추론 속도를 높여줄 TensorRT가 포함되어 있다.
- TensorRT and required libraries : CUDA, cuDNN, and NCCL 포함
- TensorRT open-source software : TensorRT parsers and plugins 포함
- Other dependencies for our application : OpenCV and its rendering libraries 포함
$ docker build -t object_detection_webcam ./dockerfiles
이때, 이미지가 Tensorflow 2.x를 설치하여 Tensorflow 1.x를 가정하여 만들어진 소스에서 에러(주로 import errors)가 발생한다.
- 해결방법: CUDA 10.1은 tensorflow 2.1과만 호환되므로 tensorflow 2.1 유지
Dockerfile 내RUN /opt/tensorrt/python/python_setup.sh
부분을 주석처리하여 이미지를 다시 만들고 컨테이너를 생성 한 후, 컨테이너 내에서$ python SSD_Model/detect_objects_webcam.py
를 실행시키면 에러(주로 import errors)가 나는 부분을 하나 하나pip install <라이브러리명>
으로 해주고 소스 상의 에러가 발생하는 파일은 열어서 tf.<library_name>을 tf.compat.v1.<libary_name>으로 바꿔준다.
$ docker run --gpus all -it -v `pwd`/:/mnt --device=/dev/video0 -e DISPLAY=$DISPLAY -v $XSOCK:$XSOCK -v $XAUTH:$XAUTH -e XAUTHORITY=$XAUTH object_detection_webcam
컨테이너가 시작되면 $ python SSD_Model/detect_objects_webcam.py
를 사용하여 응용 프로그램을 실행할 수 있다.
detect_objects_webcam.py 프로그램의 실행 흐름도는 아래의 그림과 같다.
(1) Download the frozen object detection model from TensorFlow Model Zoo
(2) Convert the frozen model (.pb file) to Universal Framework Format (UFF)
(3) Build the TensorRT engine from the UFF version of the model
(4) While True:
. Read in a frame from the webcam
(5) Run inference on that frame using our TensorRT engine
(6) Overlay the bounding boxes and class labels
(7) Display that frame back to the user
TensorFlow model zoo에서 fixed SSD object detection model을 다운로드한다. 이것은 model.py의 Preparing_ssd_model에서 수행된다.
""" Downloads pretrained object detection model and converts it to UFF.
The model is downloaded from Tensorflow object detection model zoo.
Currently only ssd_inception_v2_coco_2017_11_17 model is supported
due to model_to_uff() using logic specific to that network when converting.
Args:
model_name (str): chosen object detection model
silent (bool): if True, writes progress messages to stdout
"""
def prepare_ssd_model(model_name="ssd_inception_v2_coco_2017_11_17", silent=False):
if model_name != "ssd_inception_v2_coco_2017_11_17":
raise NotImplementedError(
"Model {} is not supported yet".format(model_name))
download_model(model_name, silent)
ssd_pb_path = PATHS.get_model_pb_path(model_name)
ssd_uff_path = PATHS.get_model_uff_path(model_name)
model_to_uff(ssd_pb_path, ssd_uff_path, silent)
TensorRT는 모든 NVIDIA GPU에 대해 애플리케이션의 런타임을 생성한다.
model.py에서 이용 가능한 유틸리티를 사용하여 fixex TensorFlow graph를 UFF (Universal Framework Format)로 변환한다. 그런 다음, parser를 사용하여 UFF 모델을 TensorRT로 가져와서 최적화를 적용하고 런타임 엔진을 생성한다. 빌드 프로세스 중에 최적화가 적용되며 별도로 적용할 필요는 없다. 런타임 엔진을 빌드하려면 아래 4 개의 매개 변수를 지정해야한다.
- Path to UFF file for our model
- Precision for inference engine (FP32, FP16, or INT8)
- Calibration dataset (only needed if you’re running in INT8)
- Batch size used during inference
engine.py에서 엔진을 빌드할 때 모든 매개 변수가 어떻게 작동하는지 살펴보자.
def build_engine(uff_model_path, trt_logger, trt_engine_datatype=trt.DataType.FLOAT, calib_dataset=None, batch_size=1, silent=False):
with trt.Builder(trt_logger) as builder, builder.create_network() as network, trt.UffParser() as parser:
builder.max_workspace_size = 2 << 30 # TensorRT가 최적화를 적용하는 데 사용해야하는 메모리를 지정. 2GB를 제공.
builder.max_batch_size = batch_size
if trt_engine_datatype == trt.DataType.HALF:
builder.fp16_mode = True
elif trt_engine_datatype == trt.DataType.INT8:
builder.fp16_mode = True
builder.int8_mode = True
builder.int8_calibrator = calibrator.SSDEntropyCalibrator(data_dir=calib_dataset, cache_file='INT8CacheFile')
parser.register_input(ModelData.INPUT_NAME, ModelData.INPUT_SHAPE)
parser.register_output("MarkOutput_0")
parser.parse(uff_model_path, network) # 실제로 위에서 지정한 매개 변수를 사용하여 UFF 파일에서 파서를 실행.
if not silent:
print("Building TensorRT engine. This may take few minutes.")
return builder.build_cuda_engine(network) # 최적화를 네트워크에 적용하고 엔진 객체를 생성.
build_engine 함수는 빌더, 파서 및 네트워크에 대한 오브젝트를 작성합니다. 파서는 SSD 모델을 UFF 형식으로 가져오고 변환 된 그래프를 네트워크 객체에 배치합니다. NGC models에서 사전 훈련된 모델, 매개 변수 및 정밀도의 여러 조합에 대한 PLAN 파일을 다운로드 할 수 있다. Standard 모델을 사용하는 경우 일반적으로 가장 먼저 확인하는 것은 NGC에서 사용 가능한 계획 파일이 내 응용 프로그램에서 직접 사용할 수 있는지 여부이다.
예는 inference.py (보다 구체적으로 infer_webcam 함수)에서 웹캠에서 한 프레임씩을 가져 와서 추론하여 TensorRT 엔진에 전달한다.
# Load image into CPU and do any pre-processing.
# 이 예에서는 axes 순서를 HWC에서 CHW로 이동하고 모든 값이 -1과 +1 사이에 있도록 이미지를 정규화 한 다음 배열을 평탄화한다.
# 이 기능에서 파이프 라인에 필요한 다른 전처리 작업을 추가 할 수도 있다.
img = self._load_img_webcam(arr)
# Copy it into appropriate place into memory (self.inputs was returned earlier by allocate_buffers())
np.copyto(self.inputs[0].host, img.ravel())
# When inferring on single image, we measure inference time to output it to the user
# TensorRT 엔진이 추론을 수행하는 데 걸리는 시간을 측정하기 위해 타이머를 시작한다.
inference_start_time = time.time()
# Fetch output from the model
# detection_out 함수는 각 탐지에 대한 경계상자 좌표, 신뢰도 및 클래스 레이블에 대한 정보를 포함.
# keepCount_out 루틴은 네트워크가 발견 한 총 탐지 횟수를 추적.
[detection_out, keepCount_out] = do_inference(
self.context, bindings=self.bindings, inputs=self.inputs,
outputs=self.outputs, stream=self.stream)
# Output inference time
print("TensorRT inference time: {} ms".format(
int(round((time.time() - inference_start_time) * 1000))))
# And return results
return detection_out, keepCount_out
detect_objects_webcam.py에서 이러한 모든 구성 요소가 어떻게 결합되는지 보자.
def main():
# Parse command line arguments
args = parse_commandline_arguments()
# Fetch .uff model path, convert from .pb
# if needed, using prepare_ssd_model
ssd_model_uff_path = PATHS.get_model_uff_path(MODEL_NAME)
if not os.path.exists(ssd_model_uff_path):
model_utils.prepare_ssd_model(MODEL_NAME)
# Set up all TensorRT data structures needed for inference
trt_inference_wrapper = inference_utils.TRTInference( <=== 153 행
args.trt_engine_path, ssd_model_uff_path,
trt_engine_datatype=args.trt_engine_datatype,
calib_dataset = args.calib_dataset,
batch_size=args.max_batch_size)
print("TRT ENGINE PATH", args.trt_engine_path)
if args.camera == True:
print('Running webcam:', args.camera)
# Define the video stream
cap = cv2.VideoCapture(0) # Change only if you have more than one webcams <== 164행
# Loop for running inference on frames from the webcam
while True:
# Read frame from camera (and expand its dimensions to fit)
ret, image_np = cap.read()
# Actually run inference
detection_out, keep_count_out = trt_inference_wrapper.infer_webcam(image_np)
# Overlay the bounding boxes on the image
# let analyze_prediction() draw them based on model output
img_pil = Image.fromarray(image_np)
prediction_fields = len(TRT_PREDICTION_LAYOUT)
for det in range(int(keep_count_out[0])):
analyze_prediction(detection_out, det * prediction_fields, img_pil)
final_img = np.asarray(img_pil)
# Display output
cv2.imshow('object detection', final_img)
if cv2.waitKey(25) & 0xFF == ord('q'):
cv2.destroyAllWindows()
break
command line 인수를 구문 분석 한 후, preparse_ssd_model은 model.py를 사용하여 fixed TensorFlow graph에서 UFF 형식으로 변환한다. 그런 다음, 위에서 논의한대로 engine.py에서 build_engine을 사용하여 실제로 TensorRT 엔진을 빌드하는 153 행에서 TensorRT 추론 객체를 초기화한다. args.trt_engine_path에 엔진 파일이 저장되어 있지 않으면 처음부터 새로 만들어야 한다. 우리 모델의 UFF 버전도 마찬가지이다. 마지막으로, 하나의 웹캠 피드에서 실시간 추론을 실행하기 때문에 배치 크기를 1로 유지한다.
카메라 플래그가 켜져 있으면 (기본값) 앱은 OpenCV (164 행)를 사용하여 비디오 스트림을 시작하고 167 행에 메인 루프를 시작한다. 이 루프는 라인 (169)에 도시 된 바와 같이 웹캠으로부터 새로운 프레임을 지속적으로 끌어온 다음 라인 (172)에 도시 된 바와 같이 그 프레임에 대해 추론을 수행한다.
마지막으로 경계 상자 결과를 원래 프레임에 오버레이하고 imshow를 사용하여 사용자에게 다시 표시한다.
$ python SSD_Model/detect_objects_webcam.py
$ python SSD_Model/detect_objects_webcam.py -p 8 ;'8'은 INT8로 quantize하여 최적화. '16'은 FP16, '32'는 FP32.