Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
PaddlePaddle
PaddleHub
提交
c3acf409
P
PaddleHub
项目概览
PaddlePaddle
/
PaddleHub
1 年多 前同步成功
通知
283
Star
12117
Fork
2091
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
200
列表
看板
标记
里程碑
合并请求
4
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
P
PaddleHub
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
200
Issue
200
列表
看板
标记
里程碑
合并请求
4
合并请求
4
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
未验证
提交
c3acf409
编写于
10月 21, 2020
作者:
W
wuzewu
提交者:
GitHub
10月 21, 2020
浏览文件
操作
浏览文件
下载
差异文件
add efficientnetb0_small_imagenet 2.0beta (#960)
上级
7915a15b
e8501c77
变更
8
展开全部
隐藏空白更改
内联
并排
Showing
8 changed file
with
737 addition
and
2491 deletion
+737
-2491
hub_module/modules/image/classification/efficientnetb0_small_imagenet/README.md
...ge/classification/efficientnetb0_small_imagenet/README.md
+0
-149
hub_module/modules/image/classification/efficientnetb0_small_imagenet/__init__.py
.../classification/efficientnetb0_small_imagenet/__init__.py
+0
-1
hub_module/modules/image/classification/efficientnetb0_small_imagenet/data_feed.py
...classification/efficientnetb0_small_imagenet/data_feed.py
+0
-98
hub_module/modules/image/classification/efficientnetb0_small_imagenet/efficientnet.py
...ssification/efficientnetb0_small_imagenet/efficientnet.py
+0
-623
hub_module/modules/image/classification/efficientnetb0_small_imagenet/label_list.txt
...assification/efficientnetb0_small_imagenet/label_list.txt
+0
-1000
hub_module/modules/image/classification/efficientnetb0_small_imagenet/layers.py
...ge/classification/efficientnetb0_small_imagenet/layers.py
+0
-248
hub_module/modules/image/classification/efficientnetb0_small_imagenet/module.py
...ge/classification/efficientnetb0_small_imagenet/module.py
+737
-303
hub_module/modules/image/classification/efficientnetb0_small_imagenet/processor.py
...classification/efficientnetb0_small_imagenet/processor.py
+0
-69
未找到文件。
hub_module/modules/image/classification/efficientnetb0_small_imagenet/README.md
已删除
100644 → 0
浏览文件 @
7915a15b
## 命令行预测
```
hub run efficientnetb0_small_imagenet --input_path "/PATH/TO/IMAGE"
```
## API
```
python
def
get_expected_image_width
()
```
返回预处理的图片宽度,也就是224。
```
python
def
get_expected_image_height
()
```
返回预处理的图片高度,也就是224。
```
python
def
get_pretrained_images_mean
()
```
返回预处理的图片均值,也就是
\[
0.485, 0.456, 0.406
\]
。
```
python
def
get_pretrained_images_std
()
```
返回预处理的图片标准差,也就是
\[
0.229, 0.224, 0.225
\]
。
```
python
def
context
(
trainable
=
True
,
pretrained
=
True
)
```
**参数**
*
trainable (bool): 计算图的参数是否为可训练的;
*
pretrained (bool): 是否加载默认的预训练模型。
**返回**
*
inputs (dict): 计算图的输入,key 为 'image', value 为图片的张量;
*
outputs (dict): 计算图的输出,key 为 'classification' 和 'feature_map',其相应的值为:
*
classification (paddle.fluid.framework.Variable): 分类结果,也就是全连接层的输出;
*
feature
\_
map (paddle.fluid.framework.Variable): 特征匹配,全连接层前面的那个张量。
*
context
\_
prog(fluid.Program): 计算图,用于迁移学习。
```
python
def
classify
(
images
=
None
,
paths
=
None
,
batch_size
=
1
,
use_gpu
=
False
,
top_k
=
1
):
```
**参数**
*
images (list
\[
numpy.ndarray
\]
): 图片数据,每一个图片数据的shape 均为
\[
H, W, C
\]
,颜色空间为 BGR;
*
paths (list
\[
str
\]
): 图片的路径;
*
batch
\_
size (int): batch 的大小;
*
use
\_
gpu (bool): 是否使用 GPU 来预测;
*
top
\_
k (int): 返回预测结果的前 k 个。
**返回**
res (list
\[
dict
\]
): 分类结果,列表的每一个元素均为字典,其中 key 为识别动物的类别,value为置信度。
```
python
def
save_inference_model
(
dirname
,
model_filename
=
None
,
params_filename
=
None
,
combined
=
True
)
```
将模型保存到指定路径。
**参数**
*
dirname: 存在模型的目录名称
*
model
\_
filename: 模型文件名称,默认为
\_\_
model
\_\_
*
params
\_
filename: 参数文件名称,默认为
\_\_
params
\_\_
(仅当
`combined`
为True时生效)
*
combined: 是否将参数保存到统一的一个文件中
## 代码示例
```
python
import
paddlehub
as
hub
import
cv2
classifier
=
hub
.
Module
(
name
=
"efficientnetb0_small_imagenet"
)
result
=
classifier
.
classify
(
images
=
[
cv2
.
imread
(
'/PATH/TO/IMAGE'
)])
# or
# result = classifier.classify(paths=['/PATH/TO/IMAGE'])
```
## 服务部署
PaddleHub Serving可以部署一个在线图像识别服务。
## 第一步:启动PaddleHub Serving
运行启动命令:
```
shell
$
hub serving start
-m
efficientnetb0_small_imagenet
```
这样就完成了一个在线图像识别服务化API的部署,默认端口号为8866。
**NOTE:**
如使用GPU预测,则需要在启动服务之前,请设置CUDA
\_
VISIBLE
\_
DEVICES环境变量,否则不用设置。
## 第二步:发送预测请求
配置好服务端,以下数行代码即可实现发送预测请求,获取预测结果
```
python
import
requests
import
json
import
cv2
import
base64
def
cv2_to_base64
(
image
):
data
=
cv2
.
imencode
(
'.jpg'
,
image
)[
1
]
return
base64
.
b64encode
(
data
.
tostring
()).
decode
(
'utf8'
)
# 发送HTTP请求
data
=
{
'images'
:[
cv2_to_base64
(
cv2
.
imread
(
"/PATH/TO/IMAGE"
))]}
headers
=
{
"Content-type"
:
"application/json"
}
url
=
"http://127.0.0.1:8866/predict/efficientnetb0_small_imagenet"
r
=
requests
.
post
(
url
=
url
,
headers
=
headers
,
data
=
json
.
dumps
(
data
))
# 打印预测结果
print
(
r
.
json
()[
"results"
])
```
### 查看代码
https://github.com/PaddlePaddle/PaddleClas
### 依赖
paddlepaddle >= 1.6.2
paddlehub >= 1.6.0
hub_module/modules/image/classification/efficientnetb0_small_imagenet/__init__.py
已删除
100644 → 0
浏览文件 @
7915a15b
# -*- coding:utf-8 -*-
hub_module/modules/image/classification/efficientnetb0_small_imagenet/data_feed.py
已删除
100644 → 0
浏览文件 @
7915a15b
# -*- coding:utf-8 -*-
# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve.
#
# 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.
import
os
import
time
from
collections
import
OrderedDict
import
cv2
import
numpy
as
np
from
PIL
import
Image
__all__
=
[
'reader'
]
DATA_DIM
=
224
img_mean
=
np
.
array
([
0.485
,
0.456
,
0.406
]).
reshape
((
3
,
1
,
1
))
img_std
=
np
.
array
([
0.229
,
0.224
,
0.225
]).
reshape
((
3
,
1
,
1
))
def
resize_short
(
img
,
target_size
):
percent
=
float
(
target_size
)
/
min
(
img
.
size
[
0
],
img
.
size
[
1
])
resized_width
=
int
(
round
(
img
.
size
[
0
]
*
percent
))
resized_height
=
int
(
round
(
img
.
size
[
1
]
*
percent
))
img
=
img
.
resize
((
resized_width
,
resized_height
),
Image
.
LANCZOS
)
return
img
def
crop_image
(
img
,
target_size
,
center
):
width
,
height
=
img
.
size
size
=
target_size
if
center
==
True
:
w_start
=
(
width
-
size
)
/
2
h_start
=
(
height
-
size
)
/
2
else
:
w_start
=
np
.
random
.
randint
(
0
,
width
-
size
+
1
)
h_start
=
np
.
random
.
randint
(
0
,
height
-
size
+
1
)
w_end
=
w_start
+
size
h_end
=
h_start
+
size
img
=
img
.
crop
((
w_start
,
h_start
,
w_end
,
h_end
))
return
img
def
process_image
(
img
):
img
=
resize_short
(
img
,
target_size
=
256
)
img
=
crop_image
(
img
,
target_size
=
DATA_DIM
,
center
=
True
)
if
img
.
mode
!=
'RGB'
:
img
=
img
.
convert
(
'RGB'
)
img
=
np
.
array
(
img
).
astype
(
'float32'
).
transpose
((
2
,
0
,
1
))
/
255
img
-=
img_mean
img
/=
img_std
return
img
def
reader
(
images
=
None
,
paths
=
None
):
"""
Preprocess to yield image.
Args:
images (list[numpy.ndarray]): images data, shape of each is [H, W, C].
paths (list[str]): paths to images.
Yield:
each (collections.OrderedDict): info of original image, preprocessed image.
"""
component
=
list
()
if
paths
:
for
im_path
in
paths
:
each
=
OrderedDict
()
assert
os
.
path
.
isfile
(
im_path
),
"The {} isn't a valid file path."
.
format
(
im_path
)
each
[
'org_im_path'
]
=
im_path
each
[
'org_im'
]
=
Image
.
open
(
im_path
)
each
[
'org_im_width'
],
each
[
'org_im_height'
]
=
each
[
'org_im'
].
size
component
.
append
(
each
)
if
images
is
not
None
:
assert
type
(
images
),
"images is a list."
for
im
in
images
:
each
=
OrderedDict
()
each
[
'org_im'
]
=
Image
.
fromarray
(
im
[:,
:,
::
-
1
])
each
[
'org_im_path'
]
=
'ndarray_time={}'
.
format
(
round
(
time
.
time
(),
6
)
*
1e6
)
each
[
'org_im_width'
],
each
[
'org_im_height'
]
=
each
[
'org_im'
].
size
component
.
append
(
each
)
for
element
in
component
:
element
[
'image'
]
=
process_image
(
element
[
'org_im'
])
yield
element
hub_module/modules/image/classification/efficientnetb0_small_imagenet/efficientnet.py
已删除
100644 → 0
浏览文件 @
7915a15b
此差异已折叠。
点击以展开。
hub_module/modules/image/classification/efficientnetb0_small_imagenet/label_list.txt
已删除
100644 → 0
浏览文件 @
7915a15b
此差异已折叠。
点击以展开。
hub_module/modules/image/classification/efficientnetb0_small_imagenet/layers.py
已删除
100644 → 0
浏览文件 @
7915a15b
# -*- coding:utf-8 -*-
# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve.
#
# 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.
from
__future__
import
absolute_import
from
__future__
import
division
from
__future__
import
print_function
import
math
import
warnings
import
paddle.fluid
as
fluid
def
initial_type
(
name
,
input
,
op_type
,
fan_out
,
init
=
"google"
,
use_bias
=
False
,
filter_size
=
0
,
stddev
=
0.02
):
if
init
==
"kaiming"
:
if
op_type
==
'conv'
:
fan_in
=
input
.
shape
[
1
]
*
filter_size
*
filter_size
elif
op_type
==
'deconv'
:
fan_in
=
fan_out
*
filter_size
*
filter_size
else
:
if
len
(
input
.
shape
)
>
2
:
fan_in
=
input
.
shape
[
1
]
*
input
.
shape
[
2
]
*
input
.
shape
[
3
]
else
:
fan_in
=
input
.
shape
[
1
]
bound
=
1
/
math
.
sqrt
(
fan_in
)
param_attr
=
fluid
.
ParamAttr
(
name
=
name
+
"_weights"
,
initializer
=
fluid
.
initializer
.
Uniform
(
low
=-
bound
,
high
=
bound
))
if
use_bias
==
True
:
bias_attr
=
fluid
.
ParamAttr
(
name
=
name
+
'_offset'
,
initializer
=
fluid
.
initializer
.
Uniform
(
low
=-
bound
,
high
=
bound
))
else
:
bias_attr
=
False
elif
init
==
'google'
:
n
=
filter_size
*
filter_size
*
fan_out
param_attr
=
fluid
.
ParamAttr
(
name
=
name
+
"_weights"
,
initializer
=
fluid
.
initializer
.
NormalInitializer
(
loc
=
0.0
,
scale
=
math
.
sqrt
(
2.0
/
n
)))
if
use_bias
==
True
:
bias_attr
=
fluid
.
ParamAttr
(
name
=
name
+
"_offset"
,
initializer
=
fluid
.
initializer
.
Constant
(
0.0
))
else
:
bias_attr
=
False
else
:
param_attr
=
fluid
.
ParamAttr
(
name
=
name
+
"_weights"
,
initializer
=
fluid
.
initializer
.
NormalInitializer
(
loc
=
0.0
,
scale
=
stddev
))
if
use_bias
==
True
:
bias_attr
=
fluid
.
ParamAttr
(
name
=
name
+
"_offset"
,
initializer
=
fluid
.
initializer
.
Constant
(
0.0
))
else
:
bias_attr
=
False
return
param_attr
,
bias_attr
def
cal_padding
(
img_size
,
stride
,
filter_size
,
dilation
=
1
):
"""Calculate padding size."""
if
img_size
%
stride
==
0
:
out_size
=
max
(
filter_size
-
stride
,
0
)
else
:
out_size
=
max
(
filter_size
-
(
img_size
%
stride
),
0
)
return
out_size
//
2
,
out_size
-
out_size
//
2
def
init_batch_norm_layer
(
name
=
"batch_norm"
):
param_attr
=
fluid
.
ParamAttr
(
name
=
name
+
'_scale'
,
initializer
=
fluid
.
initializer
.
Constant
(
1.0
))
bias_attr
=
fluid
.
ParamAttr
(
name
=
name
+
'_offset'
,
initializer
=
fluid
.
initializer
.
Constant
(
value
=
0.0
))
return
param_attr
,
bias_attr
def
init_fc_layer
(
fout
,
name
=
'fc'
):
n
=
fout
# fan-out
init_range
=
1.0
/
math
.
sqrt
(
n
)
param_attr
=
fluid
.
ParamAttr
(
name
=
name
+
'_weights'
,
initializer
=
fluid
.
initializer
.
UniformInitializer
(
low
=-
init_range
,
high
=
init_range
))
bias_attr
=
fluid
.
ParamAttr
(
name
=
name
+
'_offset'
,
initializer
=
fluid
.
initializer
.
Constant
(
value
=
0.0
))
return
param_attr
,
bias_attr
def
norm_layer
(
input
,
norm_type
=
'batch_norm'
,
name
=
None
):
if
norm_type
==
'batch_norm'
:
param_attr
=
fluid
.
ParamAttr
(
name
=
name
+
'_weights'
,
initializer
=
fluid
.
initializer
.
Constant
(
1.0
))
bias_attr
=
fluid
.
ParamAttr
(
name
=
name
+
'_offset'
,
initializer
=
fluid
.
initializer
.
Constant
(
value
=
0.0
))
return
fluid
.
layers
.
batch_norm
(
input
,
param_attr
=
param_attr
,
bias_attr
=
bias_attr
,
moving_mean_name
=
name
+
'_mean'
,
moving_variance_name
=
name
+
'_variance'
)
elif
norm_type
==
'instance_norm'
:
helper
=
fluid
.
layer_helper
.
LayerHelper
(
"instance_norm"
,
**
locals
())
dtype
=
helper
.
input_dtype
()
epsilon
=
1e-5
mean
=
fluid
.
layers
.
reduce_mean
(
input
,
dim
=
[
2
,
3
],
keep_dim
=
True
)
var
=
fluid
.
layers
.
reduce_mean
(
fluid
.
layers
.
square
(
input
-
mean
),
dim
=
[
2
,
3
],
keep_dim
=
True
)
if
name
is
not
None
:
scale_name
=
name
+
"_scale"
offset_name
=
name
+
"_offset"
scale_param
=
fluid
.
ParamAttr
(
name
=
scale_name
,
initializer
=
fluid
.
initializer
.
Constant
(
1.0
),
trainable
=
True
)
offset_param
=
fluid
.
ParamAttr
(
name
=
offset_name
,
initializer
=
fluid
.
initializer
.
Constant
(
0.0
),
trainable
=
True
)
scale
=
helper
.
create_parameter
(
attr
=
scale_param
,
shape
=
input
.
shape
[
1
:
2
],
dtype
=
dtype
)
offset
=
helper
.
create_parameter
(
attr
=
offset_param
,
shape
=
input
.
shape
[
1
:
2
],
dtype
=
dtype
)
tmp
=
fluid
.
layers
.
elementwise_mul
(
x
=
(
input
-
mean
),
y
=
scale
,
axis
=
1
)
tmp
=
tmp
/
fluid
.
layers
.
sqrt
(
var
+
epsilon
)
tmp
=
fluid
.
layers
.
elementwise_add
(
tmp
,
offset
,
axis
=
1
)
return
tmp
else
:
raise
NotImplementedError
(
"norm tyoe: [%s] is not support"
%
norm_type
)
def
conv2d
(
input
,
num_filters
=
64
,
filter_size
=
7
,
stride
=
1
,
stddev
=
0.02
,
padding
=
0
,
groups
=
None
,
name
=
"conv2d"
,
norm
=
None
,
act
=
None
,
relufactor
=
0.0
,
use_bias
=
False
,
padding_type
=
None
,
initial
=
"normal"
,
use_cudnn
=
True
):
if
padding
!=
0
and
padding_type
!=
None
:
warnings
.
warn
(
'padding value and padding type are set in the same time, and the final padding width and padding height are computed by padding_type'
)
param_attr
,
bias_attr
=
initial_type
(
name
=
name
,
input
=
input
,
op_type
=
'conv'
,
fan_out
=
num_filters
,
init
=
initial
,
use_bias
=
use_bias
,
filter_size
=
filter_size
,
stddev
=
stddev
)
def
get_padding
(
filter_size
,
stride
=
1
,
dilation
=
1
):
padding
=
((
stride
-
1
)
+
dilation
*
(
filter_size
-
1
))
//
2
return
padding
need_crop
=
False
if
padding_type
==
"SAME"
:
top_padding
,
bottom_padding
=
cal_padding
(
input
.
shape
[
2
],
stride
,
filter_size
)
left_padding
,
right_padding
=
cal_padding
(
input
.
shape
[
2
],
stride
,
filter_size
)
height_padding
=
bottom_padding
width_padding
=
right_padding
if
top_padding
!=
bottom_padding
or
left_padding
!=
right_padding
:
height_padding
=
top_padding
+
stride
width_padding
=
left_padding
+
stride
need_crop
=
True
padding
=
[
height_padding
,
width_padding
]
elif
padding_type
==
"VALID"
:
height_padding
=
0
width_padding
=
0
padding
=
[
height_padding
,
width_padding
]
elif
padding_type
==
"DYNAMIC"
:
padding
=
get_padding
(
filter_size
,
stride
)
else
:
padding
=
padding
conv
=
fluid
.
layers
.
conv2d
(
input
,
num_filters
,
filter_size
,
groups
=
groups
,
name
=
name
,
stride
=
stride
,
padding
=
padding
,
use_cudnn
=
use_cudnn
,
param_attr
=
param_attr
,
bias_attr
=
bias_attr
)
if
need_crop
:
conv
=
conv
[:,
:,
1
:,
1
:]
if
norm
is
not
None
:
conv
=
norm_layer
(
input
=
conv
,
norm_type
=
norm
,
name
=
name
+
"_norm"
)
if
act
==
'relu'
:
conv
=
fluid
.
layers
.
relu
(
conv
,
name
=
name
+
'_relu'
)
elif
act
==
'leaky_relu'
:
conv
=
fluid
.
layers
.
leaky_relu
(
conv
,
alpha
=
relufactor
,
name
=
name
+
'_leaky_relu'
)
elif
act
==
'tanh'
:
conv
=
fluid
.
layers
.
tanh
(
conv
,
name
=
name
+
'_tanh'
)
elif
act
==
'sigmoid'
:
conv
=
fluid
.
layers
.
sigmoid
(
conv
,
name
=
name
+
'_sigmoid'
)
elif
act
==
'swish'
:
conv
=
fluid
.
layers
.
swish
(
conv
,
name
=
name
+
'_swish'
)
elif
act
==
None
:
conv
=
conv
else
:
raise
NotImplementedError
(
"activation: [%s] is not support"
%
act
)
return
conv
hub_module/modules/image/classification/efficientnetb0_small_imagenet/module.py
浏览文件 @
c3acf409
此差异已折叠。
点击以展开。
hub_module/modules/image/classification/efficientnetb0_small_imagenet/processor.py
已删除
100644 → 0
浏览文件 @
7915a15b
# -*- coding:utf-8 -*-
# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve.
#
# 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.
from
__future__
import
absolute_import
from
__future__
import
division
from
__future__
import
print_function
import
base64
import
cv2
import
os
import
numpy
as
np
def
base64_to_cv2
(
b64str
):
data
=
base64
.
b64decode
(
b64str
.
encode
(
'utf8'
))
data
=
np
.
fromstring
(
data
,
np
.
uint8
)
data
=
cv2
.
imdecode
(
data
,
cv2
.
IMREAD_COLOR
)
return
data
def
softmax
(
x
):
orig_shape
=
x
.
shape
if
len
(
x
.
shape
)
>
1
:
tmp
=
np
.
max
(
x
,
axis
=
1
)
x
-=
tmp
.
reshape
((
x
.
shape
[
0
],
1
))
x
=
np
.
exp
(
x
)
tmp
=
np
.
sum
(
x
,
axis
=
1
)
x
/=
tmp
.
reshape
((
x
.
shape
[
0
],
1
))
else
:
tmp
=
np
.
max
(
x
)
x
-=
tmp
x
=
np
.
exp
(
x
)
tmp
=
np
.
sum
(
x
)
x
/=
tmp
return
x
def
postprocess
(
data_out
,
label_list
,
top_k
):
"""
Postprocess output of network, one image at a time.
Args:
data_out (numpy.ndarray): output data of network.
label_list (list): list of label.
top_k (int): Return top k results.
"""
output
=
[]
for
result
in
data_out
:
result_i
=
softmax
(
result
)
output_i
=
{}
indexs
=
np
.
argsort
(
result_i
)[::
-
1
][
0
:
top_k
]
for
index
in
indexs
:
label
=
label_list
[
index
].
split
(
','
)[
0
]
output_i
[
label
]
=
float
(
result_i
[
index
])
output
.
append
(
output_i
)
return
output
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录