cpp shitu code format
parent
a96305c225
commit
29f8fb830a
|
@ -59,9 +59,7 @@ public:
|
||||||
this->resize_size_ =
|
this->resize_size_ =
|
||||||
config_file["RecPreProcess"]["transform_ops"][0]["ResizeImage"]["size"]
|
config_file["RecPreProcess"]["transform_ops"][0]["ResizeImage"]["size"]
|
||||||
.as<int>();
|
.as<int>();
|
||||||
this->scale_ = config_file["RecPreProcess"]["transform_ops"][1]
|
this->scale_ = config_file["RecPreProcess"]["transform_ops"][1]["NormalizeImage"]["scale"].as<float>();
|
||||||
["NormalizeImage"]["scale"]
|
|
||||||
.as<float>();
|
|
||||||
this->mean_ = config_file["RecPreProcess"]["transform_ops"][1]
|
this->mean_ = config_file["RecPreProcess"]["transform_ops"][1]
|
||||||
["NormalizeImage"]["mean"]
|
["NormalizeImage"]["mean"]
|
||||||
.as < std::vector < float >> ();
|
.as < std::vector < float >> ();
|
||||||
|
@ -81,6 +79,7 @@ public:
|
||||||
// Run predictor
|
// Run predictor
|
||||||
void Run(cv::Mat &img, std::vector<float> &out_data,
|
void Run(cv::Mat &img, std::vector<float> &out_data,
|
||||||
std::vector<double> ×);
|
std::vector<double> ×);
|
||||||
|
|
||||||
void FeatureNorm(std::vector<float> &feature);
|
void FeatureNorm(std::vector<float> &feature);
|
||||||
|
|
||||||
std::shared_ptr <Predictor> predictor_;
|
std::shared_ptr <Predictor> predictor_;
|
||||||
|
|
|
@ -88,9 +88,11 @@ public:
|
||||||
std::vector <ObjectResult> *result = nullptr,
|
std::vector <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);
|
||||||
|
|
||||||
const std::vector <std::string> &GetLabelList() const {
|
const std::vector <std::string> &GetLabelList() const {
|
||||||
return this->label_list_;
|
return this->label_list_;
|
||||||
}
|
}
|
||||||
|
|
||||||
const float &GetThreshold() const { return this->threshold_; }
|
const float &GetThreshold() const { return this->threshold_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -120,6 +122,7 @@ private:
|
||||||
|
|
||||||
// Preprocess image and copy data to input buffer
|
// Preprocess image and copy data to input buffer
|
||||||
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<int> bbox_num,
|
std::vector <ObjectResult> *result, std::vector<int> bbox_num,
|
||||||
|
|
|
@ -49,12 +49,14 @@ public:
|
||||||
class PreprocessOp {
|
class PreprocessOp {
|
||||||
public:
|
public:
|
||||||
virtual void Init(const YAML::Node &item) = 0;
|
virtual void Init(const YAML::Node &item) = 0;
|
||||||
|
|
||||||
virtual void Run(cv::Mat *im, ImageBlob *data) = 0;
|
virtual void Run(cv::Mat *im, ImageBlob *data) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
class InitInfo : public PreprocessOp {
|
class InitInfo : public PreprocessOp {
|
||||||
public:
|
public:
|
||||||
virtual void Init(const YAML::Node &item) {}
|
virtual void Init(const YAML::Node &item) {}
|
||||||
|
|
||||||
virtual void Run(cv::Mat *im, ImageBlob *data);
|
virtual void Run(cv::Mat *im, ImageBlob *data);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -78,6 +80,7 @@ private:
|
||||||
class Permute : public PreprocessOp {
|
class Permute : public PreprocessOp {
|
||||||
public:
|
public:
|
||||||
virtual void Init(const YAML::Node &item) {}
|
virtual void Init(const YAML::Node &item) {}
|
||||||
|
|
||||||
virtual void Run(cv::Mat *im, ImageBlob *data);
|
virtual void Run(cv::Mat *im, ImageBlob *data);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -46,10 +46,15 @@ public:
|
||||||
this->I.resize(this->return_k * this->max_query_number);
|
this->I.resize(this->return_k * this->max_query_number);
|
||||||
this->D.resize(this->return_k * this->max_query_number);
|
this->D.resize(this->return_k * this->max_query_number);
|
||||||
};
|
};
|
||||||
|
|
||||||
void LoadIdMap();
|
void LoadIdMap();
|
||||||
|
|
||||||
void LoadIndexFile();
|
void LoadIndexFile();
|
||||||
|
|
||||||
const SearchResult &Search(float *feature, int query_number);
|
const SearchResult &Search(float *feature, int query_number);
|
||||||
|
|
||||||
const std::string &GetLabel(faiss::Index::idx_t ind);
|
const std::string &GetLabel(faiss::Index::idx_t ind);
|
||||||
|
|
||||||
const float &GetThreshold() { return this->score_thres; }
|
const float &GetThreshold() { return this->score_thres; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -45,9 +45,14 @@ public:
|
||||||
explicit YamlConfig(const std::string &path) {
|
explicit YamlConfig(const std::string &path) {
|
||||||
config_file = ReadYamlConfig(path);
|
config_file = ReadYamlConfig(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::vector <std::string> ReadDict(const std::string &path);
|
static std::vector <std::string> ReadDict(const std::string &path);
|
||||||
|
|
||||||
static std::map<int, std::string> ReadIndexId(const std::string &path);
|
static std::map<int, std::string> ReadIndexId(const std::string &path);
|
||||||
|
|
||||||
static YAML::Node ReadYamlConfig(const std::string &path);
|
static YAML::Node ReadYamlConfig(const std::string &path);
|
||||||
|
|
||||||
void PrintConfigInfo();
|
void PrintConfigInfo();
|
||||||
|
|
||||||
YAML::Node config_file;
|
YAML::Node config_file;
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,10 +6,7 @@
|
||||||
## 1. 准备环境
|
## 1. 准备环境
|
||||||
|
|
||||||
### 运行准备
|
### 运行准备
|
||||||
- Linux环境,推荐使用docker。
|
- Linux环境,推荐使用ubuntu docker。
|
||||||
- Windows环境,目前支持基于`Visual Studio 2019 Community`进行编译;此外,如果您希望通过生成`sln解决方案`的方式进行编译,可以参考该文档:[https://zhuanlan.zhihu.com/p/145446681](https://zhuanlan.zhihu.com/p/145446681)
|
|
||||||
|
|
||||||
* 该文档主要介绍基于Linux环境下的PaddleClas C++预测流程,如果需要在Windows环境下使用预测库进行C++预测,具体编译方法请参考[Windows下编译教程](./docs/windows_vs2019_build.md)。
|
|
||||||
|
|
||||||
### 1.1 编译opencv库
|
### 1.1 编译opencv库
|
||||||
|
|
||||||
|
@ -103,7 +100,7 @@ make -j
|
||||||
make inference_lib_dist
|
make inference_lib_dist
|
||||||
```
|
```
|
||||||
|
|
||||||
更多编译参数选项可以参考Paddle C++预测库官网:[https://www.paddlepaddle.org.cn/documentation/docs/zh/develop/guides/05_inference_deployment/inference/build_and_install_lib_cn.html#id16](https://www.paddlepaddle.org.cn/documentation/docs/zh/develop/guides/05_inference_deployment/inference/build_and_install_lib_cn.html#id16)。
|
更多编译参数选项可以参考[Paddle C++预测库官网](https://www.paddlepaddle.org.cn/documentation/docs/zh/develop/guides/05_inference_deployment/inference/build_and_install_lib_cn.html#id16)。
|
||||||
|
|
||||||
|
|
||||||
* 编译完成之后,可以在`build/paddle_inference_install_dir/`文件下看到生成了以下文件及文件夹。
|
* 编译完成之后,可以在`build/paddle_inference_install_dir/`文件下看到生成了以下文件及文件夹。
|
||||||
|
@ -137,6 +134,7 @@ tar -xvf paddle_inference.tgz
|
||||||
### 1.3 安装faiss库
|
### 1.3 安装faiss库
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
|
# 下载 faiss
|
||||||
git clone https://github.com/facebookresearch/faiss.git
|
git clone https://github.com/facebookresearch/faiss.git
|
||||||
cd faiss
|
cd faiss
|
||||||
cmake -B build . -DFAISS_ENABLE_PYTHON=OFF -DCMAKE_INSTALL_PREFIX=${faiss_install_path}
|
cmake -B build . -DFAISS_ENABLE_PYTHON=OFF -DCMAKE_INSTALL_PREFIX=${faiss_install_path}
|
||||||
|
@ -144,22 +142,19 @@ tar -xvf paddle_inference.tgz
|
||||||
make -C build install
|
make -C build install
|
||||||
```
|
```
|
||||||
|
|
||||||
## 2 开始运行
|
在安装`faiss`前,请安装`openblas`,`ubuntu`系统中安装命令如下:
|
||||||
|
|
||||||
### 2.1 将模型导出为inference model
|
|
||||||
|
|
||||||
* 可以参考[模型导出](../../tools/export_model.py),导出`inference model`,用于模型预测。得到预测模型后,假设模型文件放在`inference`目录下,则目录结构如下。
|
|
||||||
|
|
||||||
|
```shell
|
||||||
|
apt-get install libopenblas-dev
|
||||||
```
|
```
|
||||||
inference/
|
|
||||||
|--cls_infer.pdmodel
|
注意本教程以安装faiss cpu版本为例,安装时请参考[faiss](https://github.com/facebookresearch/faiss)官网文档,根据需求自行安装。
|
||||||
|--cls_infer.pdiparams
|
|
||||||
```
|
## 2 代码编译
|
||||||
**注意**:上述文件中,`cls_infer.pdmodel`文件存储了模型结构信息,`cls_infer.pdiparams`文件存储了模型参数信息。注意两个文件的路径需要与配置文件`tools/config.txt`中的`cls_model_path`和`cls_params_path`参数对应一致。
|
|
||||||
|
|
||||||
### 2.2 编译PaddleClas C++预测demo
|
### 2.2 编译PaddleClas C++预测demo
|
||||||
|
|
||||||
* 编译命令如下,其中Paddle C++预测库、opencv等其他依赖库的地址需要换成自己机器上的实际地址。
|
编译命令如下,其中Paddle C++预测库、opencv等其他依赖库的地址需要换成自己机器上的实际地址。同时,编译过程中需要下载编译`yaml-cpp`等C++库,请保持联网环境。
|
||||||
|
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
|
@ -169,11 +164,12 @@ sh tools/build.sh
|
||||||
具体地,`tools/build.sh`中内容如下。
|
具体地,`tools/build.sh`中内容如下。
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
OPENCV_DIR=your_opencv_dir
|
OPENCV_DIR=${opencv_install_dir}
|
||||||
LIB_DIR=your_paddle_inference_dir
|
LIB_DIR=${paddle_inference_dir}
|
||||||
CUDA_LIB_DIR=your_cuda_lib_dir
|
CUDA_LIB_DIR=/usr/local/cuda/lib64
|
||||||
CUDNN_LIB_DIR=your_cudnn_lib_dir
|
CUDNN_LIB_DIR=/usr/lib/x86_64-linux-gnu/
|
||||||
TENSORRT_DIR=your_tensorrt_lib_dir
|
FAISS_DIR=${faiss_install_dir}
|
||||||
|
FAISS_WITH_MKL=OFF
|
||||||
|
|
||||||
BUILD_DIR=build
|
BUILD_DIR=build
|
||||||
rm -rf ${BUILD_DIR}
|
rm -rf ${BUILD_DIR}
|
||||||
|
@ -182,14 +178,14 @@ cd ${BUILD_DIR}
|
||||||
cmake .. \
|
cmake .. \
|
||||||
-DPADDLE_LIB=${LIB_DIR} \
|
-DPADDLE_LIB=${LIB_DIR} \
|
||||||
-DWITH_MKL=ON \
|
-DWITH_MKL=ON \
|
||||||
-DDEMO_NAME=clas_system \
|
|
||||||
-DWITH_GPU=OFF \
|
-DWITH_GPU=OFF \
|
||||||
-DWITH_STATIC_LIB=OFF \
|
-DWITH_STATIC_LIB=OFF \
|
||||||
-DWITH_TENSORRT=OFF \
|
-DUSE_TENSORRT=OFF \
|
||||||
-DTENSORRT_DIR=${TENSORRT_DIR} \
|
|
||||||
-DOPENCV_DIR=${OPENCV_DIR} \
|
-DOPENCV_DIR=${OPENCV_DIR} \
|
||||||
-DCUDNN_LIB=${CUDNN_LIB_DIR} \
|
-DCUDNN_LIB=${CUDNN_LIB_DIR} \
|
||||||
-DCUDA_LIB=${CUDA_LIB_DIR} \
|
-DCUDA_LIB=${CUDA_LIB_DIR} \
|
||||||
|
-DFAISS_DIR=${FAISS_DIR} \
|
||||||
|
-DFAISS_WITH_MKL=${FAISS_WITH_MKL}
|
||||||
|
|
||||||
make -j
|
make -j
|
||||||
```
|
```
|
||||||
|
@ -197,47 +193,75 @@ make -j
|
||||||
上述命令中,
|
上述命令中,
|
||||||
|
|
||||||
* `OPENCV_DIR`为opencv编译安装的地址(本例中为`opencv-3.4.7/opencv3`文件夹的路径);
|
* `OPENCV_DIR`为opencv编译安装的地址(本例中为`opencv-3.4.7/opencv3`文件夹的路径);
|
||||||
|
|
||||||
* `LIB_DIR`为下载的Paddle预测库(`paddle_inference`文件夹),或编译生成的Paddle预测库(`build/paddle_inference_install_dir`文件夹)的路径;
|
* `LIB_DIR`为下载的Paddle预测库(`paddle_inference`文件夹),或编译生成的Paddle预测库(`build/paddle_inference_install_dir`文件夹)的路径;
|
||||||
|
|
||||||
* `CUDA_LIB_DIR`为cuda库文件地址,在docker中为`/usr/local/cuda/lib64`;
|
* `CUDA_LIB_DIR`为cuda库文件地址,在docker中为`/usr/local/cuda/lib64`;
|
||||||
|
|
||||||
* `CUDNN_LIB_DIR`为cudnn库文件地址,在docker中为`/usr/lib/x86_64-linux-gnu/`。
|
* `CUDNN_LIB_DIR`为cudnn库文件地址,在docker中为`/usr/lib/x86_64-linux-gnu/`。
|
||||||
|
|
||||||
* `TENSORRT_DIR`是tensorrt库文件地址,在dokcer中为`/usr/local/TensorRT6-cuda10.0-cudnn7/`,TensorRT需要结合GPU使用。
|
* `TENSORRT_DIR`是tensorrt库文件地址,在dokcer中为`/usr/local/TensorRT6-cuda10.0-cudnn7/`,TensorRT需要结合GPU使用。
|
||||||
|
* `FAISS_DIR`是faiss的安装地址
|
||||||
在执行上述命令,编译完成之后,会在当前路径下生成`build`文件夹,其中生成一个名为`clas_system`的可执行文件。
|
* `FAISS_WITH_MKL`是指在编译faiss的过程中,是否使用了mkldnn,本文档中编译faiss,没有使用,而使用了openblas,故设置为`OFF`,若使用了mkldnn,则为`ON`.
|
||||||
|
|
||||||
|
|
||||||
### 运行demo
|
在执行上述命令,编译完成之后,会在当前路径下生成`build`文件夹,其中生成一个名为`pp_shitu`的可执行文件。
|
||||||
* 首先修改`tools/config.txt`中对应字段:
|
|
||||||
* use_gpu:是否使用GPU;
|
|
||||||
* gpu_id:使用的GPU卡号;
|
|
||||||
* gpu_mem:显存;
|
|
||||||
* cpu_math_library_num_threads:底层科学计算库所用线程的数量;
|
|
||||||
* use_mkldnn:是否使用MKLDNN加速;
|
|
||||||
* use_tensorrt: 是否使用tensorRT进行加速;
|
|
||||||
* use_fp16:是否使用半精度浮点数进行计算,该选项仅在use_tensorrt为true时有效;
|
|
||||||
* cls_model_path:预测模型结构文件路径;
|
|
||||||
* cls_params_path:预测模型参数文件路径;
|
|
||||||
* resize_short_size:预处理时图像缩放大小;
|
|
||||||
* crop_size:预处理时图像裁剪后的大小。
|
|
||||||
|
|
||||||
* 然后修改`tools/run.sh`:
|
## 3 运行demo
|
||||||
* `./build/clas_system ./tools/config.txt ./docs/imgs/ILSVRC2012_val_00000666.JPEG`
|
|
||||||
* 上述命令中分别为:编译得到的可执行文件`clas_system`;运行时的配置文件`config.txt`;待预测的图像。
|
|
||||||
|
|
||||||
* 最后执行以下命令,完成对一幅图像的分类。
|
- 请参考[识别快速开始文档](../../docs/zh_CN/quick_start/quick_start_recognition.md),下载好相应的 轻量级通用主体检测模型、轻量级通用识别模型及瓶装饮料测试数据并解压。
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
sh tools/run.sh
|
mkdir models
|
||||||
|
cd models
|
||||||
|
wget https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/picodet_PPLCNet_x2_5_mainbody_lite_v1.0_infer.tar
|
||||||
|
tar -xf picodet_PPLCNet_x2_5_mainbody_lite_v1.0_infer.tar
|
||||||
|
wget https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/general_PPLCNet_x2_5_lite_v1.0_infer.tar
|
||||||
|
tar -xf general_PPLCNet_x2_5_lite_v1.0_infer.tar
|
||||||
|
cd ..
|
||||||
|
|
||||||
|
mkdir data
|
||||||
|
cd data
|
||||||
|
wget https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/data/drink_dataset_v1.0.tar
|
||||||
|
tar -xf drink_dataset_v1.0.tar
|
||||||
|
cd ..
|
||||||
```
|
```
|
||||||
|
|
||||||
* 最终屏幕上会输出结果,如下图所示。
|
- 将相应的yaml文件拷到`test`文件夹下
|
||||||
|
|
||||||
<div align="center">
|
```shell
|
||||||
<img src="./docs/imgs/cpp_infer_result.png" width="600">
|
cp ../configs/inference_drink.yaml .
|
||||||
</div>
|
```
|
||||||
|
|
||||||
|
- 将`inference_drink.yaml`中的相对路径,改成基于本目录的路径或者绝对路径。涉及到的参数有
|
||||||
|
|
||||||
其中`class id`表示置信度最高的类别对应的id,score表示图片属于该类别的概率。
|
- Global.infer_imgs :此参数可以是具体的图像地址,也可以是图像集所在的目录
|
||||||
|
- Global.det_inference_model_dir : 检测模型存储目录
|
||||||
|
- Global.rec_inference_model_dir : 识别模型存储目录
|
||||||
|
- IndexProcess.index_dir : 检索库的存储目录,在示例中,检索库在下载的demo数据中。
|
||||||
|
|
||||||
|
- 字典转换
|
||||||
|
|
||||||
|
由于python的检索库的字典,使用`pickle`进行的序列化存储,导致C++不方便读取,因此进行转换
|
||||||
|
|
||||||
|
```shell
|
||||||
|
python tools/transform_id_map.py -c inference_drink.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
转换成功后,在`IndexProcess.index_dir`目录下生成`id_map.txt`,方便c++ 读取。
|
||||||
|
|
||||||
|
- 执行程序
|
||||||
|
|
||||||
|
```shell
|
||||||
|
./build/pp_shitu -c inference_drink.yaml
|
||||||
|
# or
|
||||||
|
./build/pp_shitu -config inference_drink.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
若对图像集进行检索,则可能得到,如下结果。注意,此结果只做展示,具体以实际运行结果为准。
|
||||||
|
|
||||||
|
同时,需注意的是,由于opencv 版本问题,会导致图像在预处理的过程中,resize产生细微差别,导致python 和c++结果,轻微不同,如bbox相差几个像素,检索结果小数点后3位diff等。但不会改变最终检索label。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 4 使用自己模型
|
||||||
|
|
||||||
|
使用自己训练的模型,可以参考[模型导出](../../docs/zh_CN/inference_deployment/export_model.md),导出`inference model`,用于模型预测。
|
||||||
|
|
||||||
|
同时注意修改`yaml`文件中具体参数。
|
||||||
|
|
|
@ -37,8 +37,10 @@
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace cv;
|
using namespace cv;
|
||||||
|
|
||||||
DEFINE_string(config, "", "Path of yaml file");
|
DEFINE_string(config,
|
||||||
DEFINE_string(c, "", "Path of yaml file");
|
"", "Path of yaml file");
|
||||||
|
DEFINE_string(c,
|
||||||
|
"", "Path of yaml file");
|
||||||
|
|
||||||
void DetPredictImage(const std::vector <cv::Mat> &batch_imgs,
|
void DetPredictImage(const std::vector <cv::Mat> &batch_imgs,
|
||||||
const std::vector <std::string> &all_img_paths,
|
const std::vector <std::string> &all_img_paths,
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
OPENCV_DIR=/work/project/project/cpp_infer/opencv-3.4.7/opencv3
|
OPENCV_DIR=${opencv_install_dir}
|
||||||
LIB_DIR=/work/project/project/cpp_infer/paddle_inference/
|
LIB_DIR=${paddle_inference_dir}
|
||||||
CUDA_LIB_DIR=/usr/local/cuda/lib64
|
CUDA_LIB_DIR=/usr/local/cuda/lib64
|
||||||
CUDNN_LIB_DIR=/usr/lib/x86_64-linux-gnu/
|
CUDNN_LIB_DIR=/usr/lib/x86_64-linux-gnu/
|
||||||
FAISS_DIR=/work/project/project/cpp_infer/faiss/faiss_install
|
FAISS_DIR=${faiss_install_dir}
|
||||||
FAISS_WITH_MKL=OFF
|
FAISS_WITH_MKL=OFF
|
||||||
|
|
||||||
BUILD_DIR=build
|
BUILD_DIR=build
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
# model load config
|
|
||||||
use_gpu 0
|
|
||||||
gpu_id 0
|
|
||||||
gpu_mem 4000
|
|
||||||
cpu_threads 10
|
|
||||||
use_mkldnn 1
|
|
||||||
use_tensorrt 0
|
|
||||||
use_fp16 0
|
|
||||||
|
|
||||||
# cls config
|
|
||||||
cls_model_path /PaddleClas/inference/cls_infer.pdmodel
|
|
||||||
cls_params_path /PaddleClas/inference/cls_infer.pdiparams
|
|
||||||
resize_short_size 256
|
|
||||||
crop_size 224
|
|
||||||
|
|
||||||
# for log env info
|
|
||||||
benchmark 0
|
|
|
@ -1 +0,0 @@
|
||||||
./build/clas_system ../configs/inference_rec.yaml
|
|
Binary file not shown.
After Width: | Height: | Size: 164 KiB |
Loading…
Reference in New Issue