"""Classes to be used by the different processing functions. These classes are wrappers around their corresponding structs defined in c_interface/include/model_res.h. They will hold results after the appropriate C processing function is called. """ import ctypes import string from typing import List, Mapping, NewType, Optional, TypeVar, Union import numpy as np import numpy.typing as npt BOXES_MAX_NUM = 80 CLASSIFIER_MAX_NUM = 1000 FR_FEATURE_MAP_SIZE = 256 KEYPOINT_POINTS = 11 LANDMARK_POINTS = 5 MAX_KEYPOINTS = 100 MAX_ONET_POINTS = 100 MAX_YOLO_FACE_LANDMARK_CNT = 8 OCR_MAX_NUM = 20 SEG_WIDTH = 80 SEG_HEIGHT = 60 class AgeGenderResult(ctypes.Structure): """Age and gender result. Attributes: age: An integer indicating the age of the detected person. gender: An integer indicating the gender of detected person (0: female, 1: male). """ _fields_ = [("age", ctypes.c_uint32), ("ismale", ctypes.c_uint32)] def __init__(self, age: int = 0, ismale: int = 0): self.age = age self.ismale = ismale super().__init__() def __repr__(self): gender = "male" if self.ismale else "female" return ("---------------------------- Age Gender Result ----------------------------\n" f"Age: {self.age}\nGender: {gender}\n") class BoundingBox(ctypes.Structure): """Box that outlines the detected object in the image. Attributes: x1: A float x coordinate of the top left corner. y1: A float y coordinate of the top left corner. x2: A float x coordinate of the bottom right corner. y2: A float y coordinate of the bottom right corner. score: A float indicating the probability score of the box. class_num: An integer indicating the class with the highest score. """ _fields_ = [("x1", ctypes.c_float), ("y1", ctypes.c_float), ("x2", ctypes.c_float), ("y2", ctypes.c_float), ("score", ctypes.c_float), ("class_num", ctypes.c_int32)] def __init__(self, x1: float = 0, y1: float = 0, x2: float = 0, y2: float = 0, score: float = 0, class_num: int = 0): self.x1 = x1 self.y1 = y1 self.x2 = x2 self.y2 = y2 self.score = score self.class_num = class_num super().__init__() def __repr__(self): box = f"Box (x1, y1, x2, y2): ({self.x1}, {self.y1}, {self.x2}, {self.y2})" return ("------------------------------ Bounding Box -------------------------------\n" f"{box}\nScore: {self.score}\nClass number: {self.class_num}\n") def to_np(self, box_type: str = "xyxy", to_round: bool = False) -> npt.NDArray: """Returns a NumPy array of the 6 data values. Args: box_type: String indicating the format to return the box coordinates. "xyxy" or "xywh". to_round: If True, box coordinate values will be rounded to the nearest integer. If box_type is "xywh", rounding will be done first before subtraction. Returns: A NumPy array of length 6 with either of the following formats, depending on the provided box_type: "xyxy": [x1, y1, x2, y2, score, class_num] "xywh": [x1, y1, w, h, score, class_num] Raises: ValueError: If box_type is not supported """ box = np.array([self.x1, self.y1, self.x2, self.y2, self.score, self.class_num]) if to_round: box[:4] = np.round(box[:4]) if box_type == "xyxy": return box elif box_type == "xywh": box[2] -= box[0] box[3] -= box[1] return box else: raise ValueError(f"to_np (BoundingBox): box_type must be one of " f"['xyxy', 'xywh'], not {box_type}") class BoundingBoxLandmark(ctypes.Structure): """Box that outlines the detected object in the image, including landmark data. Attributes: x1: A float x coordinate of the top left corner. y1: A float y coordinate of the top left corner. x2: A float x coordinate of the bottom right corner. y2: A float y coordinate of the bottom right corner. score: A float indicating the probability score of the box. class_num: An integer indicating the class with the highest score. lm: A list of floats indicating the landmark coordinates. """ _fields_ = [("x1", ctypes.c_float), ("y1", ctypes.c_float), ("x2", ctypes.c_float), ("y2", ctypes.c_float), ("score", ctypes.c_float), ("class_num", ctypes.c_int32), ("lm", ctypes.c_float * MAX_YOLO_FACE_LANDMARK_CNT)] def __init__(self, x1: float = 0, y1: float = 0, x2: float = 0, y2: float = 0, score: float = 0, class_num: int = 0, lm: Optional[float] = None): self.x1 = x1 self.y1 = y1 self.x2 = x2 self.y2 = y2 self.score = score self.class_num = class_num if lm is not None: self.lm = (ctypes.c_float * MAX_YOLO_FACE_LANDMARK_CNT)(*lm) super().__init__() def __repr__(self): box = f"Box (x1, y1, x2, y2): ({self.x1}, {self.y1}, {self.x2}, {self.y2})" lm_repr = "Landmarks:\n" for (index, landmark) in enumerate(self.lm): if index % 2: # y lm_repr += f"{landmark})\n" else: # x lm_repr += f"LM[{index // 2}] (x, y): ({landmark}, " return ("------------------------- Bounding Box + Landmark -------------------------\n" f"{box}\nScore: {self.score}\nClass number: {self.class_num}\n{lm_repr}\n") def box_to_np(self, box_type: str = "xyxy", to_round: bool = False) -> npt.NDArray: """Returns a NumPy array of the bounding box data. Args: box_type: String indicating the format to return the box coordinates. "xyxy" or "xywh". to_round: If True, box coordinate values will be rounded to the nearest integer. If box_type is "xywh", rounding will be done first before subtraction. Returns: A NumPy array of length 6 with either of the following formats, depending on the provided box_type: "xyxy": [x1, y1, x2, y2, score, class_num] "xywh": [x1, y1, w, h, score, class_num] Raises: ValueError: If box_type is not supported """ box = np.array([self.x1, self.y1, self.x2, self.y2, self.score, self.class_num]) if to_round: box[:4] = np.round(box[:4]) if box_type == "xyxy": return box elif box_type == "xywh": box[2] -= box[0] box[3] -= box[1] return box else: raise ValueError(f"to_np (BoundingBox): box_type must be one of " f"['xyxy', 'xywh'], not {box_type}") def lm_to_np(self) -> npt.NDArray: """Returns a NumPy array of the landmark data.""" return np.array(self.lm) class BoundingBoxLandmarkPlus(ctypes.Structure): """Box that outlines the detected object in the image, including landmarks and top 2 scores. Attributes: x1: A float x coordinate of the top left corner. y1: A float y coordinate of the top left corner. x2: A float x coordinate of the bottom right corner. y2: A float y coordinate of the bottom right corner. score: A float indicating the probability score of the box. class_num: An integer indicating the class with the highest score. score_next: A float indicating the probability score of the box. class_num_next: An integer indicating the class with the highest score. lm: A list of floats indicating the landmark coordinates. """ _fields_ = [("x1", ctypes.c_float), ("y1", ctypes.c_float), ("x2", ctypes.c_float), ("y2", ctypes.c_float), ("score", ctypes.c_float), ("class_num", ctypes.c_int32), ("score_next", ctypes.c_float), ("class_num_next", ctypes.c_int32), ("lm", ctypes.c_float * MAX_YOLO_FACE_LANDMARK_CNT)] def __init__(self, x1: float = 0, y1: float = 0, x2: float = 0, y2: float = 0, score: float = 0, class_num: int = 0, score_next: float = 0, class_num_next: int = 0, lm: Optional[float] = None): self.x1 = x1 self.y1 = y1 self.x2 = x2 self.y2 = y2 self.score = score self.class_num = class_num self.score_next = score_next self.class_num_next = class_num_next if lm is not None: self.lm = (ctypes.c_float * MAX_YOLO_FACE_LANDMARK_CNT)(*lm) super().__init__() def __repr__(self): box = f"Box (x1, y1, x2, y2): ({self.x1}, {self.y1}, {self.x2}, {self.y2})" lm_repr = "Landmarks:\n" for (index, landmark) in enumerate(self.lm): if index % 2: # y lm_repr += f"{landmark})\n" else: # x lm_repr += f"LM[{index // 2}] (x, y): ({landmark}, " return ("------------------------- Bounding Box + Landmark -------------------------\n" f"{box}\nScore: {self.score}\nClass number: {self.class_num}\n2nd score: " f"{self.score_next}\n2nd class number: {self.class_num_next}\n{lm_repr}\n") def box_to_np(self, box_type: str = "xyxy", to_round: bool = False, second_best: bool = False) -> npt.NDArray: """Returns a NumPy array of the bounding box data. Args: box_type: String indicating the format to return the box coordinates. "xyxy" or "xywh". to_round: If True, box coordinate values will be rounded to the nearest integer. If box_type is "xywh", rounding will be done first before subtraction. second_best: If True, box will also have the second highest score and class number. Returns: A NumPy array of length 6 with either of the following formats, depending on the provided box_type: "xyxy": [x1, y1, x2, y2, score, class_num] "xywh": [x1, y1, w, h, score, class_num] If second_best is set, each box will have two additional values appended to the end: second highest score and corresponding class number. Raises: ValueError: If box_type is not supported """ if second_best: box = np.array([self.x1, self.y1, self.x2, self.y2, self.score, self.class_num, self.score_next, self.class_num_next]) else: box = np.array([self.x1, self.y1, self.x2, self.y2, self.score, self.class_num]) if to_round: box[:4] = np.round(box[:4]) if box_type == "xyxy": return box elif box_type == "xywh": box[2] -= box[0] box[3] -= box[1] return box else: raise ValueError(f"to_np (BoundingBox): box_type must be one of " f"['xyxy', 'xywh'], not {box_type}") def lm_to_np(self) -> npt.NDArray: """Returns a NumPy array of the landmark data.""" return np.array(self.lm) class ClassifierResult(ctypes.Structure): """Classifier result. Attributes: score: A float array holding scores for each class index """ _fields_ = [("score", ctypes.c_float * CLASSIFIER_MAX_NUM)] def __init__(self, score: Optional[List[float]] = None): if score is not None: self.score = (ctypes.c_float * CLASSIFIER_MAX_NUM)(*score) super().__init__() def __repr__(self): first_ten = "Displaying first 10 scores:\n" for index, score in enumerate(self.score[:10]): first_ten += f"Score[{index}]: {score}\n" return ("---------------------------- Classifier Result ----------------------------\n" f"{first_ten}\n") class FaceOccludeResult(ctypes.Structure): """Face pose result. Attributes: yaw: A float indicating yaw value. pitch: A float indicating pitch value. roll: A float indicating roll value. occ: A float indicating occlusion value. """ _fields_ = [("yaw", ctypes.c_float), ("pitch", ctypes.c_float), ("roll", ctypes.c_float), ("occ", ctypes.c_float)] def __init__(self, yaw: float = 0, pitch: float = 0, roll: float = 0, occ: float = 0): self.yaw = yaw self.pitch = pitch self.roll = roll self.occ = occ super().__init__() def __repr__(self): return ("--------------------------- Face Occlude Result ---------------------------\n" f"Yaw: {self.yaw}\nPitch: {self.pitch}\nRoll: {self.roll}\nOcc: {self.occ}") class FRResult(ctypes.Structure): """Face recognition result. Attributes: feature_map: A list of floats indicating the feature map. """ _fields_ = [("feature_map", ctypes.c_float * FR_FEATURE_MAP_SIZE)] def __init__(self, feature_map: Optional[List[float]] = None): if feature_map is not None: self.feature_map = (ctypes.c_float * FR_FEATURE_MAP_SIZE)(*feature_map) super().__init__() def __repr__(self): first_ten = "Displaying first 10 feature map values:\n" for index, value in enumerate(self.feature_map[:10]): first_ten += f"FR[{index}]: {value}\n" return ("-------------------------------- FR Result --------------------------------\n" f"{first_ten}\n") class LandmarkPoint(ctypes.Structure): """Landmark point in integer format. Attributes: x: An integer indicating the x coordinate. y: An integer indicating the y coordinate. """ _fields_ = [("x", ctypes.c_uint32), ("y", ctypes.c_uint32)] def __init__(self, x: int = 0, y: int = 0): self.x = x self.y = y super().__init__() def __repr__(self): return f"Landmark point (x, y): ({self.x}, {self.y})" class LandmarkPointFloat(ctypes.Structure): """Landmark point in floating point format. Attributes: x: A float indicating the x coordinate. y: A float indicating the y coordinate. """ _fields_ = [("x", ctypes.c_float), ("y", ctypes.c_float)] def __init__(self, x: float = 0, y: float = 0): self.x = x self.y = y super().__init__() def __repr__(self): return f"Landmark point (x, y): ({self.x}, {self.y})" class LandmarkResult(ctypes.Structure): """Landmark result for variable number of landmarks. Attributes: marks: List of LandmarkPoint instances. score: A float indicating the probability score. blur: A float indicating the blur. """ _fields_ = [("marks", LandmarkPoint * MAX_ONET_POINTS), ("score", ctypes.c_float), ("blur", ctypes.c_float)] def __init__(self, marks: Optional[List[LandmarkPoint]] = None, score: float = 0, blur: float = 0): if marks is not None: self.marks = (LandmarkPoint * MAX_ONET_POINTS)(*marks) self.score = score self.blur = blur super().__init__() def __repr__(self): landmarks = "Landmarks:\n" for index, point in enumerate(self.marks): landmarks += f"#{index} {point}\n" return ("----------------------------- Landmark Result -----------------------------\n" f"{landmarks}\nScore: {self.score}\nBlur: {self.blur}") class LandmarkResult5p(ctypes.Structure): """Landmark result for 5 landmark points. Attributes: marks: List of LandmarkPoint instances. score: A float indicating the probability score. blur: A float indicating the blur. class_num: An integer indicating the class number with the highest score. """ _fields_ = [("marks", LandmarkPoint * LANDMARK_POINTS), ("score", ctypes.c_float), ("blur", ctypes.c_float), ("class_num", ctypes.c_int32)] def __init__(self, marks: Optional[List[LandmarkPoint]] = None, score: float = 0, blur: float = 0, class_num: int = 0): if marks is not None: self.marks = (LandmarkPoint * LANDMARK_POINTS)(*marks) self.score = score self.blur = blur self.class_num = class_num super().__init__() def __repr__(self): landmarks = "Landmarks:\n" for index, point in enumerate(self.marks): landmarks += f"#{index} {point}\n" return ("----------------------------- Landmark Result -----------------------------\n" f"{landmarks}\nScore: {self.score}\nBlur: {self.blur}\nClass number: " f"{self.class_num}") class Keypoint(ctypes.Structure): """Keypoint. Attributes: x: A float indicating the x coordinate. y: A float indicating the y coordinate. """ _fields_ = [("x", ctypes.c_float), ("y", ctypes.c_float)] def __init__(self, x: float = 0, y: float = 0): self.x = x self.y = y super().__init__() def __repr__(self): return f"Keypoint point (x, y): ({self.x}, {self.y})" class KeyPointResult(ctypes.Structure): """Keypoint result. Attributes: point_count: An integer indicating the number of points found. points: List of Keypoint instances. scores: A list of floats indicating the probability scores for each Keypoint. """ _fields_ = [("point_count", ctypes.c_uint32), ("points", Keypoint * MAX_KEYPOINTS), ("scores", ctypes.c_float * MAX_KEYPOINTS)] def __init__(self, point_count: int = 0, points: Optional[List[Keypoint]] = None, scores: Optional[List[float]] = None): self.point_count = point_count if points is not None: self.points = (Keypoint * MAX_KEYPOINTS)(*points) if scores is not None: self.scores = (ctypes.c_float * MAX_KEYPOINTS)(*scores) super().__init__() def __repr__(self): keypoint = "Keypoints:\n" for index, (point, score) in enumerate(zip(self.points[:self.point_count], self.scores[:self.point_count])): keypoint += f"#{index} {point}, Score: {score}\n" return ("----------------------------- Keypoint Result -----------------------------\n" f"{keypoint}") class OnetPlusResult(ctypes.Structure): """ONET plus result. Attributes: marks: List of LandmarkPointFloat instances. scores: A list of floats indicating the probability scores for each landmark. """ _fields_ = [("marks", LandmarkPointFloat * LANDMARK_POINTS), ("scores", ctypes.c_float * LANDMARK_POINTS)] def __init__(self, marks: Optional[List[LandmarkPointFloat]] = None, scores: Optional[List[float]] = None): if marks is not None: self.marks = (LandmarkPointFloat * LANDMARK_POINTS)(*marks) if scores is not None: self.scores = (ctypes.c_float * LANDMARK_POINTS)(*scores) super().__init__() def __repr__(self): landmarks = "Landmarks:\n" for index, point in enumerate(self.marks): landmarks += (f"#{index} {point}; score: {self.scores[index]}\n") return ("----------------------------- ONET Plus Result ----------------------------\n" f"{landmarks}") class OCRResult(ctypes.Structure): """Licenseplate OCR result. Attributes: char_count: An integer indicating the number of characters found. char_boxes: Sequence of BoundingBox instances found in an image. valid: An integer indicating if the plate is valid. hyphen: An integer indicating the position the hyphen comes after. """ _fields_ = [("char_count", ctypes.c_uint32), ("char_boxes", BoundingBox * OCR_MAX_NUM), ("valid", ctypes.c_uint8), ("hyphen", ctypes.c_uint8)] char_map = string.digits + string.ascii_uppercase def __init__(self, char_count: int = 0, char_boxes: Optional[BoundingBox] = None, valid: int = 0, hyphen: int = 0): self.char_count = char_count if char_boxes is not None: self.char_boxes = (BoundingBox * OCR_MAX_NUM)(*char_boxes) self.valid = valid self.hyphen = hyphen super().__init__() def __repr__(self): box_repr = "" for index, box in enumerate(self.char_boxes[:self.char_count]): box_repr += f"\nBox #{index}:\n{box}\n" return ("------------------------------- OCR Result --------------------------------\n" f"Plate: {self.get_plate()}\nBox_count: {self.char_count}\n{box_repr}\n") def get_plate(self, input_map: Optional[Mapping[str, str]] = None): """Returns the plate value from the detected boxes. Args: input_map: Mapping from string representing the class number to a string representing the actual character value. Returns: A string representing the values on the licenseplate. """ if not self.valid: return "" if input_map is None: char_map = self.char_map else: char_map = list(input_map.values()) chars = [char_map[box.class_num] for box in self.char_boxes[:self.char_count]] if self.hyphen: # hyphen is the character after which it should be placed chars = [*chars[:self.hyphen + 1], "-", *chars[self.hyphen + 1:]] return "".join(chars) class PersonHeadResult(ctypes.Structure): """Results of person bounding boxes mapped to head bounding boxes. Attributes: num_persons: An integer indicating number of total people. num_heads: An integer indicating number of matching heads. person_boxes: List of all person BoundingBox instances. head_boxes: List of all matching head BoundingBox instances. """ _fields_ = [("num_persons", ctypes.c_uint32), ("num_heads", ctypes.c_uint32), ("person_boxes", BoundingBox * BOXES_MAX_NUM), ("head_boxes", BoundingBox * BOXES_MAX_NUM)] def __init__(self, num_persons: int = 0, num_heads: int = 0, person_boxes: Optional[List[BoundingBox]] = None, head_boxes: Optional[List[BoundingBox]] = None): self.num_persons = num_persons self.num_heads = num_heads if person_boxes is not None: self.person_boxes = (BoundingBox * BOXES_MAX_NUM)(*person_boxes) if head_boxes is not None: self.head_boxes = (BoundingBox * BOXES_MAX_NUM)(*head_boxes) super().__init__() def __repr__(self): box_repr = "" for index, (person_box, head_box) in enumerate(zip(self.person_boxes[:self.num_persons], self.head_boxes[:self.num_heads])): box_repr += f"\nPerson #{index}:\n{person_box}\nCorresponding head:\n{head_box}\n" return ("--------------------------- Person Head Result ----------------------------\n" f"Person count: {self.num_persons}\nHead count: {self.num_heads}\n{box_repr}\n") class SegResult(ctypes.Structure): """Semantic segmentation result. Attributes: seg_class_result: List of integers indicating the class number for each image pixel. """ _fields_ = [("seg_class_result", ctypes.c_uint32 * (SEG_WIDTH * SEG_HEIGHT))] def __init__(self, seg_class_result: Optional[List[int]] = None): if seg_class_result is not None: self.seg_class_result = (ctypes.c_uint32 * (SEG_WIDTH * SEG_HEIGHT))(*seg_class_result) super().__init__() def __repr__(self): first_ten = "Displaying first 10 pixels:\n" for index, value in enumerate(self.seg_class_result[:10]): first_ten += f"seg[{index}]: {value}\n" return ("--------------------------- Segmentation Result ---------------------------\n" f"{first_ten}") def to_np(self) -> npt.NDArray[np.uint32]: """Returns a NumPy array of the class numbers for each pixel.""" classes = np.asarray(self.seg_class_result, dtype=np.uint32) return np.reshape(classes, (SEG_HEIGHT, SEG_WIDTH)) class UpperbodyKeypointResult(ctypes.Structure): """Upperbody keypoint result. Attributes: marks: List of Keypoint instances. """ _fields_ = [("marks", Keypoint * KEYPOINT_POINTS)] def __init__(self, marks: Optional[List[Keypoint]] = None): if marks is not None: self.marks = (Keypoint * KEYPOINT_POINTS)(*marks) super().__init__() def __repr__(self): keypoint = "Keypoints:\n" for index, point in enumerate(self.marks): keypoint += f"#{index} {point}\n" return ("----------------------------- Keypoint Result -----------------------------\n" f"{keypoint}") class YoloResult(ctypes.Structure): """Results of all bounding boxes found in an image. Attributes: class_count: An integer indicating total possible classes. box_count: An integer indicating number of found boxes. boxes: List of BoundingBox instances found in an image. """ _fields_ = [("class_count", ctypes.c_uint32), ("box_count", ctypes.c_uint32), ("boxes", BoundingBox * BOXES_MAX_NUM)] def __init__(self, class_count: int = 0, box_count: int = 0, boxes: Optional[List[BoundingBox]] = None): self.class_count = class_count self.box_count = box_count if boxes is not None: self.boxes = (BoundingBox * BOXES_MAX_NUM)(*boxes) super().__init__() def __repr__(self): box_repr = "" for index, box in enumerate(self.boxes[:self.box_count]): box_repr += f"\nBox #{index}:\n{box}\n" return ("------------------------------- Yolo Result -------------------------------\n" f"Class count: {self.class_count}\nBox_count: {self.box_count}\n{box_repr}\n") class YoloLandmarkResult(ctypes.Structure): """Results of all bounding boxes (including landmark data) found in an image. Attributes: class_count: An integer indicating total possible classes. box_count: An integer indicating number of found boxes. boxes: List of BoundingBoxLandmark instances found in an image. """ _fields_ = [("class_count", ctypes.c_uint32), ("box_count", ctypes.c_uint32), ("boxes", BoundingBoxLandmark * BOXES_MAX_NUM)] def __init__(self, class_count: int = 0, box_count: int = 0, boxes: Optional[List[BoundingBoxLandmark]] = None): self.class_count = class_count self.box_count = box_count if boxes is not None: self.boxes = (BoundingBoxLandmark * BOXES_MAX_NUM)(*boxes) super().__init__() def __repr__(self): box_repr = "" for index, box in enumerate(self.boxes[:self.box_count]): box_repr += f"\nBox #{index}:\n{box}\n" return ("------------------------------- Yolo Result -------------------------------\n" f"Class count: {self.class_count}\nBox_count: {self.box_count}\n{box_repr}\n") class YoloxLandmarkResult(ctypes.Structure): """Results of all bounding boxes (including landmark data) found in an image. Attributes: class_count: An integer indicating total possible classes. box_count: An integer indicating number of found boxes. boxes: List of BoundingBoxLandmarkPlus instances found in an image. """ _fields_ = [("class_count", ctypes.c_uint32), ("box_count", ctypes.c_uint32), ("boxes", BoundingBoxLandmarkPlus * BOXES_MAX_NUM)] def __init__(self, class_count: int = 0, box_count: int = 0, boxes: Optional[List[BoundingBoxLandmarkPlus]] = None): self.class_count = class_count self.box_count = box_count if boxes is not None: self.boxes = (BoundingBoxLandmarkPlus * BOXES_MAX_NUM)(*boxes) super().__init__() def __repr__(self): box_repr = "" for index, box in enumerate(self.boxes[:self.box_count]): box_repr += f"\nBox #{index}:\n{box}\n" return ("------------------------------- Yolo Result -------------------------------\n" f"Class count: {self.class_count}\nBox_count: {self.box_count}\n{box_repr}\n") Det = NewType("Det", List[List[Union[float, int]]]) ResultClass = TypeVar("ResultClass", AgeGenderResult, ClassifierResult, FaceOccludeResult, FRResult, LandmarkResult, LandmarkResult5p, KeyPointResult, OnetPlusResult, OCRResult, PersonHeadResult, SegResult, UpperbodyKeypointResult, YoloResult, YoloLandmarkResult, YoloxLandmarkResult)