3. Custom Model 만들기
NuFiStreamer nufi element에서 이용할 수 있는 커스텀 model을 만들어봅니다. Gstreamer에 대한 이해가 필요하다면 Gstreamer docs를 참고해주세요.
Model 설명
- Model은 nufi element의
model
property로 설정되어 이미지를 처리하기 위한 객체입니다.- nufi element에 대한 세부정보는 nufi element 문서를 참고하세요.
- 기본적으로 NuFiStreamer에서 제공하는 Model들은 다음과 같습니다.
- Object Detection
- Object Tracking
- Fall Detection
- 제공하는 Model 이외에도 커스텀하게 만든 image processing Model도 nufi element에서 이용할 수 있습니다.
- 커스텀 Model 구현 시 nufi.models 하위의 BaseModel 추상 클래스를 구현해야 합니다.
- BaseModel은 다음과 같은 메소드를 필수적으로 구현해야 합니다.
def process(self, image: np.ndarray, pipeline_id: Optional[int] = None) -> List[NufiResult]
- 이미지를 처리하는 Model의 핵심 메소드입니다.
- Input
image
: 처리하고자 하는 타겟 이미지 데이터pipeline_id
: 해당 Model을 이용하는 nufi element가 속한 pipeline id (optional)
- Output
List[NufiResult]
- BaseModel은 다음과 같은 메소드를 필수적으로 구현해야 합니다.
NufiResult 설명
- NufiResult는 nufi.core.postprocess 하위에 존재합니다.
- NufiResult가 가지는 property는 다음과 같습니다.
- data (Any): 이미지에 그려지지 않는 메타데이터를 포함하는 데이터 (예: image frame seq, fall detection 쓰러짐 여부 등)
- data는 어떠한 데이터든 올 수 있지만 Json encode, decode가 가능한 데이터여야만 합니다.
- 구현한 Class를 경우 nufi.core.postprocess 하위에
SerializableBase
를 상속하는 Class여야만 합니다.
- drawables (List[Drawable]): 실제로 이미지에 metadata를 그리는 객체인 Drawable list
- is_draw (bool): 해당 NufiResult가 가진 drawables 출력 여부
- data (Any): 이미지에 그려지지 않는 메타데이터를 포함하는 데이터 (예: image frame seq, fall detection 쓰러짐 여부 등)
Drawable 설명
- Drawable은 nufi.core.postprocess 하위에 존재합니다.
- 기본적으로 NuFiStreamer에서는 다음과 같은 Drawable 구현체를 제공합니다.
- Box: 사각형을 그리기 위한 Drawable 구현체입니다.
- Line: 선을 그리기 위한 Drawable 구현체입니다.
- Point: 점을 그리기 위한 Drawable 구현체입니다.
- 커스텀 Model을 구현할 때
process
메소드가 제공되는 Drawable 구현체들을 가지는 NufiResult를 반환해도 됩니다. - 이미지에 그려야 하는 모양이 제공되는 Drawable 구현체로 그릴 수 없다면 커스텀하게 만듭니다.
- 커스텀 구현 시 nufi.core.postprocess 하위의 Drawable 추상 클래스를 구현해야 합니다.
- Drawable은 다음과 같은 메소드를 필수적으로 구현해야 합니다.
def draw(self, image: np.ndarray)
- 이미지에 metadata를 그리는 메소드입니다.
- Input
image
: 처리하고자 하는 타겟 이미지 데이터
- Drawable은 다음과 같은 메소드를 필수적으로 구현해야 합니다.
구현 예시
- 해당 가이드에서는 모델을 구현하는 방법만 안내합니다.
- 커스텀 모델을 Pipeline에서 사용하는 방법은 4. Custom Model 이용하는 Pipeline 만들기를 참고하세요.
Drawable 예제 - 제공된 Drawable 구현체를 이용하는 경우
- MidRectanglePrinter 모델은 NuFiStreamer에서 제공하는 Drawable 구현체인 Box를 이용합니다.
- 해당 모델을 이용하는 pipeline 예시는 Triangle 모델 파이프라인 예시를 참고하세요.
from nufi.models import BaseModel
from nufi.core.postprocess import Box, NufiResult
class MidRectanglePrinter(BaseModel):
def __init__(self, id):
self.id = id
print(f"Initialize {self.id} rectangle printer")
def process(
self, image: ndarray, pipeline_id: Optional[int] = None
) -> List[NufiResult]:
box_points = self._calculate_center_box(image)
box = Box(
"rectangle",
box_points,
(255, 0, 0),
is_draw=True,
)
return [NufiResult(drawables=[box])]
def clean(self, pipeline_id: Optional[int] = None):
return super().clean(pipeline_id)
def _calculate_center_box(self, image: ndarray) -> List[int]:
height, width = image.shape[:2]
box_width = width // 4
box_height = height // 4
x1 = (width - box_width) // 2
y1 = (height - box_height) // 2
x2 = x1 + box_width
y2 = y1 + box_height
return [x1, y1, x2, y2]
Drawable 예제 - 커스텀하게 Drawable 구현체를 만드는 경우
- 커스텀 Drawable 구현체인 Triangle을 만듭니다.
- Triangle은
draw
메소드에서 삼각형을 이미지에 그리도록 설계했습니다.
- Triangle은
- 이를 이용해서 MidTrianglePrinter Model에서 NufiResult를 반환합니다.
- 해당 모델을 이용하는 pipeline 예시는 Triangle 모델 파이프라인 예시를 참고하세요.
from nufi.models import BaseModel
from nufi.core.postprocess import NufiResult, Drawable
class Triangle(Drawable):
def __init__(
self,
xyxyxy: List[tuple],
color=(0, 0, 0),
thickness=2,
is_draw=True,
):
self.xyxyxy = xyxyxy
self.color = color
self.thickness = thickness
self.is_draw = is_draw
def draw(self, image: ndarray):
if self.is_draw:
triangle_pts = np.array(self.xyxyxy, np.int32)
cv2.polylines(
image,
[triangle_pts],
isClosed=True,
color=self.color,
thickness=self.thickness,
)
class MidTrianglePrinter(BaseModel):
def __init__(self, id):
self.id = id
print(f"Initialize {self.id} rectangle printer")
def process(
self, image: ndarray, pipeline_id: Optional[int] = None
) -> List[NufiResult]:
points = self.calculate_points(image)
triangle = Triangle(
xyxyxy=points,
color=(255, 0, 0),
thickness=3,
is_draw=True,
)
return [NufiResult(drawables=[triangle])]
def clean(self, pipeline_id: Optional[int] = None):
return super().clean(pipeline_id)
def calculate_points(self, image: ndarray) -> List[int]:
height, width = image.shape[:2]
tri_width = width // 3
tri_height = height // 3
x1 = (width - tri_width) // 2
y1 = height - tri_height
x2 = x1 + tri_width
y2 = y1
x3 = (x1 + x2) // 2
y3 = y2 - tri_height
return [(x1, y1), (x2, y2), (x3, y3)]
NufiResult 예제 - data property 설정하는 방법
Class
- Class를 NufiResult data property에 넣기 위해선 nufi.core.postprocess 하위의
SerializableBase
를 상속해야합니다.SerializableBase
에 의해서 Json encode, decode가 가능해집니다.
import numpy as np
from nufi.models import BaseModel, SerializableBase
from nufi.core.postprocess import Box, NufiResult
class ImageFrameInfo(SerializableBase):
def __init__(self, width, height):
self.width = width
self.height = height
def __repr__(self) -> str:
return f"ImageFrameInfo(width={self.width}, height={self.height})"
class MidRectanglePrinter(BaseModel):
def __init__(self, id):
self.id = id
print(f"Initialize {self.id} rectangle printer")
def process(
self, image: np.ndarray, pipeline_id: Optional[int] = None
) -> List[NufiResult]:
box_points = self._calculate_center_box(image)
height, width = image.shape[:2]
data = ImageFrameInfo(width=width, height=height)
box = Box(
"rectangle",
box_points,
(255, 0, 0),
is_draw=True,
)
return [NufiResult(data=data, drawables=[box])]
def clean(self, pipeline_id: Optional[int] = None):
return super().clean(pipeline_id)
def _calculate_center_box(self, image: ndarray) -> List[int]:
height, width = image.shape[:2]
box_width = width // 4
box_height = height // 4
x1 = (width - box_width) // 2
y1 = (height - box_height) // 2
x2 = x1 + box_width
y2 = y1 + box_height
return [x1, y1, x2, y2]
Dictionary
- image width, height 정보를 data에 dictionary로 변환하여 입력합니다.
import numpy as np
from nufi.models import BaseModel, SerializableBase
from nufi.core.postprocess import Box, NufiResult
class MidRectanglePrinter(BaseModel):
def __init__(self, id: int):
self.id = id
print(f"Initialize {self.id} rectangle printer")
def process(
self, image: np.ndarray, pipeline_id: Optional[int] = None
) -> List[NufiResult]:
box_points = self._calculate_center_box(image)
height, width = image.shape[:2]
data = {"width": width, "height": height} # dictionary NufiResult에 입력
box = Box(
"rectangle",
box_points,
(255, 0, 0),
is_draw=True,
)
return [NufiResult(data=data, drawables=[box])]
def clean(self, pipeline_id: Optional[int] = None):
return super().clean(pipeline_id)
def _calculate_center_box(self, image: ndarray) -> List[int]:
height, width = image.shape[:2]
box_width = width // 4
box_height = height // 4
x1 = (width - box_width) // 2
y1 = (height - box_height) // 2
x2 = x1 + box_width
y2 = y1 + box_height
return [x1, y1, x2, y2]
Primitive Type
- MidRectanglePrinter id 정보를 data에 primitive type으로 입력합니다.
import numpy as np
from nufi.models import BaseModel, SerializableBase
from nufi.core.postprocess import Box, NufiResult
class MidRectanglePrinter(BaseModel):
def __init__(self, id):
self.id = id
print(f"Initialize {self.id} rectangle printer")
def process(
self, image: np.ndarray, pipeline_id: Optional[int] = None
) -> List[NufiResult]:
box_points = self._calculate_center_box(image)
data = self.id # primitive type NufiResult에 입력
box = Box(
"rectangle",
box_points,
(255, 0, 0),
is_draw=True,
)
return [NufiResult(data=data, drawables=[box])]
def clean(self, pipeline_id: Optional[int] = None):
return super().clean(pipeline_id)
def _calculate_center_box(self, image: ndarray) -> List[int]:
height, width = image.shape[:2]
box_width = width // 4
box_height = height // 4
x1 = (width - box_width) // 2
y1 = (height - box_height) // 2
x2 = x1 + box_width
y2 = y1 + box_height
return [x1, y1, x2, y2]