Browse Source

[to #42322933]video-object-detection init

新增video-object-detection 算法
        Link: https://code.alibaba-inc.com/Ali-MaaS/MaaS-lib/codereview/10247489
master
leyuan.hjy yingda.chen 3 years ago
parent
commit
172522d196
18 changed files with 886 additions and 5 deletions
  1. +3
    -0
      data/test/videos/test_realtime_vod.mp4
  2. +2
    -0
      modelscope/metainfo.py
  3. +2
    -0
      modelscope/models/cv/realtime_object_detection/__init__.py
  4. +117
    -0
      modelscope/models/cv/realtime_object_detection/realtime_video_detector.py
  5. +2
    -0
      modelscope/models/cv/realtime_object_detection/yolox/exp/build.py
  6. +1
    -1
      modelscope/models/cv/realtime_object_detection/yolox/exp/default/__init__.py
  7. +43
    -0
      modelscope/models/cv/realtime_object_detection/yolox/exp/default/streamyolo.py
  8. +0
    -1
      modelscope/models/cv/realtime_object_detection/yolox/exp/yolox_base.py
  9. +3
    -0
      modelscope/models/cv/realtime_object_detection/yolox/models/__init__.py
  10. +307
    -0
      modelscope/models/cv/realtime_object_detection/yolox/models/dfp_pafpn.py
  11. +0
    -1
      modelscope/models/cv/realtime_object_detection/yolox/models/network_blocks.py
  12. +41
    -0
      modelscope/models/cv/realtime_object_detection/yolox/models/streamyolo.py
  13. +170
    -0
      modelscope/models/cv/realtime_object_detection/yolox/models/tal_head.py
  14. +29
    -2
      modelscope/outputs.py
  15. +59
    -0
      modelscope/pipelines/cv/realtime_video_object_detection_pipeline.py
  16. +1
    -0
      modelscope/utils/constant.py
  17. +60
    -0
      modelscope/utils/cv/image_utils.py
  18. +46
    -0
      tests/pipelines/test_realtime_video_object_detection.py

+ 3
- 0
data/test/videos/test_realtime_vod.mp4 View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f58df1d25590c158ae0a04b3999bd44b610cdaddb17d78afd84c34b3f00d4e87
size 4068783

+ 2
- 0
modelscope/metainfo.py View File

@@ -14,6 +14,7 @@ class Models(object):
# vision models
detection = 'detection'
realtime_object_detection = 'realtime-object-detection'
realtime_video_object_detection = 'realtime-video-object-detection'
scrfd = 'scrfd'
classification_model = 'ClassificationModel'
nafnet = 'nafnet'
@@ -170,6 +171,7 @@ class Pipelines(object):
face_image_generation = 'gan-face-image-generation'
product_retrieval_embedding = 'resnet50-product-retrieval-embedding'
realtime_object_detection = 'cspnet_realtime-object-detection_yolox'
realtime_video_object_detection = 'cspnet_realtime-video-object-detection_streamyolo'
face_recognition = 'ir101-face-recognition-cfglint'
image_instance_segmentation = 'cascade-mask-rcnn-swin-image-instance-segmentation'
image2image_translation = 'image-to-image-translation'


+ 2
- 0
modelscope/models/cv/realtime_object_detection/__init__.py View File

@@ -5,9 +5,11 @@ from modelscope.utils.import_utils import LazyImportModule

if TYPE_CHECKING:
from .realtime_detector import RealtimeDetector
from .realtime_video_detector import RealtimeVideoDetector
else:
_import_structure = {
'realtime_detector': ['RealtimeDetector'],
'realtime_video_detector': ['RealtimeVideoDetector'],
}

import sys


+ 117
- 0
modelscope/models/cv/realtime_object_detection/realtime_video_detector.py View File

@@ -0,0 +1,117 @@
# Copyright (c) Alibaba, Inc. and its affiliates.
import argparse
import logging as logger
import os
import os.path as osp
import time

import cv2
import json
import torch
from tqdm import tqdm

from modelscope.metainfo import Models
from modelscope.models.base.base_torch_model import TorchModel
from modelscope.models.builder import MODELS
from modelscope.preprocessors import LoadImage
from modelscope.utils.config import Config
from modelscope.utils.constant import ModelFile, Tasks
from .yolox.data.data_augment import ValTransform
from .yolox.exp import get_exp_by_name
from .yolox.utils import postprocess


@MODELS.register_module(
group_key=Tasks.video_object_detection,
module_name=Models.realtime_video_object_detection)
class RealtimeVideoDetector(TorchModel):

def __init__(self, model_dir: str, *args, **kwargs):
super().__init__(model_dir, *args, **kwargs)
self.config = Config.from_file(
os.path.join(self.model_dir, ModelFile.CONFIGURATION))

# model type
self.exp = get_exp_by_name(self.config.model_type)

# build model
self.model = self.exp.get_model()
model_path = osp.join(model_dir, ModelFile.TORCH_MODEL_BIN_FILE)
ckpt = torch.load(model_path, map_location='cpu')

# load the model state dict
self.model.load_state_dict(ckpt['model'])
self.model.eval()

# params setting
self.exp.num_classes = self.config.num_classes
self.confthre = self.config.conf_thr
self.num_classes = self.exp.num_classes
self.nmsthre = self.exp.nmsthre
self.test_size = self.exp.test_size
self.preproc = ValTransform(legacy=False)
self.current_buffer = None
self.label_mapping = self.config['labels']

def inference(self, img):
with torch.no_grad():
outputs, self.current_buffer = self.model(
img, buffer=self.current_buffer, mode='on_pipe')
return outputs

def forward(self, inputs):
return self.inference_video(inputs)

def preprocess(self, img):
img = LoadImage.convert_to_ndarray(img)
height, width = img.shape[:2]
self.ratio = min(self.test_size[0] / img.shape[0],
self.test_size[1] / img.shape[1])

img, _ = self.preproc(img, None, self.test_size)
img = torch.from_numpy(img).unsqueeze(0)
img = img.float()

# Video decoding and preprocessing automatically are not supported by Pipeline/Model
# Sending preprocessed video frame tensor to GPU buffer self-adaptively
if next(self.model.parameters()).is_cuda:
img = img.to(next(self.model.parameters()).device)
return img

def postprocess(self, input):
outputs = postprocess(
input,
self.num_classes,
self.confthre,
self.nmsthre,
class_agnostic=True)

if len(outputs) == 1:
bboxes = outputs[0][:, 0:4].cpu().numpy() / self.ratio
scores = outputs[0][:, 5].cpu().numpy()
labels = outputs[0][:, 6].cpu().int().numpy()
pred_label_names = []
for lab in labels:
pred_label_names.append(self.label_mapping[lab])

return bboxes, scores, pred_label_names

def inference_video(self, v_path):
outputs = []
desc = 'Detecting video: {}'.format(v_path)
for frame, result in tqdm(
self.inference_video_iter(v_path), desc=desc):
outputs.append(result)

return outputs

def inference_video_iter(self, v_path):
capture = cv2.VideoCapture(v_path)
while capture.isOpened():
ret, frame = capture.read()
if not ret:
break
output = self.preprocess(frame)
output = self.inference(output)
output = self.postprocess(output)
yield frame, output

+ 2
- 0
modelscope/models/cv/realtime_object_detection/yolox/exp/build.py View File

@@ -13,6 +13,8 @@ def get_exp_by_name(exp_name):
from .default import YoloXNanoExp as YoloXExp
elif exp == 'yolox_tiny':
from .default import YoloXTinyExp as YoloXExp
elif exp == 'streamyolo':
from .default import StreamYoloExp as YoloXExp
else:
pass
return YoloXExp()

+ 1
- 1
modelscope/models/cv/realtime_object_detection/yolox/exp/default/__init__.py View File

@@ -1,5 +1,5 @@
# The implementation is based on YOLOX, available at https://github.com/Megvii-BaseDetection/YOLOX
from .streamyolo import StreamYoloExp
from .yolox_nano import YoloXNanoExp
from .yolox_s import YoloXSExp
from .yolox_tiny import YoloXTinyExp

+ 43
- 0
modelscope/models/cv/realtime_object_detection/yolox/exp/default/streamyolo.py View File

@@ -0,0 +1,43 @@
# The implementation is based on StreamYOLO, available at https://github.com/yancie-yjr/StreamYOLO
import os
import sys

import torch

from ..yolox_base import Exp as YoloXExp


class StreamYoloExp(YoloXExp):

def __init__(self):
super(YoloXExp, self).__init__()
self.depth = 1.0
self.width = 1.0
self.num_classes = 8
self.test_size = (600, 960)
self.test_conf = 0.3
self.nmsthre = 0.65

def get_model(self):
from ...models import StreamYOLO, DFPPAFPN, TALHead

def init_yolo(M):
for m in M.modules():
if isinstance(m, nn.BatchNorm2d):
m.eps = 1e-3
m.momentum = 0.03

if getattr(self, 'model', None) is None:
in_channels = [256, 512, 1024]
backbone = DFPPAFPN(
self.depth, self.width, in_channels=in_channels)
head = TALHead(
self.num_classes,
self.width,
in_channels=in_channels,
gamma=1.0,
ignore_thr=0.5,
ignore_value=1.6)
self.model = StreamYOLO(backbone, head)

return self.model

+ 0
- 1
modelscope/models/cv/realtime_object_detection/yolox/exp/yolox_base.py View File

@@ -1,5 +1,4 @@
# The implementation is based on YOLOX, available at https://github.com/Megvii-BaseDetection/YOLOX

import os
import random



+ 3
- 0
modelscope/models/cv/realtime_object_detection/yolox/models/__init__.py View File

@@ -1,6 +1,9 @@
# The implementation is based on YOLOX, available at https://github.com/Megvii-BaseDetection/YOLOX

from .darknet import CSPDarknet, Darknet
from .dfp_pafpn import DFPPAFPN
from .streamyolo import StreamYOLO
from .tal_head import TALHead
from .yolo_fpn import YOLOFPN
from .yolo_head import YOLOXHead
from .yolo_pafpn import YOLOPAFPN


+ 307
- 0
modelscope/models/cv/realtime_object_detection/yolox/models/dfp_pafpn.py View File

@@ -0,0 +1,307 @@
# The implementation is based on StreamYOLO, available at https://github.com/yancie-yjr/StreamYOLO
import torch
import torch.nn as nn
import torch.nn.functional as F

from .darknet import CSPDarknet
from .network_blocks import BaseConv, CSPLayer, DWConv


class DFPPAFPN(nn.Module):
"""
YOLOv3 model. Darknet 53 is the default backbone of this model.
"""

def __init__(
self,
depth=1.0,
width=1.0,
in_features=('dark3', 'dark4', 'dark5'),
in_channels=[256, 512, 1024],
depthwise=False,
act='silu',
):
super().__init__()
self.backbone = CSPDarknet(depth, width, depthwise=depthwise, act=act)
self.in_features = in_features
self.in_channels = in_channels
Conv = DWConv if depthwise else BaseConv

self.lateral_conv0 = BaseConv(
int(in_channels[2] * width),
int(in_channels[1] * width),
1,
1,
act=act)
self.C3_p4 = CSPLayer(
int(2 * in_channels[1] * width),
int(in_channels[1] * width),
round(3 * depth),
False,
depthwise=depthwise,
act=act,
) # cat

self.reduce_conv1 = BaseConv(
int(in_channels[1] * width),
int(in_channels[0] * width),
1,
1,
act=act)
self.C3_p3 = CSPLayer(
int(2 * in_channels[0] * width),
int(in_channels[0] * width),
round(3 * depth),
False,
depthwise=depthwise,
act=act,
)

# bottom-up conv
self.bu_conv2 = Conv(
int(in_channels[0] * width),
int(in_channels[0] * width),
3,
2,
act=act)
self.C3_n3 = CSPLayer(
int(2 * in_channels[0] * width),
int(in_channels[1] * width),
round(3 * depth),
False,
depthwise=depthwise,
act=act,
)

# bottom-up conv
self.bu_conv1 = Conv(
int(in_channels[1] * width),
int(in_channels[1] * width),
3,
2,
act=act)
self.C3_n4 = CSPLayer(
int(2 * in_channels[1] * width),
int(in_channels[2] * width),
round(3 * depth),
False,
depthwise=depthwise,
act=act,
)

self.jian2 = Conv(
in_channels=int(in_channels[0] * width),
out_channels=int(in_channels[0] * width) // 2,
ksize=1,
stride=1,
act=act,
)

self.jian1 = Conv(
in_channels=int(in_channels[1] * width),
out_channels=int(in_channels[1] * width) // 2,
ksize=1,
stride=1,
act=act,
)

self.jian0 = Conv(
in_channels=int(in_channels[2] * width),
out_channels=int(in_channels[2] * width) // 2,
ksize=1,
stride=1,
act=act,
)

def off_forward(self, input):
"""
Args:
inputs: input images.

Returns:
Tuple[Tensor]: FPN feature.
"""

# backbone
rurrent_out_features = self.backbone(torch.split(input, 3, dim=1)[0])
rurrent_features = [rurrent_out_features[f] for f in self.in_features]
[rurrent_x2, rurrent_x1, rurrent_x0] = rurrent_features

rurrent_fpn_out0 = self.lateral_conv0(rurrent_x0) # 1024->512/32
rurrent_f_out0 = F.interpolate(
rurrent_fpn_out0, size=rurrent_x1.shape[2:4],
mode='nearest') # 512/16
rurrent_f_out0 = torch.cat([rurrent_f_out0, rurrent_x1],
1) # 512->1024/16
rurrent_f_out0 = self.C3_p4(rurrent_f_out0) # 1024->512/16

rurrent_fpn_out1 = self.reduce_conv1(rurrent_f_out0) # 512->256/16
rurrent_f_out1 = F.interpolate(
rurrent_fpn_out1, size=rurrent_x2.shape[2:4],
mode='nearest') # 256/8
rurrent_f_out1 = torch.cat([rurrent_f_out1, rurrent_x2],
1) # 256->512/8
rurrent_pan_out2 = self.C3_p3(rurrent_f_out1) # 512->256/8

rurrent_p_out1 = self.bu_conv2(rurrent_pan_out2) # 256->256/16
rurrent_p_out1 = torch.cat([rurrent_p_out1, rurrent_fpn_out1],
1) # 256->512/16
rurrent_pan_out1 = self.C3_n3(rurrent_p_out1) # 512->512/16

rurrent_p_out0 = self.bu_conv1(rurrent_pan_out1) # 512->512/32
rurrent_p_out0 = torch.cat([rurrent_p_out0, rurrent_fpn_out0],
1) # 512->1024/32
rurrent_pan_out0 = self.C3_n4(rurrent_p_out0) # 1024->1024/32

#####

support_out_features = self.backbone(torch.split(input, 3, dim=1)[1])
support_features = [support_out_features[f] for f in self.in_features]
[support_x2, support_x1, support_x0] = support_features

support_fpn_out0 = self.lateral_conv0(support_x0) # 1024->512/32
support_f_out0 = F.interpolate(
support_fpn_out0, size=support_x1.shape[2:4],
mode='nearest') # 512/16
support_f_out0 = torch.cat([support_f_out0, support_x1],
1) # 512->1024/16
support_f_out0 = self.C3_p4(support_f_out0) # 1024->512/16

support_fpn_out1 = self.reduce_conv1(support_f_out0) # 512->256/16
support_f_out1 = F.interpolate(
support_fpn_out1, size=support_x2.shape[2:4],
mode='nearest') # 256/8
support_f_out1 = torch.cat([support_f_out1, support_x2],
1) # 256->512/8
support_pan_out2 = self.C3_p3(support_f_out1) # 512->256/8

support_p_out1 = self.bu_conv2(support_pan_out2) # 256->256/16
support_p_out1 = torch.cat([support_p_out1, support_fpn_out1],
1) # 256->512/16
support_pan_out1 = self.C3_n3(support_p_out1) # 512->512/16

support_p_out0 = self.bu_conv1(support_pan_out1) # 512->512/32
support_p_out0 = torch.cat([support_p_out0, support_fpn_out0],
1) # 512->1024/32
support_pan_out0 = self.C3_n4(support_p_out0) # 1024->1024/32

# 0.5 channel
pan_out2 = torch.cat(
[self.jian2(rurrent_pan_out2),
self.jian2(support_pan_out2)],
dim=1) + rurrent_pan_out2
pan_out1 = torch.cat(
[self.jian1(rurrent_pan_out1),
self.jian1(support_pan_out1)],
dim=1) + rurrent_pan_out1
pan_out0 = torch.cat(
[self.jian0(rurrent_pan_out0),
self.jian0(support_pan_out0)],
dim=1) + rurrent_pan_out0

outputs = (pan_out2, pan_out1, pan_out0)

return outputs

def online_forward(self, input, buffer=None, node='star'):
"""
Args:
inputs: input images.

Returns:
Tuple[Tensor]: FPN feature.
"""

# backbone
rurrent_out_features = self.backbone(input)
rurrent_features = [rurrent_out_features[f] for f in self.in_features]
[rurrent_x2, rurrent_x1, rurrent_x0] = rurrent_features

rurrent_fpn_out0 = self.lateral_conv0(rurrent_x0) # 1024->512/32
rurrent_f_out0 = F.interpolate(
rurrent_fpn_out0, size=rurrent_x1.shape[2:4],
mode='nearest') # 512/16
rurrent_f_out0 = torch.cat([rurrent_f_out0, rurrent_x1],
1) # 512->1024/16
rurrent_f_out0 = self.C3_p4(rurrent_f_out0) # 1024->512/16

rurrent_fpn_out1 = self.reduce_conv1(rurrent_f_out0) # 512->256/16
rurrent_f_out1 = F.interpolate(
rurrent_fpn_out1, size=rurrent_x2.shape[2:4],
mode='nearest') # 256/8
rurrent_f_out1 = torch.cat([rurrent_f_out1, rurrent_x2],
1) # 256->512/8
rurrent_pan_out2 = self.C3_p3(rurrent_f_out1) # 512->256/8

rurrent_p_out1 = self.bu_conv2(rurrent_pan_out2) # 256->256/16
rurrent_p_out1 = torch.cat([rurrent_p_out1, rurrent_fpn_out1],
1) # 256->512/16
rurrent_pan_out1 = self.C3_n3(rurrent_p_out1) # 512->512/16

rurrent_p_out0 = self.bu_conv1(rurrent_pan_out1) # 512->512/32
rurrent_p_out0 = torch.cat([rurrent_p_out0, rurrent_fpn_out0],
1) # 512->1024/32
rurrent_pan_out0 = self.C3_n4(rurrent_p_out0) # 1024->1024/32

#####
if node == 'star':
pan_out2 = torch.cat(
[self.jian2(rurrent_pan_out2),
self.jian2(rurrent_pan_out2)],
dim=1) + rurrent_pan_out2
pan_out1 = torch.cat(
[self.jian1(rurrent_pan_out1),
self.jian1(rurrent_pan_out1)],
dim=1) + rurrent_pan_out1
pan_out0 = torch.cat(
[self.jian0(rurrent_pan_out0),
self.jian0(rurrent_pan_out0)],
dim=1) + rurrent_pan_out0
elif node == 'buffer':

[support_pan_out2, support_pan_out1, support_pan_out0] = buffer

pan_out2 = torch.cat(
[self.jian2(rurrent_pan_out2),
self.jian2(support_pan_out2)],
dim=1) + rurrent_pan_out2
pan_out1 = torch.cat(
[self.jian1(rurrent_pan_out1),
self.jian1(support_pan_out1)],
dim=1) + rurrent_pan_out1
pan_out0 = torch.cat(
[self.jian0(rurrent_pan_out0),
self.jian0(support_pan_out0)],
dim=1) + rurrent_pan_out0

outputs = (pan_out2, pan_out1, pan_out0)

buffer_ = (rurrent_pan_out2, rurrent_pan_out1, rurrent_pan_out0)

return outputs, buffer_

def forward(self, input, buffer=None, mode='off_pipe'):

if mode == 'off_pipe':
# Glops caculate mode
if input.size()[1] == 3:
input = torch.cat([input, input], dim=1)
output = self.off_forward(input)
# offline train mode
elif input.size()[1] == 6:
output = self.off_forward(input)

return output

elif mode == 'on_pipe':
# online star state
if buffer is None:
output, buffer_ = self.online_forward(input, node='star')
# online inference
else:
assert len(buffer) == 3
assert input.size()[1] == 3
output, buffer_ = self.online_forward(
input, buffer=buffer, node='buffer')

return output, buffer_

+ 0
- 1
modelscope/models/cv/realtime_object_detection/yolox/models/network_blocks.py View File

@@ -1,5 +1,4 @@
# The implementation is based on YOLOX, available at https://github.com/Megvii-BaseDetection/YOLOX

import torch
import torch.nn as nn



+ 41
- 0
modelscope/models/cv/realtime_object_detection/yolox/models/streamyolo.py View File

@@ -0,0 +1,41 @@
# The implementation is based on StreamYOLO, available at https://github.com/yancie-yjr/StreamYOLO
import torch.nn as nn

from .dfp_pafpn import DFPPAFPN
from .tal_head import TALHead


class StreamYOLO(nn.Module):
"""
YOLOX model module. The module list is defined by create_yolov3_modules function.
The network returns loss values from three YOLO layers during training
and detection results during test.
"""

def __init__(self, backbone=None, head=None):
super().__init__()
if backbone is None:
backbone = DFPPAFPN()
if head is None:
head = TALHead(20)

self.backbone = backbone
self.head = head

def forward(self, x, targets=None, buffer=None, mode='off_pipe'):
# fpn output content features of [dark3, dark4, dark5]
assert mode in ['off_pipe', 'on_pipe']

if mode == 'off_pipe':
fpn_outs = self.backbone(x, buffer=buffer, mode='off_pipe')
if self.training:
pass
else:
outputs = self.head(fpn_outs, imgs=x)

return outputs
elif mode == 'on_pipe':
fpn_outs, buffer_ = self.backbone(x, buffer=buffer, mode='on_pipe')
outputs = self.head(fpn_outs)

return outputs, buffer_

+ 170
- 0
modelscope/models/cv/realtime_object_detection/yolox/models/tal_head.py View File

@@ -0,0 +1,170 @@
# The implementation is based on StreamYOLO, available at https://github.com/yancie-yjr/StreamYOLO
import torch
import torch.nn as nn
import torch.nn.functional as F

from .network_blocks import BaseConv, DWConv


class TALHead(nn.Module):

def __init__(
self,
num_classes,
width=1.0,
strides=[8, 16, 32],
in_channels=[256, 512, 1024],
act='silu',
depthwise=False,
gamma=1.5,
ignore_thr=0.2,
ignore_value=0.2,
):
"""
Args:
act (str): activation type of conv. Defalut value: "silu".
depthwise (bool): wheather apply depthwise conv in conv branch. Defalut value: False.
"""
super().__init__()

self.gamma = gamma
self.ignore_thr = ignore_thr
self.ignore_value = ignore_value

self.n_anchors = 1
self.num_classes = num_classes
self.decode_in_inference = True # for deploy, set to False

self.cls_convs = nn.ModuleList()
self.reg_convs = nn.ModuleList()
self.cls_preds = nn.ModuleList()
self.reg_preds = nn.ModuleList()
self.obj_preds = nn.ModuleList()
self.stems = nn.ModuleList()
Conv = DWConv if depthwise else BaseConv

for i in range(len(in_channels)):
self.stems.append(
BaseConv(
in_channels=int(in_channels[i] * width),
out_channels=int(256 * width),
ksize=1,
stride=1,
act=act,
))
self.cls_convs.append(
nn.Sequential(*[
Conv(
in_channels=int(256 * width),
out_channels=int(256 * width),
ksize=3,
stride=1,
act=act,
),
Conv(
in_channels=int(256 * width),
out_channels=int(256 * width),
ksize=3,
stride=1,
act=act,
),
]))
self.reg_convs.append(
nn.Sequential(*[
Conv(
in_channels=int(256 * width),
out_channels=int(256 * width),
ksize=3,
stride=1,
act=act,
),
Conv(
in_channels=int(256 * width),
out_channels=int(256 * width),
ksize=3,
stride=1,
act=act,
),
]))
self.cls_preds.append(
nn.Conv2d(
in_channels=int(256 * width),
out_channels=self.n_anchors * self.num_classes,
kernel_size=1,
stride=1,
padding=0,
))
self.reg_preds.append(
nn.Conv2d(
in_channels=int(256 * width),
out_channels=4,
kernel_size=1,
stride=1,
padding=0,
))
self.obj_preds.append(
nn.Conv2d(
in_channels=int(256 * width),
out_channels=self.n_anchors * 1,
kernel_size=1,
stride=1,
padding=0,
))

self.strides = strides
self.grids = [torch.zeros(1)] * len(in_channels)
self.expanded_strides = [None] * len(in_channels)

def forward(self, xin, labels=None, imgs=None):
outputs = []
for k, (cls_conv, reg_conv, stride_this_level, x) in enumerate(
zip(self.cls_convs, self.reg_convs, self.strides, xin)):
x = self.stems[k](x)
cls_x = x
reg_x = x

cls_feat = cls_conv(cls_x)
cls_output = self.cls_preds[k](cls_feat)

reg_feat = reg_conv(reg_x)
reg_output = self.reg_preds[k](reg_feat)
obj_output = self.obj_preds[k](reg_feat)

if self.training:
pass

else:
output = torch.cat(
[reg_output,
obj_output.sigmoid(),
cls_output.sigmoid()], 1)

outputs.append(output)

if self.training:
pass
else:
self.hw = [x.shape[-2:] for x in outputs]
outputs = torch.cat([x.flatten(start_dim=2) for x in outputs],
dim=2).permute(0, 2, 1)
if self.decode_in_inference:
return self.decode_outputs(outputs, dtype=xin[0].type())
else:
return outputs

def decode_outputs(self, outputs, dtype):
grids = []
strides = []
for (hsize, wsize), stride in zip(self.hw, self.strides):
yv, xv = torch.meshgrid([torch.arange(hsize), torch.arange(wsize)])
grid = torch.stack((xv, yv), 2).view(1, -1, 2)
grids.append(grid)
shape = grid.shape[:2]
strides.append(torch.full((*shape, 1), stride))

grids = torch.cat(grids, dim=1).type(dtype)
strides = torch.cat(strides, dim=1).type(dtype)

outputs[..., :2] = (outputs[..., :2] + grids) * strides
outputs[..., 2:4] = torch.exp(outputs[..., 2:4]) * strides
return outputs

+ 29
- 2
modelscope/outputs.py View File

@@ -165,6 +165,32 @@ TASK_OUTPUTS = {
Tasks.image_object_detection:
[OutputKeys.SCORES, OutputKeys.LABELS, OutputKeys.BOXES],

# video object detection result for single sample
# {

# "scores": [[0.8, 0.25, 0.05, 0.05], [0.9, 0.1, 0.05, 0.05]]
# "labels": [["person", "traffic light", "car", "bus"],
# ["person", "traffic light", "car", "bus"]]
# "boxes":
# [
# [
# [x1, y1, x2, y2],
# [x1, y1, x2, y2],
# [x1, y1, x2, y2],
# [x1, y1, x2, y2],
# ],
# [
# [x1, y1, x2, y2],
# [x1, y1, x2, y2],
# [x1, y1, x2, y2],
# [x1, y1, x2, y2],
# ]
# ],

# }
Tasks.video_object_detection:
[OutputKeys.SCORES, OutputKeys.LABELS, OutputKeys.BOXES],

# instance segmentation result for single sample
# {
# "scores": [0.9, 0.1, 0.05, 0.05],
@@ -676,8 +702,9 @@ TASK_OUTPUTS = {
# "text_embedding": np.array with shape [1, D],
# "similarity": float
# }
Tasks.multi_modal_similarity:
[OutputKeys.IMG_EMBEDDING, OutputKeys.TEXT_EMBEDDING, OutputKeys.SCORES],
Tasks.multi_modal_similarity: [
OutputKeys.IMG_EMBEDDING, OutputKeys.TEXT_EMBEDDING, OutputKeys.SCORES
],

# VQA result for a sample
# {"text": "this is a text answser. "}


+ 59
- 0
modelscope/pipelines/cv/realtime_video_object_detection_pipeline.py View File

@@ -0,0 +1,59 @@
# Copyright (c) Alibaba, Inc. and its affiliates.
import os.path as osp
from typing import Any, Dict, List, Union

import cv2
import json
import numpy as np
import torch
from PIL import Image
from torchvision import transforms

from modelscope.metainfo import Pipelines
from modelscope.models.cv.realtime_object_detection import \
RealtimeVideoDetector
from modelscope.outputs import OutputKeys
from modelscope.pipelines import pipeline
from modelscope.pipelines.base import Input, Model, Pipeline, Tensor
from modelscope.pipelines.builder import PIPELINES
from modelscope.preprocessors import load_image
from modelscope.utils.constant import ModelFile, Tasks
from modelscope.utils.logger import get_logger

logger = get_logger()


@PIPELINES.register_module(
Tasks.video_object_detection,
module_name=Pipelines.realtime_video_object_detection)
class RealtimeVideoObjectDetectionPipeline(Pipeline):

def __init__(self, model: str, **kwargs):
super().__init__(model=model, **kwargs)
self.model = RealtimeVideoDetector(model)

def preprocess(self, input: Input) -> Dict[Tensor, Union[str, np.ndarray]]:
return input

def forward(self, input: Input) -> Dict[Tensor, Dict[str, np.ndarray]]:
self.video_path = input
# Processing the whole video and return results for each frame
forward_output = self.model.inference_video(self.video_path)
return {'forward_output': forward_output}

def postprocess(self, input: Dict[Tensor, Dict[str, np.ndarray]],
**kwargs) -> str:
forward_output = input['forward_output']

scores, boxes, labels = [], [], []
for result in forward_output:
box, score, label = result
scores.append(score)
boxes.append(box)
labels.append(label)

return {
OutputKeys.BOXES: boxes,
OutputKeys.SCORES: scores,
OutputKeys.LABELS: labels,
}

+ 1
- 0
modelscope/utils/constant.py View File

@@ -38,6 +38,7 @@ class CVTasks(object):
image_classification_dailylife = 'image-classification-dailylife'

image_object_detection = 'image-object-detection'
video_object_detection = 'video-object-detection'

image_segmentation = 'image-segmentation'
semantic_segmentation = 'semantic-segmentation'


+ 60
- 0
modelscope/utils/cv/image_utils.py View File

@@ -231,6 +231,66 @@ def show_video_tracking_result(video_in_path, bboxes, video_save_path):
cap.release()


def show_video_object_detection_result(video_in_path, bboxes_list, labels_list,
video_save_path):

PALETTE = {
'person': [128, 0, 0],
'bicycle': [128, 128, 0],
'car': [64, 0, 0],
'motorcycle': [0, 128, 128],
'bus': [64, 128, 0],
'truck': [192, 128, 0],
'traffic light': [64, 0, 128],
'stop sign': [192, 0, 128],
}
from tqdm import tqdm
import math
cap = cv2.VideoCapture(video_in_path)
with tqdm(total=len(bboxes_list)) as pbar:
pbar.set_description(
'Writing results to video: {}'.format(video_save_path))
for i in range(len(bboxes_list)):
bboxes = bboxes_list[i].astype(int)
labels = labels_list[i]
success, frame = cap.read()
if success is False:
raise Exception(video_in_path,
' can not be correctly decoded by OpenCV.')
if i == 0:
size = (frame.shape[1], frame.shape[0])
fourcc = cv2.VideoWriter_fourcc('M', 'J', 'P', 'G')
video_writer = cv2.VideoWriter(video_save_path, fourcc,
cap.get(cv2.CAP_PROP_FPS), size,
True)

FONT_SCALE = 1e-3 # Adjust for larger font size in all images
THICKNESS_SCALE = 1e-3 # Adjust for larger thickness in all images
TEXT_Y_OFFSET_SCALE = 1e-2 # Adjust for larger Y-offset of text and bounding box
H, W, _ = frame.shape
zeros_mask = np.zeros((frame.shape)).astype(np.uint8)
for bbox, l in zip(bboxes, labels):
cv2.rectangle(frame, (bbox[0], bbox[1]), (bbox[2], bbox[3]),
PALETTE[l], 1)
cv2.putText(
frame,
l, (bbox[0], bbox[1] - int(TEXT_Y_OFFSET_SCALE * H)),
fontFace=cv2.FONT_HERSHEY_TRIPLEX,
fontScale=min(H, W) * FONT_SCALE,
thickness=math.ceil(min(H, W) * THICKNESS_SCALE),
color=PALETTE[l])
zeros_mask = cv2.rectangle(
zeros_mask, (bbox[0], bbox[1]), (bbox[2], bbox[3]),
color=PALETTE[l],
thickness=-1)

frame = cv2.addWeighted(frame, 1., zeros_mask, .65, 0)
video_writer.write(frame)
pbar.update(1)
video_writer.release
cap.release()


def panoptic_seg_masks_to_image(masks):
draw_img = np.zeros([masks[0].shape[0], masks[0].shape[1], 3])
from mmdet.core.visualization.palette import get_palette


+ 46
- 0
tests/pipelines/test_realtime_video_object_detection.py View File

@@ -0,0 +1,46 @@
# Copyright (c) Alibaba, Inc. and its affiliates.
import unittest

import cv2
import numpy as np

from modelscope.outputs import OutputKeys
from modelscope.pipelines import pipeline
from modelscope.pipelines.base import Pipeline
from modelscope.utils.constant import Tasks
from modelscope.utils.cv.image_utils import show_video_object_detection_result
from modelscope.utils.demo_utils import DemoCompatibilityCheck
from modelscope.utils.logger import get_logger
from modelscope.utils.test_utils import test_level

logger = get_logger()


class RealtimeVideoObjectDetectionTest(unittest.TestCase,
DemoCompatibilityCheck):

def setUp(self) -> None:
self.model_id = 'damo/cv_cspnet_video-object-detection_streamyolo'
self.test_video = 'data/test/videos/test_realtime_vod.mp4'

@unittest.skipUnless(test_level() >= 0, 'skip test in current test level')
def test_run_modelhub(self):
realtime_video_object_detection = pipeline(
Tasks.video_object_detection, model=self.model_id)
result = realtime_video_object_detection(self.test_video)
if result:
logger.info('Video output to test_vod_results.avi')
show_video_object_detection_result(self.test_video,
result[OutputKeys.BOXES],
result[OutputKeys.LABELS],
'test_vod_results.avi')
else:
raise ValueError('process error')

@unittest.skip('demo compatibility test is only enabled on a needed-basis')
def test_demo_compatibility(self):
self.compatibility_check()


if __name__ == '__main__':
unittest.main()

Loading…
Cancel
Save