제로샷 객체 탐지

제로샷 객체 탐지 (Zero-shot object detection)

전통적으로 객체 탐지에 사용되는 모델은 학습을 위해 라벨링된 이미지 데이터셋이 필요하고, 학습 데이터의 클래스 집합을 탐지하는 데만 국한돼요.

출처: 문서

본문

제로샷 객체 탐지는 클래스에 대한 사전 학습이나 지식 없이 이미지 안의 객체와 그 클래스를 탐지하는 컴퓨터 비전 작업이에요. 제로샷 객체 탐지 모델은 이미지를 입력으로 받고, 후보 클래스 목록도 함께 받아 객체가 탐지된 경계 상자(bounding box)와 라벨을 출력해요.

[!NOTE] Hugging Face에는 이런 개방 어휘 제로샷 객체 탐지기(open vocabulary zero shot object detectors)가 많이 있어요.

이 가이드에서는 이러한 모델을 사용하는 방법을 배워요:

  • 텍스트 프롬프트 기반으로 객체 탐지하기
  • 배치 객체 탐지하기
  • 이미지 유도 객체 탐지하기

시작하기 전에 필요한 라이브러리를 모두 설치했는지 확인해요:

pip install -q transformers

제로샷 객체 탐지 파이프라인

모델로 추론을 시도하는 가장 간단한 방법은 pipeline()에서 쓰는 거예요. Hugging Face Hub의 체크포인트에서 제로샷 객체 탐지용 파이프라인을 인스턴스화해요:

>>> from transformers import pipeline

>>> # Use any checkpoint from the hf.co/models?pipeline_tag=zero-shot-object-detection
>>> checkpoint = "iSEE-Laboratory/llmdet_large"
>>> detector = pipeline(model=checkpoint, task="zero-shot-object-detection")

다음으로 객체를 탐지할 이미지를 선택해요. 여기서는 NASA Great Images 데이터셋의 일부인 우주비행사 Eileen Collins의 이미지를 사용할 거예요.

>>> from transformers.image_utils import load_image

>>> url = "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/transformers/tasks/zero-sh-obj-detection_1.png"
>>> image = load_image(url)
>>> image

찾고 싶은 후보 개체 라벨과 이미지를 파이프라인에 전달해요. 여기서는 이미지를 직접 전달해요. 다른 적절한 옵션으로는 이미지의 로컬 경로나 이미지 URL이 있어요. 이미지에서 질의하고 싶은 모든 항목에 대한 텍스트 설명도 전달해요.

>>> predictions = detector(
...     image,
...     candidate_labels=["human face", "rocket", "nasa badge", "star-spangled banner"],
...     threshold=0.45,
... )
>>> predictions
[{'score': 0.8409242033958435,
  'label': 'human face',
  'box': {'xmin': 179, 'ymin': 74, 'xmax': 272, 'ymax': 179}},
 {'score': 0.7380027770996094,
  'label': 'rocket',
  'box': {'xmin': 353, 'ymin': 0, 'xmax': 466, 'ymax': 284}},
 {'score': 0.5850900411605835,
  'label': 'star-spangled banner',
  'box': {'xmin': 0, 'ymin': 0, 'xmax': 96, 'ymax': 511}},
 {'score': 0.5697067975997925,
  'label': 'human face',
  'box': {'xmin': 18, 'ymin': 15, 'xmax': 366, 'ymax': 511}},
 {'score': 0.47813931107521057,
  'label': 'star-spangled banner',
  'box': {'xmin': 353, 'ymin': 0, 'xmax': 459, 'ymax': 274}},
 {'score': 0.46597740054130554,
  'label': 'nasa badge',
  'box': {'xmin': 353, 'ymin': 0, 'xmax': 462, 'ymax': 279}},
 {'score': 0.4585932493209839,
  'label': 'nasa badge',
  'box': {'xmin': 132, 'ymin': 348, 'xmax': 208, 'ymax': 423}}]

예측 결과를 시각화해 보도록 해요:

>>> from PIL import ImageDraw

>>> draw = ImageDraw.Draw(image)

>>> for prediction in predictions:
...     box = prediction["box"]
...     label = prediction["label"]
...     score = prediction["score"]

...     xmin, ymin, xmax, ymax = box.values()
...     draw.rectangle((xmin, ymin, xmax, ymax), outline="red", width=1)
...     draw.text((xmin, ymin), f"{label}: {round(score,2)}", fill="white")

>>> image

손으로 직접 하는 텍스트 프롬프트 제로샷 객체 탐지

이제 제로샷 객체 탐지 파이프라인을 사용하는 법을 봤으니, 같은 결과를 수동으로 재현해 보도록 해요.

먼저 Hugging Face Hub의 체크포인트에서 모델과 관련 프로세서를 로드해요. 여기서는 이전과 같은 체크포인트를 사용할 거예요:

>>> from transformers import AutoProcessor, AutoModelForZeroShotObjectDetection

>>> model = AutoModelForZeroShotObjectDetection.from_pretrained(checkpoint, device_map="auto")
>>> processor = AutoProcessor.from_pretrained(checkpoint)

분위기를 바꾸기 위해 다른 이미지를 사용해 보도록 해요.

>>> url = "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/transformers/tasks/zero-sh-obj-detection_3.png"
>>> image = load_image(url)
>>> image

프로세서를 사용해 모델용 입력을 준비해요.

>>> text_labels = ["hat", "book", "sunglasses", "camera"]
>>> inputs = processor(text=text_labels, images=image, return_tensors="pt").to(model.device)

입력을 모델에 통과시키고, 후처리하고, 결과를 시각화해요. 이미지 프로세서는 모델에 넣기 전에 이미지를 리사이즈하므로, 예측된 경계 상자가 원본 이미지 기준으로 올바른 좌표를 갖도록 post_process_object_detection 메서드를 사용해야 해요:

>>> import torch

>>> with torch.inference_mode():
...     outputs = model(**inputs)

>>> results = processor.post_process_grounded_object_detection(
...    outputs, threshold=0.50, target_sizes=[(image.height, image.width)], text_labels=text_labels,
... )[0]

>>> draw = ImageDraw.Draw(image)

>>> scores = results["scores"]
>>> text_labels = results["text_labels"]
>>> boxes = results["boxes"]

>>> for box, score, text_label in zip(boxes, scores, text_labels):
...     xmin, ymin, xmax, ymax = box
...     draw.rectangle((xmin, ymin, xmax, ymax), outline="red", width=1)
...     draw.text((xmin, ymin), f"{text_label}: {round(score.item(),2)}", fill="white")

>>> image

배치 처리 (Batch processing)

여러 이미지에서 서로 다른(또는 같은) 객체를 검색하기 위해 여러 이미지와 텍스트 쿼리 세트를 전달할 수 있어요. 우주비행사 이미지와 해변 이미지를 함께 사용해 보도록 해요. 배치 처리의 경우 프로세서에 텍스트 쿼리를 중첩 리스트로 전달하고, 이미지는 PIL 이미지, PyTorch 텐서, 또는 NumPy 배열의 리스트로 전달해야 해요.

>>> url1 = "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/transformers/tasks/zero-sh-obj-detection_1.png"
>>> url2 = "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/transformers/tasks/zero-sh-obj-detection_3.png"
>>> images = [load_image(url1), load_image(url2)]
>>> text_queries = [
...     ["human face", "rocket", "nasa badge", "star-spangled banner"],
...     ["hat", "book", "sunglasses", "camera", "can"],
... ]
>>> inputs = processor(text=text_queries, images=images, return_tensors="pt", padding=True)

앞서 후처리에서 단일 이미지의 크기를 텐서로 전달했지만, 튜플을 전달할 수도 있고 여러 이미지의 경우에는 튜플 리스트를 전달할 수도 있어요. 두 예시에 대한 예측을 만들고 두 번째 예시(image_idx = 1)를 시각화해 보도록 해요.

>>> with torch.no_grad():
>>>     outputs = model(**inputs)

>>> target_sizes = [(image.height, image.width) for image in images]
>>> results = processor.post_process_grounded_object_detection(
...     outputs, threshold=0.3, target_sizes=target_sizes, text_labels=text_labels,
... )

결과를 시각화해요:

>>> image_idx = 1
>>> draw = ImageDraw.Draw(images[image_idx])

>>> scores = results[image_idx]["scores"].tolist()
>>> text_labels = results[image_idx]["text_labels"]
>>> boxes = results[image_idx]["boxes"].tolist()

>>> for box, score, text_label in zip(boxes, scores, text_labels):
>>>     xmin, ymin, xmax, ymax = box
>>>     draw.rectangle((xmin, ymin, xmax, ymax), outline="red", width=1)
>>>     draw.text((xmin, ymin), f"{text_label}: {round(score,2)}", fill="white")

>>> images[image_idx]

이미지 유도 객체 탐지 (Image-guided object detection)

텍스트 쿼리로 하는 제로샷 객체 탐지 외에도 OWL-ViT와 OWLv2 같은 모델은 이미지 유도 객체 탐지를 제공해요. 즉 이미지 쿼리를 사용해 대상 이미지에서 비슷한 객체를 찾을 수 있다는 뜻이에요.

>>> from transformers import AutoProcessor, AutoModelForZeroShotObjectDetection

>>> checkpoint = "google/owlv2-base-patch16-ensemble"
>>> model = AutoModelForZeroShotObjectDetection.from_pretrained(checkpoint, device_map="auto")
>>> processor = AutoProcessor.from_pretrained(checkpoint)

텍스트 쿼리와 달리 단일 예시 이미지만 허용돼요.

소파 위에 고양이 두 마리가 있는 이미지를 대상 이미지로, 고양이 한 마리 이미지를 쿼리로 사용해 보도록 해요:

>>> url = "http://images.cocodataset.org/val2017/000000039769.jpg"
>>> image_target = Image.open(requests.get(url, stream=True).raw)

>>> query_url = "http://images.cocodataset.org/val2017/000000524280.jpg"
>>> query_image = Image.open(requests.get(query_url, stream=True).raw)

이미지들을 간단히 살펴봐요:

>>> import matplotlib.pyplot as plt

>>> fig, ax = plt.subplots(1, 2)
>>> ax[0].imshow(image_target)
>>> ax[1].imshow(query_image)
>>> fig.show()

전처리 단계에서는 텍스트 쿼리 대신 query_images를 사용해야 해요:

>>> inputs = processor(images=image_target, query_images=query_image, return_tensors="pt")

예측의 경우, 입력을 모델에 전달하는 대신 image_guided_detection()에 전달해요. 라벨이 없다는 점만 제외하면 이전처럼 예측 결과를 그려요.

>>> with torch.no_grad():
...     outputs = model.image_guided_detection(**inputs)
...     target_sizes = torch.tensor([image_target.size[::-1]])
...     results = processor.post_process_image_guided_detection(outputs=outputs, target_sizes=target_sizes)[0]

>>> draw = ImageDraw.Draw(image_target)

>>> scores = results["scores"].tolist()
>>> boxes = results["boxes"].tolist()

>>> for box, score in zip(boxes, scores):
...     xmin, ymin, xmax, ymax = box
...     draw.rectangle((xmin, ymin, xmax, ymax), outline="white", width=4)

>>> image_target