|
| 1 | +# 串连检测和分类 |
| 2 | + |
| 3 | +本文将在 [tutorial01](01-single-classification-model.zh.md) 的基础上扩展计算图:先检测、再扣图分类。对外提供视频解析服务。完整的代码在 [simple_det_classify](../../flow-python/examples/simple_det_classify) 。 |
| 4 | + |
| 5 | +## 移除分类预处理 |
| 6 | + |
| 7 | +之前提到过:MegEngine 除了不需要转模型,还能消除预处理。我们修改 `dump.py` 把预处理从 SDK/业务代码提到模型内。这样的好处是:**划清工程和算法的边界**,预处理本来就应该由 scientist 维护,每次只需要 release mge 文件,减少交接内容 |
| 8 | + |
| 9 | +```bash |
| 10 | +$ cat ${MegFlow}/flow-python/examples/simple_det_classify/dump.py |
| 11 | +... |
| 12 | + data = mge.Tensor(np.ones(shape, dtype=np.uint8)) |
| 13 | + |
| 14 | + @jit.trace(capture_as_const=True) |
| 15 | + def pred_func(data): |
| 16 | + out = data.astype(np.float32) |
| 17 | + # resnet18 预处理 |
| 18 | + output_h, output_w = 224, 224 |
| 19 | + # resize |
| 20 | + M = mge.tensor(np.array([[1,0,0], [0,1,0], [0,0,1]], dtype=np.float32).reshape((1,3,3))) |
| 21 | + out = F.vision.warp_perspective(out, M, (output_h, output_w), format='NHWC') |
| 22 | + # mean |
| 23 | + _mean = mge.Tensor(np.array([103.530, 116.280, 123.675], dtype=np.float32)) |
| 24 | + out = F.sub(out, _mean) |
| 25 | + # div |
| 26 | + _div = mge.Tensor(np.array([57.375, 57.120, 58.395], dtype=np.float32)) |
| 27 | + out = F.div(out, _div) |
| 28 | + # dimshuffile |
| 29 | + out = F.transpose(out, (0,3,1,2)) |
| 30 | + |
| 31 | + outputs = model(out) |
| 32 | + return outputs |
| 33 | +... |
| 34 | +``` |
| 35 | +具体实现是在 trace inference 里增加预处理动作,fuse opr 优化加速的事情交给 MegEngine 即可。更多 cv 操作参照 [MegEngine API 文档](https://megengine.org.cn/doc/stable/zh/reference/api/megengine.functional.vision.warp_perspective.html?highlight=warp_perspective)。 |
| 36 | + |
| 37 | +因为推理输入变成了 BGR,所以 dump 模型的时候参数也应该跟着变 |
| 38 | +```bash |
| 39 | +$ python3 dump.py -a resnet18 -s 1 224 224 3 |
| 40 | +``` |
| 41 | + |
| 42 | +## 准备检测模型 |
| 43 | +这里直接用现成的 YOLOX mge 模型。复用 [cat_finder 的检测](../../flow-python/examples/cat_finder/det.py) 或者从 [YOLOX 官网](https://github.com/Megvii-BaseDetection/YOLOX/tree/main/demo/MegEngine/python) 下载最新版。 |
| 44 | + |
| 45 | +## 配置计算图 |
| 46 | +`flow-python/examples` 增加 `simple_det_classify/video_cpu.toml` |
| 47 | + |
| 48 | +```bash |
| 49 | +$ cat flow-python/examples/simple_det_classify/video_cpu.toml |
| 50 | + |
| 51 | +main = "tutorial_02" |
| 52 | + |
| 53 | +# 重资源结点要先声明 |
| 54 | +[[nodes]] |
| 55 | +name = "det" |
| 56 | +ty = "Detect" |
| 57 | +model = "yolox-s" |
| 58 | +conf = 0.25 |
| 59 | +nms = 0.45 |
| 60 | +tsize = 640 |
| 61 | +path = "models/simple_det_classify_models/yolox_s.mge" |
| 62 | +interval = 5 |
| 63 | +visualize = 1 |
| 64 | +device = "cpu" |
| 65 | +device_id = 0 |
| 66 | + |
| 67 | +[[nodes]] |
| 68 | +name = "classify" |
| 69 | +ty = "Classify" |
| 70 | +path = "models/simple_det_classify_models/resnet18_preproc_inside.mge" |
| 71 | +device = "cpu" |
| 72 | +device_id = 0 |
| 73 | + |
| 74 | +[[graphs]] |
| 75 | +name = "subgraph" |
| 76 | +inputs = [{ name = "inp", cap = 16, ports = ["det:inp"] }] |
| 77 | +outputs = [{ name = "out", cap = 16, ports = ["classify:out"] }] |
| 78 | +# 描述连接关系 |
| 79 | +connections = [ |
| 80 | + { cap = 16, ports = ["det:out", "classify:inp"] }, |
| 81 | +] |
| 82 | + |
| 83 | +... |
| 84 | +# ty 改成 VdieoServer |
| 85 | + [[graphs.nodes]] |
| 86 | + name = "source" |
| 87 | + ty = "VideoServer" |
| 88 | + port = 8085 |
| 89 | + |
| 90 | +... |
| 91 | +``` |
| 92 | +想对上一期的配置,需要关注 3 点: |
| 93 | +* 视频流中的重资源结点,需要声明在 `[[graphs]]` 之外,因为多路视频需要复用这个结点。如果每一路都要启一个 det 结点,资源会爆掉 |
| 94 | +* `connections` 不再是空白,因为两个结点要描述连接关系 |
| 95 | +* Server 类型改成 `VideoServer`,告诉 UI 是要处理视频的 |
| 96 | +
|
| 97 | +## 实现细节 |
| 98 | +* 可以看到此时 [resnet18 的 lite.py](../../flow-python/examples/simple_det_classify/lite.py) 已经删除了 preprocess 函数 |
| 99 | +* det.py 可以直接用 `cat_finder` 的 |
| 100 | +
|
| 101 | +## 运行测试 |
| 102 | +
|
| 103 | +运行服务 |
| 104 | +```bash |
| 105 | +$ cd flow-python/examples |
| 106 | +$ run_with_plugins -c simple_det_classify/video_cpu.toml -p simple_det_classify |
| 107 | +``` |
| 108 | +
|
| 109 | +### WebUI 方式 |
| 110 | +浏览器打开 8085 端口服务(例如 http://127.0.0.1:8085/docs ) |
| 111 | +
|
| 112 | +* 参照 [如何生成 rtsp](../how-to-build-and-run/generate-rtsp.zh.md),提供一个 rtsp 流地址 |
| 113 | +* 或者干脆给 .mp4 文件的绝对路径(文件和 8085 服务在同一台机器上) |
| 114 | +
|
| 115 | +### 命令行方式 |
| 116 | +```bash |
| 117 | +$ curl -X POST 'http://127.0.0.1:8085/start/rtsp%3A%2F%2F127.0.0.1%3A8554%2Ftest1.ts' # start rtsp://127.0.0.1:8554/test1.ts |
| 118 | +start stream whose id is 2% |
| 119 | +$ curl 'http://127.0.0.1:8085/list' # list all stream |
| 120 | +[{"id":1,"url":"rtsp://10.122.101.175:8554/test1.ts"},{"id":0,"url":"rtsp://10.122.101.175:8554/test1.ts"}]% |
| 121 | +``` |
| 122 | +路径中的 `%2F`、`%3A` 是 [URL](https://www.ietf.org/rfc/rfc1738.txt) 的转义字符 |
| 123 | +
|
| 124 | +### Python Client |
| 125 | +
|
| 126 | +```Python |
| 127 | +$ cat ${MegFlow_DIR}/flow-python/examples/simple_det_classify/client.py |
| 128 | + |
| 129 | +import requests |
| 130 | +import urllib |
| 131 | + |
| 132 | + |
| 133 | +def test(): |
| 134 | + ip = 'localhost' |
| 135 | + port = '8085' |
| 136 | + video_path = 'rtsp://127.0.0.1:8554/vehicle.ts' |
| 137 | + video_path = urllib.parse.quote(video_path, safe='') |
| 138 | + url = 'http://{}:{}/start/{}'.format(ip, port, video_path) |
| 139 | + |
| 140 | + res = requests.post(url) |
| 141 | + ret = res.content |
| 142 | + print(ret) |
| 143 | + |
| 144 | + |
| 145 | +if __name__ == "__main__": |
| 146 | + test() |
| 147 | +``` |
0 commit comments