Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
PaddlePaddle
PaddleDetection
提交
04d837cf
P
PaddleDetection
项目概览
PaddlePaddle
/
PaddleDetection
大约 2 年 前同步成功
通知
708
Star
11112
Fork
2696
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
184
列表
看板
标记
里程碑
合并请求
40
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
P
PaddleDetection
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
184
Issue
184
列表
看板
标记
里程碑
合并请求
40
合并请求
40
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
未验证
提交
04d837cf
编写于
10月 18, 2021
作者:
G
Guanghua Yu
提交者:
GitHub
10月 18, 2021
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
update picodet Lite demo (#4282)
* update picodet lite demo
上级
6486b773
变更
12
隐藏空白更改
内联
并排
Showing
12 changed file
with
381 addition
and
59 deletion
+381
-59
deploy/lite/README.md
deploy/lite/README.md
+28
-17
deploy/lite/det_runtime_config.json
deploy/lite/det_runtime_config.json
+10
-0
deploy/lite/include/config_parser.h
deploy/lite/include/config_parser.h
+14
-0
deploy/lite/include/object_detector.h
deploy/lite/include/object_detector.h
+6
-12
deploy/lite/include/picodet_postprocess.h
deploy/lite/include/picodet_postprocess.h
+38
-0
deploy/lite/include/utils.h
deploy/lite/include/utils.h
+39
-0
deploy/lite/keypoint_runtime_config.json
deploy/lite/keypoint_runtime_config.json
+0
-3
deploy/lite/src/keypoint_postprocess.cc
deploy/lite/src/keypoint_postprocess.cc
+1
-1
deploy/lite/src/main.cc
deploy/lite/src/main.cc
+15
-10
deploy/lite/src/object_detector.cc
deploy/lite/src/object_detector.cc
+54
-16
deploy/lite/src/picodet_postprocess.cc
deploy/lite/src/picodet_postprocess.cc
+127
-0
deploy/lite/src/utils.cc
deploy/lite/src/utils.cc
+49
-0
未找到文件。
deploy/lite/README.md
浏览文件 @
04d837cf
...
@@ -24,7 +24,7 @@ Paddle Lite是飞桨轻量化推理引擎,为手机、IOT端提供高效推理
...
@@ -24,7 +24,7 @@ Paddle Lite是飞桨轻量化推理引擎,为手机、IOT端提供高效推理
1.
[
**建议**
]直接下载,预测库下载链接如下:
1.
[
**建议**
]直接下载,预测库下载链接如下:
|平台|预测库下载链接|
|平台|预测库下载链接|
|-|-|
|-|-|
|Android|
[
arm7
](
https://github.com/PaddlePaddle/Paddle-Lite/releases/download/v2.
8/inference_lite_lib.android.armv7.gcc.c++_static.with_extra.with_cv.tar.gz
)
/
[
arm8
](
https://github.com/PaddlePaddle/Paddle-Lite/releases/download/v2.8/inference_lite_lib.android.armv8.gcc
.c++_static.with_extra.with_cv.tar.gz
)
|
|Android|
[
arm7
](
https://github.com/PaddlePaddle/Paddle-Lite/releases/download/v2.
9.1/inference_lite_lib.android.armv7.clang.c++_static.with_extra.with_cv.tar.gz
)
/
[
arm8
](
https://github.com/PaddlePaddle/Paddle-Lite/releases/download/v2.9.1/inference_lite_lib.android.armv8.clang
.c++_static.with_extra.with_cv.tar.gz
)
|
**注意**
:1. 如果是从 Paddle-Lite
[
官方文档
](
https://paddle-lite.readthedocs.io/zh/latest/quick_start/release_lib.html#android-toolchain-gcc
)
下载的预测库,注意选择
`with_extra=ON,with_cv=ON`
的下载链接。2. 目前只提供Android端demo,IOS端demo可以参考
[
Paddle-Lite IOS demo
](
https://github.com/PaddlePaddle/Paddle-Lite-Demo/tree/master/PaddleLite-ios-demo
)
**注意**
:1. 如果是从 Paddle-Lite
[
官方文档
](
https://paddle-lite.readthedocs.io/zh/latest/quick_start/release_lib.html#android-toolchain-gcc
)
下载的预测库,注意选择
`with_extra=ON,with_cv=ON`
的下载链接。2. 目前只提供Android端demo,IOS端demo可以参考
[
Paddle-Lite IOS demo
](
https://github.com/PaddlePaddle/Paddle-Lite-Demo/tree/master/PaddleLite-ios-demo
)
...
@@ -40,7 +40,7 @@ git checkout develop
...
@@ -40,7 +40,7 @@ git checkout develop
**注意**
:编译Paddle-Lite获得预测库时,需要打开
`--with_cv=ON --with_extra=ON`
两个选项,
`--arch`
表示
`arm`
版本,这里指定为armv8,更多编译命令介绍请参考
[
链接
](
https://paddle-lite.readthedocs.io/zh/latest/source_compile/compile_andriod.html#id2
)
。
**注意**
:编译Paddle-Lite获得预测库时,需要打开
`--with_cv=ON --with_extra=ON`
两个选项,
`--arch`
表示
`arm`
版本,这里指定为armv8,更多编译命令介绍请参考
[
链接
](
https://paddle-lite.readthedocs.io/zh/latest/source_compile/compile_andriod.html#id2
)
。
直接下载预测库并解压后,可以得到
`inference_lite_lib.android.armv8.
gcc
.c++_static.with_extra.with_cv/`
文件夹,通过编译Paddle-Lite得到的预测库位于
`Paddle-Lite/build.lite.android.armv8.gcc/inference_lite_lib.android.armv8/`
文件夹下。
直接下载预测库并解压后,可以得到
`inference_lite_lib.android.armv8.
clang
.c++_static.with_extra.with_cv/`
文件夹,通过编译Paddle-Lite得到的预测库位于
`Paddle-Lite/build.lite.android.armv8.gcc/inference_lite_lib.android.armv8/`
文件夹下。
预测库的文件目录如下:
预测库的文件目录如下:
```
```
...
@@ -120,7 +120,7 @@ Paddle-Lite 提供了多种策略来自动优化原始的模型,其中包括
...
@@ -120,7 +120,7 @@ Paddle-Lite 提供了多种策略来自动优化原始的模型,其中包括
#### 2.1.3 转换示例
#### 2.1.3 转换示例
下面以PaddleDetection中的
`
PP-YOLO-tiny
`
模型为例,介绍使用
`paddle_lite_opt`
完成预训练模型到inference模型,再到Paddle-Lite优化模型的转换。
下面以PaddleDetection中的
`
ppyolo
`
模型为例,介绍使用
`paddle_lite_opt`
完成预训练模型到inference模型,再到Paddle-Lite优化模型的转换。
```
shell
```
shell
# 进入PaddleDetection根目录
# 进入PaddleDetection根目录
...
@@ -130,7 +130,7 @@ cd PaddleDetection_root_path
...
@@ -130,7 +130,7 @@ cd PaddleDetection_root_path
python tools/export_model.py
-c
configs/ppyolo/ppyolo_tiny_650e_coco.yml
-o
weights
=
https://paddledet.bj.bcebos.com/models/ppyolo_tiny_650e_coco.pdparams
python tools/export_model.py
-c
configs/ppyolo/ppyolo_tiny_650e_coco.yml
-o
weights
=
https://paddledet.bj.bcebos.com/models/ppyolo_tiny_650e_coco.pdparams
# 将inference模型转化为Paddle-Lite优化模型
# 将inference模型转化为Paddle-Lite优化模型
paddle_lite_opt
--valid_targets
=
arm
--
optimize_out_type
=
naive_buffe
--
model_file
=
output_inference/ppyolo_tiny_650e_coco/model.pdmodel
--param_file
=
output_inference/ppyolo_tiny_650e_coco/model.pdiparams
--optimize_out
=
output_inference/ppyolo_tiny_650e_coco/model
paddle_lite_opt
--valid_targets
=
arm
--model_file
=
output_inference/ppyolo_tiny_650e_coco/model.pdmodel
--param_file
=
output_inference/ppyolo_tiny_650e_coco/model.pdiparams
--optimize_out
=
output_inference/ppyolo_tiny_650e_coco/model
# 将inference模型配置转化为json格式
# 将inference模型配置转化为json格式
python deploy/lite/convert_yml_to_json.py output_inference/ppyolo_tiny_650e_coco/infer_cfg.yml
python deploy/lite/convert_yml_to_json.py output_inference/ppyolo_tiny_650e_coco/infer_cfg.yml
...
@@ -180,7 +180,7 @@ cd deploy/lite/
...
@@ -180,7 +180,7 @@ cd deploy/lite/
inference_lite_path
=
/
{
lite prediction library path
}
/inference_lite_lib.android.armv8.gcc.c++_static.with_extra.with_cv/
inference_lite_path
=
/
{
lite prediction library path
}
/inference_lite_lib.android.armv8.gcc.c++_static.with_extra.with_cv/
mkdir
$inference_lite_path
/demo/cxx/lite
mkdir
$inference_lite_path
/demo/cxx/lite
cp
-r
Makefile src/ include/ runtime_config.json
$inference_lite_path
/demo/cxx/lite
cp
-r
Makefile src/ include/
*
runtime_config.json
$inference_lite_path
/demo/cxx/lite
cd
$inference_lite_path
/demo/cxx/lite
cd
$inference_lite_path
/demo/cxx/lite
...
@@ -194,7 +194,7 @@ make ARM_ABI = arm8
...
@@ -194,7 +194,7 @@ make ARM_ABI = arm8
```
shell
```
shell
mdkir deploy
mdkir deploy
cp
main runtime_config.json deploy/
cp
main
*
runtime_config.json deploy/
cd
deploy
cd
deploy
mkdir
model_det
mkdir
model_det
mkdir
model_keypoint
mkdir
model_keypoint
...
@@ -219,31 +219,42 @@ cp ../../../cxx/lib/libpaddle_light_api_shared.so ./
...
@@ -219,31 +219,42 @@ cp ../../../cxx/lib/libpaddle_light_api_shared.so ./
```
```
deploy/
deploy/
|-- model_det/
|-- model_det/
| |--mdoel.nb 优化后的检测模型文件
| |--mdoel.nb
优化后的检测模型文件
| |--infer_cfg.json 检测器模型配置文件
| |--infer_cfg.json
检测器模型配置文件
|-- model_keypoint/
|-- model_keypoint/
| |--mdoel.nb 优化后的关键点模型文件
| |--mdoel.nb 优化后的关键点模型文件
| |--infer_cfg.json 关键点模型配置文件
| |--infer_cfg.json 关键点模型配置文件
|-- main 生成的移动端执行文件
|-- main 生成的移动端执行文件
|-- runtime_config.json 移动端执行时参数配置文件
|-- det_runtime_config.json 目标检测执行时参数配置文件
|-- libpaddle_light_api_shared.so Paddle-Lite库文件
|-- keypoint_runtime_config.json 关键点检测执行时参数配置文件
|-- libpaddle_light_api_shared.so Paddle-Lite库文件
```
```
**注意:**
**注意:**
*
`
runtime_config.json`
包含了检测器的超参数,请按需进行修改(注意配置中路径及文件需存在)
:
*
`
det_runtime_config.json`
包含了目标检测的超参数,请按需进行修改
:
```
shell
```
shell
{
{
"model_dir_det"
:
"./model_det/"
,
#检测器模型路径
"model_dir_det"
:
"./model_det/"
,
#检测器模型路径
"batch_size_det"
: 1,
#检测预测时batchsize
"batch_size_det"
: 1,
#检测预测时batchsize
"threshold_det"
: 0.5,
#检测器输出阈值
"threshold_det"
: 0.5,
#检测器输出阈值
"image_file"
:
"demo.jpg"
,
#测试图片
"image_dir"
:
""
,
#测试图片文件夹
"run_benchmark"
:
false
,
#性能测试开关
"cpu_threads"
: 4
#线程数
}
```
*
`keypoint_runtime_config.json`
包含了关键点检测的超参数,请按需进行修改:
```
shell
{
"model_dir_keypoint"
:
"./model_keypoint/"
,
#关键点模型路径(不使用需为空字符)
"model_dir_keypoint"
:
"./model_keypoint/"
,
#关键点模型路径(不使用需为空字符)
"batch_size_keypoint"
: 8,
#关键点预测时batchsize
"batch_size_keypoint"
: 8,
#关键点预测时batchsize
"threshold_keypoint"
: 0.5,
#关键点输出阈值
"threshold_keypoint"
: 0.5,
#关键点输出阈值
"image_file"
:
"demo.jpg"
,
#测试图片
"image_file"
:
"demo.jpg"
,
#测试图片
"image_dir"
:
""
,
#测试图片文件夹
"image_dir"
:
""
,
#测试图片文件夹
"run_benchmark"
:
false
,
#性能测试开关
"run_benchmark"
:
false
,
#性能测试开关
"cpu_threads"
:
1
#线程数
"cpu_threads"
:
4
#线程数
}
}
```
```
...
@@ -259,8 +270,8 @@ export LD_LIBRARY_PATH=/data/local/tmp/deploy:$LD_LIBRARY_PATH
...
@@ -259,8 +270,8 @@ export LD_LIBRARY_PATH=/data/local/tmp/deploy:$LD_LIBRARY_PATH
# 修改权限为可执行
# 修改权限为可执行
chmod
777 main
chmod
777 main
# 执行程序
#
以检测为例,
执行程序
./main
./main
det_runtime_config.json
```
```
如果对代码做了修改,则需要重新编译并push到手机上。
如果对代码做了修改,则需要重新编译并push到手机上。
...
...
deploy/lite/det_runtime_config.json
0 → 100644
浏览文件 @
04d837cf
{
"model_dir_det"
:
"./model_det/"
,
"batch_size_det"
:
1
,
"threshold_det"
:
0.5
,
"image_file"
:
"./demo.jpg"
,
"image_dir"
:
""
,
"run_benchmark"
:
false
,
"cpu_threads"
:
4
}
\ No newline at end of file
deploy/lite/include/config_parser.h
浏览文件 @
04d837cf
...
@@ -78,12 +78,26 @@ class ConfigPaser {
...
@@ -78,12 +78,26 @@ class ConfigPaser {
return
false
;
return
false
;
}
}
// Get NMS for postprocess
if
(
config
.
isMember
(
"NMS"
))
{
nms_info_
=
config
[
"NMS"
];
}
// Get fpn_stride in PicoDet
if
(
config
.
isMember
(
"fpn_stride"
))
{
fpn_stride_
.
clear
();
for
(
auto
item
:
config
[
"fpn_stride"
])
{
fpn_stride_
.
emplace_back
(
item
.
as
<
int
>
());
}
}
return
true
;
return
true
;
}
}
float
draw_threshold_
;
float
draw_threshold_
;
std
::
string
arch_
;
std
::
string
arch_
;
Json
::
Value
preprocess_info_
;
Json
::
Value
preprocess_info_
;
Json
::
Value
nms_info_
;
std
::
vector
<
std
::
string
>
label_list_
;
std
::
vector
<
std
::
string
>
label_list_
;
std
::
vector
<
int
>
fpn_stride_
;
};
};
}
// namespace PaddleDetection
}
// namespace PaddleDetection
deploy/lite/include/object_detector.h
浏览文件 @
04d837cf
...
@@ -28,26 +28,19 @@
...
@@ -28,26 +28,19 @@
#include "include/config_parser.h"
#include "include/config_parser.h"
#include "include/preprocess_op.h"
#include "include/preprocess_op.h"
#include "include/utils.h"
#include "include/picodet_postprocess.h"
using
namespace
paddle
::
lite_api
;
// NOLINT
using
namespace
paddle
::
lite_api
;
// NOLINT
namespace
PaddleDetection
{
namespace
PaddleDetection
{
// Object Detection Result
struct
ObjectResult
{
// Rectangle coordinates of detected object: left, right, top, down
std
::
vector
<
int
>
rect
;
// Class id of detected object
int
class_id
;
// Confidence of detected object
float
confidence
;
};
// Generate visualization colormap for each class
// Generate visualization colormap for each class
std
::
vector
<
int
>
GenerateColorMap
(
int
num_class
);
std
::
vector
<
int
>
GenerateColorMap
(
int
num_class
);
// Visualiztion Detection Result
// Visualiztion Detection Result
cv
::
Mat
VisualizeResult
(
const
cv
::
Mat
&
img
,
cv
::
Mat
VisualizeResult
(
const
cv
::
Mat
&
img
,
const
std
::
vector
<
ObjectResult
>&
results
,
const
std
::
vector
<
PaddleDetection
::
ObjectResult
>&
results
,
const
std
::
vector
<
std
::
string
>&
lables
,
const
std
::
vector
<
std
::
string
>&
lables
,
const
std
::
vector
<
int
>&
colormap
,
const
std
::
vector
<
int
>&
colormap
,
const
bool
is_rbox
);
const
bool
is_rbox
);
...
@@ -74,7 +67,7 @@ class ObjectDetector {
...
@@ -74,7 +67,7 @@ class ObjectDetector {
const
double
threshold
=
0.5
,
const
double
threshold
=
0.5
,
const
int
warmup
=
0
,
const
int
warmup
=
0
,
const
int
repeats
=
1
,
const
int
repeats
=
1
,
std
::
vector
<
ObjectResult
>*
result
=
nullptr
,
std
::
vector
<
PaddleDetection
::
ObjectResult
>*
result
=
nullptr
,
std
::
vector
<
int
>*
bbox_num
=
nullptr
,
std
::
vector
<
int
>*
bbox_num
=
nullptr
,
std
::
vector
<
double
>*
times
=
nullptr
);
std
::
vector
<
double
>*
times
=
nullptr
);
...
@@ -88,7 +81,7 @@ class ObjectDetector {
...
@@ -88,7 +81,7 @@ class ObjectDetector {
void
Preprocess
(
const
cv
::
Mat
&
image_mat
);
void
Preprocess
(
const
cv
::
Mat
&
image_mat
);
// Postprocess result
// Postprocess result
void
Postprocess
(
const
std
::
vector
<
cv
::
Mat
>
mats
,
void
Postprocess
(
const
std
::
vector
<
cv
::
Mat
>
mats
,
std
::
vector
<
ObjectResult
>*
result
,
std
::
vector
<
PaddleDetection
::
ObjectResult
>*
result
,
std
::
vector
<
int
>
bbox_num
,
std
::
vector
<
int
>
bbox_num
,
bool
is_rbox
);
bool
is_rbox
);
...
@@ -99,6 +92,7 @@ class ObjectDetector {
...
@@ -99,6 +92,7 @@ class ObjectDetector {
std
::
vector
<
int
>
out_bbox_num_data_
;
std
::
vector
<
int
>
out_bbox_num_data_
;
float
threshold_
;
float
threshold_
;
ConfigPaser
config_
;
ConfigPaser
config_
;
};
};
}
// namespace PaddleDetection
}
// namespace PaddleDetection
deploy/lite/include/picodet_postprocess.h
0 → 100644
浏览文件 @
04d837cf
// Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <string>
#include <vector>
#include <memory>
#include <utility>
#include <ctime>
#include <numeric>
#include "include/utils.h"
namespace
PaddleDetection
{
void
PicoDetPostProcess
(
std
::
vector
<
PaddleDetection
::
ObjectResult
>*
results
,
std
::
vector
<
const
float
*>
outs
,
std
::
vector
<
int
>
fpn_stride
,
std
::
vector
<
float
>
im_shape
,
std
::
vector
<
float
>
scale_factor
,
float
score_threshold
=
0.3
,
float
nms_threshold
=
0.5
,
int
num_class
=
80
,
int
reg_max
=
7
);
}
// namespace PaddleDetection
\ No newline at end of file
deploy/lite/include/utils.h
0 → 100644
浏览文件 @
04d837cf
// Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <string>
#include <vector>
#include <memory>
#include <utility>
#include <ctime>
#include <numeric>
#include <algorithm>
namespace
PaddleDetection
{
// Object Detection Result
struct
ObjectResult
{
// Rectangle coordinates of detected object: left, right, top, down
std
::
vector
<
int
>
rect
;
// Class id of detected object
int
class_id
;
// Confidence of detected object
float
confidence
;
};
void
nms
(
std
::
vector
<
ObjectResult
>
&
input_boxes
,
float
nms_threshold
);
}
// namespace PaddleDetection
\ No newline at end of file
deploy/lite/runtime_config.json
→
deploy/lite/
keypoint_
runtime_config.json
浏览文件 @
04d837cf
{
{
"model_dir_det"
:
"./model_det/"
,
"batch_size_det"
:
1
,
"threshold_det"
:
0.5
,
"model_dir_keypoint"
:
"./model_keypoint/"
,
"model_dir_keypoint"
:
"./model_keypoint/"
,
"batch_size_keypoint"
:
8
,
"batch_size_keypoint"
:
8
,
"threshold_keypoint"
:
0.5
,
"threshold_keypoint"
:
0.5
,
...
...
deploy/lite/src/keypoint_postprocess.cc
浏览文件 @
04d837cf
...
@@ -52,7 +52,7 @@ void get_affine_transform(std::vector<float>& center,
...
@@ -52,7 +52,7 @@ void get_affine_transform(std::vector<float>& center,
float
dst_h
=
static_cast
<
float
>
(
output_size
[
1
]);
float
dst_h
=
static_cast
<
float
>
(
output_size
[
1
]);
float
rot_rad
=
rot
*
PI
/
HALF_CIRCLE_DEGREE
;
float
rot_rad
=
rot
*
PI
/
HALF_CIRCLE_DEGREE
;
std
::
vector
<
float
>
src_dir
=
get_dir
(
-
0.5
*
src_w
,
0
,
rot_rad
);
std
::
vector
<
float
>
src_dir
=
get_dir
(
-
0.5
*
src_w
,
0
,
rot_rad
);
std
::
vector
<
float
>
dst_dir
{
-
0.5
*
dst_w
,
0.0
};
std
::
vector
<
float
>
dst_dir
{
static_cast
<
float
>
(
-
0.5
)
*
dst_w
,
0.0
};
cv
::
Point2f
srcPoint2f
[
3
],
dstPoint2f
[
3
];
cv
::
Point2f
srcPoint2f
[
3
],
dstPoint2f
[
3
];
srcPoint2f
[
0
]
=
cv
::
Point2f
(
center
[
0
],
center
[
1
]);
srcPoint2f
[
0
]
=
cv
::
Point2f
(
center
[
0
],
center
[
1
]);
srcPoint2f
[
1
]
=
cv
::
Point2f
(
center
[
0
]
+
src_dir
[
0
],
center
[
1
]
+
src_dir
[
1
]);
srcPoint2f
[
1
]
=
cv
::
Point2f
(
center
[
0
]
+
src_dir
[
0
],
center
[
1
]
+
src_dir
[
1
]);
...
...
deploy/lite/src/main.cc
浏览文件 @
04d837cf
...
@@ -152,9 +152,10 @@ void PredictImage(const std::vector<std::string> all_img_paths,
...
@@ -152,9 +152,10 @@ void PredictImage(const std::vector<std::string> all_img_paths,
bool
is_rbox
=
false
;
bool
is_rbox
=
false
;
if
(
run_benchmark
)
{
if
(
run_benchmark
)
{
det
->
Predict
(
det
->
Predict
(
batch_imgs
,
threshold_det
,
10
,
1
0
,
&
result
,
&
bbox_num
,
&
det_times
);
batch_imgs
,
threshold_det
,
50
,
5
0
,
&
result
,
&
bbox_num
,
&
det_times
);
}
else
{
}
else
{
det
->
Predict
(
batch_imgs
,
0.5
,
0
,
1
,
&
result
,
&
bbox_num
,
&
det_times
);
det
->
Predict
(
batch_imgs
,
threshold_det
,
0
,
1
,
&
result
,
&
bbox_num
,
&
det_times
);
}
}
// get labels and colormap
// get labels and colormap
...
@@ -272,7 +273,7 @@ void PredictImage(const std::vector<std::string> all_img_paths,
...
@@ -272,7 +273,7 @@ void PredictImage(const std::vector<std::string> all_img_paths,
cv
::
Mat
vis_img
=
PaddleDetection
::
VisualizeResult
(
cv
::
Mat
vis_img
=
PaddleDetection
::
VisualizeResult
(
im
,
im_result
,
labels
,
colormap
,
is_rbox
);
im
,
im_result
,
labels
,
colormap
,
is_rbox
);
std
::
string
det_savepath
=
std
::
string
det_savepath
=
output_path
+
output_path
+
"result_"
+
image_file_path
.
substr
(
image_file_path
.
find_last_of
(
'/'
)
+
1
);
image_file_path
.
substr
(
image_file_path
.
find_last_of
(
'/'
)
+
1
);
cv
::
imwrite
(
det_savepath
,
vis_img
,
compression_params
);
cv
::
imwrite
(
det_savepath
,
vis_img
,
compression_params
);
printf
(
"Visualized output saved as %s
\n
"
,
det_savepath
.
c_str
());
printf
(
"Visualized output saved as %s
\n
"
,
det_savepath
.
c_str
());
...
@@ -284,7 +285,9 @@ void PredictImage(const std::vector<std::string> all_img_paths,
...
@@ -284,7 +285,9 @@ void PredictImage(const std::vector<std::string> all_img_paths,
det_t
[
2
]
+=
det_times
[
2
];
det_t
[
2
]
+=
det_times
[
2
];
}
}
PrintBenchmarkLog
(
det_t
,
all_img_paths
.
size
());
PrintBenchmarkLog
(
det_t
,
all_img_paths
.
size
());
PrintBenchmarkLog
(
keypoint_t
,
kpts_imgs
);
if
(
keypoint
)
{
PrintBenchmarkLog
(
keypoint_t
,
kpts_imgs
);
}
PrintTotalIimeLog
((
det_t
[
0
]
+
det_t
[
1
]
+
det_t
[
2
])
/
all_img_paths
.
size
(),
PrintTotalIimeLog
((
det_t
[
0
]
+
det_t
[
1
]
+
det_t
[
2
])
/
all_img_paths
.
size
(),
(
keypoint_t
[
0
]
+
keypoint_t
[
1
]
+
keypoint_t
[
2
])
/
kpts_imgs
,
(
keypoint_t
[
0
]
+
keypoint_t
[
1
]
+
keypoint_t
[
2
])
/
kpts_imgs
,
midtimecost
/
all_img_paths
.
size
());
midtimecost
/
all_img_paths
.
size
());
...
@@ -293,13 +296,15 @@ void PredictImage(const std::vector<std::string> all_img_paths,
...
@@ -293,13 +296,15 @@ void PredictImage(const std::vector<std::string> all_img_paths,
int
main
(
int
argc
,
char
**
argv
)
{
int
main
(
int
argc
,
char
**
argv
)
{
std
::
cout
<<
"Usage: "
<<
argv
[
0
]
std
::
cout
<<
"Usage: "
<<
argv
[
0
]
<<
" [config_path](option) [image_dir](option)
\n
"
;
<<
" [config_path](option) [image_dir](option)
\n
"
;
std
::
string
config_path
=
"runtime_config.json"
;
if
(
argc
<
2
)
{
std
::
cout
<<
"Usage: ./main det_runtime_config.json"
<<
std
::
endl
;
return
-
1
;
}
std
::
string
config_path
=
argv
[
1
];
std
::
string
img_path
=
""
;
std
::
string
img_path
=
""
;
if
(
argc
>=
2
)
{
config_path
=
argv
[
1
];
if
(
argc
>=
3
)
{
if
(
argc
>=
3
)
{
img_path
=
argv
[
2
];
img_path
=
argv
[
2
];
}
}
}
// Parsing command-line
// Parsing command-line
PaddleDetection
::
load_jsonf
(
config_path
,
RT_Config
);
PaddleDetection
::
load_jsonf
(
config_path
,
RT_Config
);
...
...
deploy/lite/src/object_detector.cc
浏览文件 @
04d837cf
...
@@ -31,7 +31,7 @@ void ObjectDetector::LoadModel(std::string model_file, int num_theads) {
...
@@ -31,7 +31,7 @@ void ObjectDetector::LoadModel(std::string model_file, int num_theads) {
// Visualiztion MaskDetector results
// Visualiztion MaskDetector results
cv
::
Mat
VisualizeResult
(
const
cv
::
Mat
&
img
,
cv
::
Mat
VisualizeResult
(
const
cv
::
Mat
&
img
,
const
std
::
vector
<
ObjectResult
>&
results
,
const
std
::
vector
<
PaddleDetection
::
ObjectResult
>&
results
,
const
std
::
vector
<
std
::
string
>&
lables
,
const
std
::
vector
<
std
::
string
>&
lables
,
const
std
::
vector
<
int
>&
colormap
,
const
std
::
vector
<
int
>&
colormap
,
const
bool
is_rbox
=
false
)
{
const
bool
is_rbox
=
false
)
{
...
@@ -100,7 +100,7 @@ void ObjectDetector::Preprocess(const cv::Mat& ori_im) {
...
@@ -100,7 +100,7 @@ void ObjectDetector::Preprocess(const cv::Mat& ori_im) {
}
}
void
ObjectDetector
::
Postprocess
(
const
std
::
vector
<
cv
::
Mat
>
mats
,
void
ObjectDetector
::
Postprocess
(
const
std
::
vector
<
cv
::
Mat
>
mats
,
std
::
vector
<
ObjectResult
>*
result
,
std
::
vector
<
PaddleDetection
::
ObjectResult
>*
result
,
std
::
vector
<
int
>
bbox_num
,
std
::
vector
<
int
>
bbox_num
,
bool
is_rbox
=
false
)
{
bool
is_rbox
=
false
)
{
result
->
clear
();
result
->
clear
();
...
@@ -128,7 +128,7 @@ void ObjectDetector::Postprocess(const std::vector<cv::Mat> mats,
...
@@ -128,7 +128,7 @@ void ObjectDetector::Postprocess(const std::vector<cv::Mat> mats,
int
x4
=
(
output_data_
[
8
+
j
*
10
]
*
rw
);
int
x4
=
(
output_data_
[
8
+
j
*
10
]
*
rw
);
int
y4
=
(
output_data_
[
9
+
j
*
10
]
*
rh
);
int
y4
=
(
output_data_
[
9
+
j
*
10
]
*
rh
);
ObjectResult
result_item
;
PaddleDetection
::
ObjectResult
result_item
;
result_item
.
rect
=
{
x1
,
y1
,
x2
,
y2
,
x3
,
y3
,
x4
,
y4
};
result_item
.
rect
=
{
x1
,
y1
,
x2
,
y2
,
x3
,
y3
,
x4
,
y4
};
result_item
.
class_id
=
class_id
;
result_item
.
class_id
=
class_id
;
result_item
.
confidence
=
score
;
result_item
.
confidence
=
score
;
...
@@ -145,7 +145,7 @@ void ObjectDetector::Postprocess(const std::vector<cv::Mat> mats,
...
@@ -145,7 +145,7 @@ void ObjectDetector::Postprocess(const std::vector<cv::Mat> mats,
int
wd
=
xmax
-
xmin
;
int
wd
=
xmax
-
xmin
;
int
hd
=
ymax
-
ymin
;
int
hd
=
ymax
-
ymin
;
ObjectResult
result_item
;
PaddleDetection
::
ObjectResult
result_item
;
result_item
.
rect
=
{
xmin
,
ymin
,
xmax
,
ymax
};
result_item
.
rect
=
{
xmin
,
ymin
,
xmax
,
ymax
};
result_item
.
class_id
=
class_id
;
result_item
.
class_id
=
class_id
;
result_item
.
confidence
=
score
;
result_item
.
confidence
=
score
;
...
@@ -160,7 +160,7 @@ void ObjectDetector::Predict(const std::vector<cv::Mat>& imgs,
...
@@ -160,7 +160,7 @@ void ObjectDetector::Predict(const std::vector<cv::Mat>& imgs,
const
double
threshold
,
const
double
threshold
,
const
int
warmup
,
const
int
warmup
,
const
int
repeats
,
const
int
repeats
,
std
::
vector
<
ObjectResult
>*
result
,
std
::
vector
<
PaddleDetection
::
ObjectResult
>*
result
,
std
::
vector
<
int
>*
bbox_num
,
std
::
vector
<
int
>*
bbox_num
,
std
::
vector
<
double
>*
times
)
{
std
::
vector
<
double
>*
times
)
{
auto
preprocess_start
=
std
::
chrono
::
steady_clock
::
now
();
auto
preprocess_start
=
std
::
chrono
::
steady_clock
::
now
();
...
@@ -185,6 +185,7 @@ void ObjectDetector::Predict(const std::vector<cv::Mat>& imgs,
...
@@ -185,6 +185,7 @@ void ObjectDetector::Predict(const std::vector<cv::Mat>& imgs,
in_data_all
.
end
(),
inputs_
.
im_data_
.
begin
(),
inputs_
.
im_data_
.
end
());
in_data_all
.
end
(),
inputs_
.
im_data_
.
begin
(),
inputs_
.
im_data_
.
end
());
}
}
auto
preprocess_end
=
std
::
chrono
::
steady_clock
::
now
();
auto
preprocess_end
=
std
::
chrono
::
steady_clock
::
now
();
std
::
vector
<
const
float
*>
output_data_list_
;
// Prepare input tensor
// Prepare input tensor
auto
input_names
=
predictor_
->
GetInputNames
();
auto
input_names
=
predictor_
->
GetInputNames
();
...
@@ -213,16 +214,46 @@ void ObjectDetector::Predict(const std::vector<cv::Mat>& imgs,
...
@@ -213,16 +214,46 @@ void ObjectDetector::Predict(const std::vector<cv::Mat>& imgs,
predictor_
->
Run
();
predictor_
->
Run
();
// Get output tensor
// Get output tensor
auto
output_names
=
predictor_
->
GetOutputNames
();
auto
output_names
=
predictor_
->
GetOutputNames
();
auto
out_tensor
=
predictor_
->
GetTensor
(
output_names
[
0
]);
if
(
config_
.
arch_
==
"PicoDet"
)
{
auto
out_bbox_num
=
predictor_
->
GetTensor
(
output_names
[
1
]);
for
(
int
j
=
0
;
j
<
output_names
.
size
();
j
++
)
{
auto
output_tensor
=
predictor_
->
GetTensor
(
output_names
[
j
]);
const
float
*
outptr
=
output_tensor
->
data
<
float
>
();
std
::
vector
<
int64_t
>
output_shape
=
output_tensor
->
shape
();
output_data_list_
.
push_back
(
outptr
);
}
}
else
{
auto
out_tensor
=
predictor_
->
GetTensor
(
output_names
[
0
]);
auto
out_bbox_num
=
predictor_
->
GetTensor
(
output_names
[
1
]);
}
}
}
bool
is_rbox
=
false
;
bool
is_rbox
=
false
;
auto
inference_start
=
std
::
chrono
::
steady_clock
::
now
();
auto
inference_start
=
std
::
chrono
::
steady_clock
::
now
();
for
(
int
i
=
0
;
i
<
repeats
;
i
++
)
{
for
(
int
i
=
0
;
i
<
repeats
;
i
++
)
{
predictor_
->
Run
();
predictor_
->
Run
();
// Get output tensor
}
auto
output_names
=
predictor_
->
GetOutputNames
();
auto
inference_end
=
std
::
chrono
::
steady_clock
::
now
();
auto
postprocess_start
=
std
::
chrono
::
steady_clock
::
now
();
// Get output tensor
output_data_list_
.
clear
();
int
num_class
=
80
;
int
reg_max
=
7
;
auto
output_names
=
predictor_
->
GetOutputNames
();
// TODO: Unified model output.
if
(
config_
.
arch_
==
"PicoDet"
)
{
for
(
int
i
=
0
;
i
<
output_names
.
size
();
i
++
)
{
auto
output_tensor
=
predictor_
->
GetTensor
(
output_names
[
i
]);
const
float
*
outptr
=
output_tensor
->
data
<
float
>
();
std
::
vector
<
int64_t
>
output_shape
=
output_tensor
->
shape
();
if
(
i
==
0
)
{
num_class
=
output_shape
[
2
];
}
if
(
i
==
config_
.
fpn_stride_
.
size
())
{
reg_max
=
output_shape
[
2
]
/
4
-
1
;
}
output_data_list_
.
push_back
(
outptr
);
}
}
else
{
auto
output_tensor
=
predictor_
->
GetTensor
(
output_names
[
0
]);
auto
output_tensor
=
predictor_
->
GetTensor
(
output_names
[
0
]);
auto
output_shape
=
output_tensor
->
shape
();
auto
output_shape
=
output_tensor
->
shape
();
auto
out_bbox_num
=
predictor_
->
GetTensor
(
output_names
[
1
]);
auto
out_bbox_num
=
predictor_
->
GetTensor
(
output_names
[
1
]);
...
@@ -250,15 +281,22 @@ void ObjectDetector::Predict(const std::vector<cv::Mat>& imgs,
...
@@ -250,15 +281,22 @@ void ObjectDetector::Predict(const std::vector<cv::Mat>& imgs,
out_bbox_num_size
,
out_bbox_num_size
,
out_bbox_num_data_
.
data
());
out_bbox_num_data_
.
data
());
}
}
auto
inference_end
=
std
::
chrono
::
steady_clock
::
now
();
auto
postprocess_start
=
std
::
chrono
::
steady_clock
::
now
();
// Postprocessing result
// Postprocessing result
result
->
clear
();
result
->
clear
();
Postprocess
(
imgs
,
result
,
out_bbox_num_data_
,
is_rbox
);
if
(
config_
.
arch_
==
"PicoDet"
)
{
bbox_num
->
clear
();
PaddleDetection
::
PicoDetPostProcess
(
for
(
int
k
=
0
;
k
<
out_bbox_num_data_
.
size
();
k
++
)
{
result
,
output_data_list_
,
config_
.
fpn_stride_
,
int
tmp
=
out_bbox_num_data_
[
k
];
inputs_
.
im_shape_
,
inputs_
.
scale_factor_
,
bbox_num
->
push_back
(
tmp
);
config_
.
nms_info_
[
"score_threshold"
].
as
<
float
>
(),
config_
.
nms_info_
[
"nms_threshold"
].
as
<
float
>
(),
num_class
,
reg_max
);
bbox_num
->
push_back
(
result
->
size
());
}
else
{
Postprocess
(
imgs
,
result
,
out_bbox_num_data_
,
is_rbox
);
bbox_num
->
clear
();
for
(
int
k
=
0
;
k
<
out_bbox_num_data_
.
size
();
k
++
)
{
int
tmp
=
out_bbox_num_data_
[
k
];
bbox_num
->
push_back
(
tmp
);
}
}
}
auto
postprocess_end
=
std
::
chrono
::
steady_clock
::
now
();
auto
postprocess_end
=
std
::
chrono
::
steady_clock
::
now
();
...
...
deploy/lite/src/picodet_postprocess.cc
0 → 100644
浏览文件 @
04d837cf
// Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "include/picodet_postprocess.h"
namespace
PaddleDetection
{
float
fast_exp
(
float
x
)
{
union
{
uint32_t
i
;
float
f
;
}
v
{};
v
.
i
=
(
1
<<
23
)
*
(
1.4426950409
*
x
+
126.93490512
f
);
return
v
.
f
;
}
template
<
typename
_Tp
>
int
activation_function_softmax
(
const
_Tp
*
src
,
_Tp
*
dst
,
int
length
)
{
const
_Tp
alpha
=
*
std
::
max_element
(
src
,
src
+
length
);
_Tp
denominator
{
0
};
for
(
int
i
=
0
;
i
<
length
;
++
i
)
{
dst
[
i
]
=
fast_exp
(
src
[
i
]
-
alpha
);
denominator
+=
dst
[
i
];
}
for
(
int
i
=
0
;
i
<
length
;
++
i
)
{
dst
[
i
]
/=
denominator
;
}
return
0
;
}
// PicoDet decode
PaddleDetection
::
ObjectResult
disPred2Bbox
(
const
float
*&
dfl_det
,
int
label
,
float
score
,
int
x
,
int
y
,
int
stride
,
std
::
vector
<
float
>
im_shape
,
int
reg_max
)
{
float
ct_x
=
(
x
+
0.5
)
*
stride
;
float
ct_y
=
(
y
+
0.5
)
*
stride
;
std
::
vector
<
float
>
dis_pred
;
dis_pred
.
resize
(
4
);
for
(
int
i
=
0
;
i
<
4
;
i
++
)
{
float
dis
=
0
;
float
*
dis_after_sm
=
new
float
[
reg_max
+
1
];
activation_function_softmax
(
dfl_det
+
i
*
(
reg_max
+
1
),
dis_after_sm
,
reg_max
+
1
);
for
(
int
j
=
0
;
j
<
reg_max
+
1
;
j
++
)
{
dis
+=
j
*
dis_after_sm
[
j
];
}
dis
*=
stride
;
dis_pred
[
i
]
=
dis
;
delete
[]
dis_after_sm
;
}
int
xmin
=
(
int
)(
std
::
max
)(
ct_x
-
dis_pred
[
0
],
.0
f
);
int
ymin
=
(
int
)(
std
::
max
)(
ct_y
-
dis_pred
[
1
],
.0
f
);
int
xmax
=
(
int
)(
std
::
min
)(
ct_x
+
dis_pred
[
2
],
(
float
)
im_shape
[
0
]);
int
ymax
=
(
int
)(
std
::
min
)(
ct_y
+
dis_pred
[
3
],
(
float
)
im_shape
[
1
]);
PaddleDetection
::
ObjectResult
result_item
;
result_item
.
rect
=
{
xmin
,
ymin
,
xmax
,
ymax
};
result_item
.
class_id
=
label
;
result_item
.
confidence
=
score
;
return
result_item
;
}
void
PicoDetPostProcess
(
std
::
vector
<
PaddleDetection
::
ObjectResult
>*
results
,
std
::
vector
<
const
float
*>
outs
,
std
::
vector
<
int
>
fpn_stride
,
std
::
vector
<
float
>
im_shape
,
std
::
vector
<
float
>
scale_factor
,
float
score_threshold
,
float
nms_threshold
,
int
num_class
,
int
reg_max
)
{
std
::
vector
<
std
::
vector
<
PaddleDetection
::
ObjectResult
>>
bbox_results
;
bbox_results
.
resize
(
num_class
);
int
in_h
=
im_shape
[
0
],
in_w
=
im_shape
[
1
];
for
(
int
i
=
0
;
i
<
fpn_stride
.
size
();
++
i
)
{
int
feature_h
=
in_h
/
fpn_stride
[
i
];
int
feature_w
=
in_w
/
fpn_stride
[
i
];
for
(
int
idx
=
0
;
idx
<
feature_h
*
feature_w
;
idx
++
)
{
const
float
*
scores
=
outs
[
i
]
+
(
idx
*
num_class
);
int
row
=
idx
/
feature_w
;
int
col
=
idx
%
feature_w
;
float
score
=
0
;
int
cur_label
=
0
;
for
(
int
label
=
0
;
label
<
num_class
;
label
++
)
{
if
(
scores
[
label
]
>
score
)
{
score
=
scores
[
label
];
cur_label
=
label
;
}
}
if
(
score
>
score_threshold
)
{
const
float
*
bbox_pred
=
outs
[
i
+
fpn_stride
.
size
()]
+
(
idx
*
4
*
(
reg_max
+
1
));
bbox_results
[
cur_label
].
push_back
(
disPred2Bbox
(
bbox_pred
,
cur_label
,
score
,
col
,
row
,
fpn_stride
[
i
],
im_shape
,
reg_max
));
}
}
}
for
(
int
i
=
0
;
i
<
(
int
)
bbox_results
.
size
();
i
++
)
{
PaddleDetection
::
nms
(
bbox_results
[
i
],
nms_threshold
);
for
(
auto
box
:
bbox_results
[
i
])
{
box
.
rect
[
0
]
=
box
.
rect
[
0
]
/
scale_factor
[
1
];
box
.
rect
[
2
]
=
box
.
rect
[
2
]
/
scale_factor
[
1
];
box
.
rect
[
1
]
=
box
.
rect
[
1
]
/
scale_factor
[
0
];
box
.
rect
[
3
]
=
box
.
rect
[
3
]
/
scale_factor
[
0
];
results
->
push_back
(
box
);
}
}
}
}
// namespace PaddleDetection
\ No newline at end of file
deploy/lite/src/utils.cc
0 → 100644
浏览文件 @
04d837cf
// Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "include/utils.h"
namespace
PaddleDetection
{
void
nms
(
std
::
vector
<
ObjectResult
>
&
input_boxes
,
float
nms_threshold
)
{
std
::
sort
(
input_boxes
.
begin
(),
input_boxes
.
end
(),
[](
ObjectResult
a
,
ObjectResult
b
)
{
return
a
.
confidence
>
b
.
confidence
;
});
std
::
vector
<
float
>
vArea
(
input_boxes
.
size
());
for
(
int
i
=
0
;
i
<
int
(
input_boxes
.
size
());
++
i
)
{
vArea
[
i
]
=
(
input_boxes
.
at
(
i
).
rect
[
2
]
-
input_boxes
.
at
(
i
).
rect
[
0
]
+
1
)
*
(
input_boxes
.
at
(
i
).
rect
[
3
]
-
input_boxes
.
at
(
i
).
rect
[
1
]
+
1
);
}
for
(
int
i
=
0
;
i
<
int
(
input_boxes
.
size
());
++
i
)
{
for
(
int
j
=
i
+
1
;
j
<
int
(
input_boxes
.
size
());)
{
float
xx1
=
(
std
::
max
)(
input_boxes
[
i
].
rect
[
0
],
input_boxes
[
j
].
rect
[
0
]);
float
yy1
=
(
std
::
max
)(
input_boxes
[
i
].
rect
[
1
],
input_boxes
[
j
].
rect
[
1
]);
float
xx2
=
(
std
::
min
)(
input_boxes
[
i
].
rect
[
2
],
input_boxes
[
j
].
rect
[
2
]);
float
yy2
=
(
std
::
min
)(
input_boxes
[
i
].
rect
[
3
],
input_boxes
[
j
].
rect
[
3
]);
float
w
=
(
std
::
max
)(
float
(
0
),
xx2
-
xx1
+
1
);
float
h
=
(
std
::
max
)(
float
(
0
),
yy2
-
yy1
+
1
);
float
inter
=
w
*
h
;
float
ovr
=
inter
/
(
vArea
[
i
]
+
vArea
[
j
]
-
inter
);
if
(
ovr
>=
nms_threshold
)
{
input_boxes
.
erase
(
input_boxes
.
begin
()
+
j
);
vArea
.
erase
(
vArea
.
begin
()
+
j
);
}
else
{
j
++
;
}
}
}
}
}
// namespace PaddleDetection
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录