Win10+CUDA8.0+CuDnn5.0+微软SSD编译

版权声明:guojawee https://blog.csdn.net/weixin_36750623/article/details/83536766

Win10+CUDA8.0+CuDnn5.0+微软SSD编译

http://m.blog.csdn.net/Chen_yingpeng/article/details/59056245

1.依赖软件

  • 1.VS2013
  • 2.Anacaonda Python 2.7 x64 (https://www.continuum.io/downloads) 安装目录C:/Anaconda2下
  • 3.Git-2.11.1-64-bit.exe
  • 4.CUDA8.0 cuDNN v5.0
    • 安装Cuda8.0
      默认安装路径,安装完成后进行测试
      测试:
      (1)进入cmd(我用的是管理员),输入nvcc -V命令
      (2)接下来运行个例程试试
      在C:\ProgramData\NVIDIA Corporation\CUDA Samples\v8.0\1_Utilities\deviceQuery中打开deviceQuery_vs2013.sln
      再在主程序的最后屏蔽掉exit(EXIT_SUCCESS); 加上system(“pause”);否则会闪退。
      点击编译运行,结果会显示你的显卡的信息。
      注解:如果运行成功,那么恭喜你的CUDA,CUDNN成功的在win10上安装成功了~~~

    • 配置CUDNN V5 : 略过此步骤
      把解压后的cudnn-8.0-win-x64-v4.0-prod.zip文件夹中的bin,lib/x64,include中的内容拷贝至 C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v8.0中的bin,lib/x64,include三个子目录下即可。

2.开始配置

caffe-ssd-microsoft: https://github.com/conner99/caffe/tree/ssd-microsoft

0.cudnn解压到E:\Caffe,即生成 E:\Caffe\cuda
1.
E:\Caffe\caffe-ssd-microsoft\windows

[a]. 配置CommonSettings.props
CommonSettings.props.example–>CommonSettings.props
修改CommonSettings.props:

UseCuDNN-->true;    //使用CUDNN,CUDNN解压后的cuda的上层目录 
CuDnnPath-->E:\caffe
 
CudaVersion-->8.0;  //CUDA版本
 
PythonSopport-->true;   //python接口
 
PythonDir-->C:\Anaconda2\   //Anaconda安装路径 pythondir
 
compute_52,sm_52;compute_61,sm_61      

https://developer.nvidia.com/cuda-gpus

[b].修改bbox_util.cu,注释掉所有带thrust的语句
[c].修改detection_output_layer.cu和detection_output_layer.cpp文件,注释掉所有regex和rv的语句

//boost::regex exp(""(null|true|false|-?[0-9]+(\.[0-9]+)?)"");
//std::string rv = boost::regex_replace(ss.str(), exp, "$1");
//outfile << rv.substr(rv.find("["), rv.rfind("]") - rv.find("["))
//    << std::endl << "]" << std::endl;

[d].修改detection_output_layer.hpp,注释
//#include<boost/regex.hpp>
[e].
(1)编译项目,报错:
在路径.\caffe-master\include\caffe\3rdparty\下添加hungarian.h文件。
(2)编译项目,报错:
在路径.\caffe-master\src\caffe\3rdparty\下添加hungarian.cpp文件。

2.用Visual Studio 2013打开windows文件夹下的Caffe.sln,检查解决方案中的项目,重点看libcaffe和test_all是否成功地被导入。
3. 编译libcaffe
(1) 方法是右键libcaffe,选择设为启动项目。
(2)右键libcaffe解决方案->libcaffe属性管理器

配置属性–>常规–>活动(Release)/(Debug) 活动(x64)

扫描二维码关注公众号,回复: 3762033 查看本文章

(3)开始编译libcaffe

生成–>生成解决方案

[说明]查看战利品libcaffe
caffe_master文件夹下应该生成了一个名为Build的文件夹,以后编译的成功和运行需要的文件都会存放在
…\build\X64\Relase (Release版本)
…\Build\x64\Debug (Debug版本)
下面,至此,说明’libcaffe’已经成功地编译完啦。

4.编译caffe
(1) 方法是右键caffe,选择设为启动项目。

(2)右键caffe解决方案->caffe属性管理器
配置属性–>常规–>活动(Release)/(Debug) 活动(x64)

(3)开始编译caffe
生成–>生成解决方案

报错,同上:没有找到"object"解决方案

[说明]查看战利品caffe
在目录
.\Build\x64\Release
.\Build\x64\Debug
下生成caffe.exe文件

5.进入解决方案下的classification项目的ssd_detect.cpp文件中,去除注释,修改相应的模型文件和网络结构定义文件的路径。

6.环境变量配置:

环境变量中新建名为PythonPath,值为  E:\Caffe\caffe-ssd-microsoft\Build\x64\Release\pycaffe

python
>import caffe
[错误]
    ImportError: No module named google.protobuf.internal
//说明需要安装一些东西:
C:\windows\system32>conda install protobuf 
C:\windows\system32>pip install protobuf 
C:\windows\system32>esay_install protobuf


SSD移植到Windows下C++

1.新建VS工程,空项目(修改成Release x64工程)

在这里插入图片描述
在这里插入图片描述

2.环境变量

E:\Caffe\NugetPackages\OpenCV.2.4.10\build\native\bin\x64\v120\Release
E:\Caffe\caffe-ssd-microsoft\Build\x64\Release\pycaffe\caffe
新建PythonPath
D:\Caffe\caffe-ssd-microsoft\Build\x64\Release\pycaffe

Path添加
D:\Caffe\caffe-ssd-microsoft\Build\x64\Release
D:\Caffe\NugetPackages\OpenCV.2.4.10\build\native\bin\x64\v120\Release

3.下面进行VS的配置

在这里插入图片描述

(1)包含目录(头文件)

C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v8.0\include
E:\Caffe\cuda\include 
E:\Caffe\caffe-ssd-microsoft\caffe-ssd-microsoft\include
E:\Caffe\caffe-ssd-microsoft\include
E:\Caffe\NugetPackages\boost.1.59.0.0\lib\native\include
E:\Caffe\NugetPackages\glog.0.3.3.0\build\native\include
E:\Caffe\NugetPackages\gflags.2.1.2.1\build\native\include
E:\Caffe\NugetPackages\protobuf-v120.2.6.1\build\native\include
E:\Caffe\NugetPackages\OpenBLAS.0.2.14.1\lib\native\include

E:\Caffe\NugetPackages\OpenCV.2.4.10\build\native\include
E:\Caffe\NugetPackages\OpenCV.2.4.10\build\native\include\opencv2          //  opencv的包含目录
E:\Caffe\NugetPackages\OpenCV.2.4.10\build\native\include\opencv

(2)库目录(各种lib)

C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v8.0\lib\x64
E:\Caffe\caffe-ssd-microsoft\Build\x64\Release 
E:\Caffe\NugetPackages\boost_regex-vc120.1.59.0.0\lib\native\address-model-64\lib
E:\Caffe\NugetPackages\boost_chrono-vc120.1.59.0.0\lib\native\address-model-64\lib
E:\Caffe\NugetPackages\boost_thread-vc120.1.59.0.0\lib\native\address-model-64\lib
E:\Caffe\NugetPackages\boost_filesystem-vc120.1.59.0.0\lib\native\address-model-64\lib
E:\Caffe\NugetPackages\boost_system-vc120.1.59.0.0\lib\native\address-model-64\lib
E:\Caffe\NugetPackages\boost_date_time-vc120.1.59.0.0\lib\native\address-model-64\lib
E:\Caffe\NugetPackages\glog.0.3.3.0\build\native\lib\x64\v120\Release\dynamic
E:\Caffe\NugetPackages\gflags.2.1.2.1\build\native\x64\v120\static\Lib
E:\Caffe\NugetPackages\hdf5-v120-complete.1.8.15.2\lib\native\lib\x64
E:\Caffe\NugetPackages\lmdb-v120-clean.0.9.14.0\lib\native\lib\x64 
E:\Caffe\NugetPackages\LevelDB-vc120.1.2.0.0\build\native\lib\x64\v120\Release 
E:\Caffe\NugetPackages\OpenBLAS.0.2.14.1\lib\native\lib\x64
E:\Caffe\NugetPackages\protobuf-v120.2.6.1\build\native\lib\x64\v120\Release
E:\Caffe\NugetPackages\OpenCV.2.4.10\build\native\lib\x64\v120\Release 

(3)链接器 ------ tree /f > 1.txt

libboost_date_time-vc120-mt-1_59.lib
libboost_filesystem-vc120-mt-1_59.lib
libboost_system-vc120-mt-1_59.lib
libglog.lib
libcaffe.lib
gflags.lib
gflags_nothreads.lib
hdf5.lib
hdf5_hl.lib
libprotobuf.lib
libopenblas.dll.a
Shlwapi.lib
LevelDb.lib
lmdb.lib
cublas.lib
cuda.lib
curand.lib
cudart.lib
cudnn.lib
    opencv_calib3d2410.lib
    opencv_contrib2410.lib
    opencv_core2410.lib
    opencv_features2d2410.lib
    opencv_flann2410.lib
    opencv_gpu2410.lib
    opencv_highgui2410.lib
    opencv_imgproc2410.lib
    opencv_legacy2410.lib
    opencv_ml2410.lib
    opencv_nonfree2410.lib
    opencv_objdetect2410.lib
    opencv_ocl2410.lib
    opencv_photo2410.lib
    opencv_stitching2410.lib
    opencv_superres2410.lib
    opencv_ts2410.lib
    opencv_video2410.lib
    opencv_videostab2410.lib

在这里插入图片描述

(4)预处理器

NDEBUG
_SCL_SECURE_NO_WARNINGS
USE_OPENCV
USE_LEVELDB
USE_LMDB
WITH_PYTHON_LAYER
BOOST_PYTHON_STATIC_LIB
USE_CUDNN
_UNICODE
UNICODE
_CRT_SECURE_NO_WARNINGS


[1]将需要修改deploy.prototxt,caffemodel,2.mp4文件拷贝到新建的项目下
[2]###修改deploy.prototxt###

4.测试代码

ssd_detection.cpp代码

#include"head.h"
#include <caffe/caffe.hpp>
#ifdef USE_OPENCV
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#endif  // USE_OPENCV
#include <algorithm>
#include <iomanip>
#include <iosfwd>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include <stdlib.h>
#include <iostream>

#ifdef USE_OPENCV
using namespace caffe;  // NOLINT(build/namespaces)
using namespace cv;


class Detector {
public:
Detector(const string& model_file,
const string& weights_file,
const string& mean_file,
const string& mean_value);

std::vector<vector<float> > Detect(const cv::Mat& img);

private:
void SetMean(const string& mean_file, const string& mean_value);

void WrapInputLayer(std::vector<cv::Mat>* input_channels);

void Preprocess(const cv::Mat& img,
std::vector<cv::Mat>* input_channels);

private:
shared_ptr<Net<float> > net_;
cv::Size input_geometry_;
int num_channels_;
cv::Mat mean_;
};

Detector::Detector(const string& model_file,
const string& weights_file,
const string& mean_file,
const string& mean_value) {
#ifdef CPU_ONLY
Caffe::set_mode(Caffe::CPU);
#else
Caffe::set_mode(Caffe::GPU);
#endif

/* Load the network. */
net_.reset(new Net<float>(model_file, TEST));
net_->CopyTrainedLayersFrom(weights_file);

CHECK_EQ(net_->num_inputs(), 1) << "Network should have exactly one input.";
CHECK_EQ(net_->num_outputs(), 1) << "Network should have exactly one output.";

Blob<float>* input_layer = net_->input_blobs()[0];
num_channels_ = input_layer->channels();
CHECK(num_channels_ == 3 || num_channels_ == 1)
<< "Input layer should have 1 or 3 channels.";
input_geometry_ = cv::Size(input_layer->width(), input_layer->height());

/* Load the binaryproto mean file. */
SetMean(mean_file, mean_value);
}

std::vector<vector<float> > Detector::Detect(const cv::Mat& img) {
Blob<float>* input_layer = net_->input_blobs()[0];
input_layer->Reshape(1, num_channels_,
input_geometry_.height, input_geometry_.width);
/* Forward dimension change to all layers. */
net_->Reshape();

std::vector<cv::Mat> input_channels;
WrapInputLayer(&input_channels);

Preprocess(img, &input_channels);

net_->Forward();

/* Copy the output layer to a std::vector */
Blob<float>* result_blob = net_->output_blobs()[0];
const float* result = result_blob->cpu_data();
const int num_det = result_blob->height();
vector<vector<float> > detections;
for (int k = 0; k < num_det; ++k) {
if (result[0] == -1) {
// Skip invalid detection.
result += 7;
continue;
}
vector<float> detection(result, result + 7);
detections.push_back(detection);
result += 7;
}
return detections;
}

/* Load the mean file in binaryproto format. */
void Detector::SetMean(const string& mean_file, const string& mean_value) {
cv::Scalar channel_mean;
if (!mean_file.empty()) {
CHECK(mean_value.empty()) <<
"Cannot specify mean_file and mean_value at the same time";
BlobProto blob_proto;
ReadProtoFromBinaryFileOrDie(mean_file.c_str(), &blob_proto);

/* Convert from BlobProto to Blob<float> */
Blob<float> mean_blob;
mean_blob.FromProto(blob_proto);
CHECK_EQ(mean_blob.channels(), num_channels_)
<< "Number of channels of mean file doesn't match input layer.";

/* The format of the mean file is planar 32-bit float BGR or grayscale. */
std::vector<cv::Mat> channels;
float* data = mean_blob.mutable_cpu_data();
for (int i = 0; i < num_channels_; ++i) {
/* Extract an individual channel. */
cv::Mat channel(mean_blob.height(), mean_blob.width(), CV_32FC1, data);
channels.push_back(channel);
data += mean_blob.height() * mean_blob.width();
}

/* Merge the separate channels into a single image. */
cv::Mat mean;
cv::merge(channels, mean);

/* Compute the global mean pixel value and create a mean image
* filled with this value. */
channel_mean = cv::mean(mean);
mean_ = cv::Mat(input_geometry_, mean.type(), channel_mean);
}
if (!mean_value.empty()) {
CHECK(mean_file.empty()) <<
"Cannot specify mean_file and mean_value at the same time";
stringstream ss(mean_value);
vector<float> values;
string item;
while (getline(ss, item, ',')) {
float value = std::atof(item.c_str());
values.push_back(value);
}
CHECK(values.size() == 1 || values.size() == num_channels_) <<
"Specify either 1 mean_value or as many as channels: " << num_channels_;

std::vector<cv::Mat> channels;
for (int i = 0; i < num_channels_; ++i) {
/* Extract an individual channel. */
cv::Mat channel(input_geometry_.height, input_geometry_.width, CV_32FC1,
cv::Scalar(values[i]));
channels.push_back(channel);
}
cv::merge(channels, mean_);
}
}

void Detector::WrapInputLayer(std::vector<cv::Mat>* input_channels) {
Blob<float>* input_layer = net_->input_blobs()[0];

int width = input_layer->width();
int height = input_layer->height();
float* input_data = input_layer->mutable_cpu_data();
for (int i = 0; i < input_layer->channels(); ++i)
{
cv::Mat channel(height, width, CV_32FC1, input_data);
input_channels->push_back(channel);
input_data += width * height;
}
}

void Detector::Preprocess(const cv::Mat& img,
std::vector<cv::Mat>* input_channels) {
/* Convert the input image to the input image format of the network. */
cv::Mat sample;
if (img.channels() == 3 && num_channels_ == 1)
cv::cvtColor(img, sample, cv::COLOR_BGR2GRAY);
else if (img.channels() == 4 && num_channels_ == 1)
cv::cvtColor(img, sample, cv::COLOR_BGRA2GRAY);
else if (img.channels() == 4 && num_channels_ == 3)
cv::cvtColor(img, sample, cv::COLOR_BGRA2BGR);
else if (img.channels() == 1 && num_channels_ == 3)
cv::cvtColor(img, sample, cv::COLOR_GRAY2BGR);
else
sample = img;

cv::Mat sample_resized;
if (sample.size() != input_geometry_)
cv::resize(sample, sample_resized, input_geometry_);
else
sample_resized = sample;

cv::Mat sample_float;
if (num_channels_ == 3)
sample_resized.convertTo(sample_float, CV_32FC3);
else
sample_resized.convertTo(sample_float, CV_32FC1);

cv::Mat sample_normalized;
cv::subtract(sample_float, mean_, sample_normalized);

/* This operation will write the separate BGR planes directly to the
* input layer of the network because it is wrapped by the cv::Mat
* objects in input_channels. */
cv::split(sample_normalized, *input_channels);

CHECK(reinterpret_cast<float*>(input_channels->at(0).data)
== net_->input_blobs()[0]->cpu_data())
<< "Input channels are not wrapping the input layer of the network.";
}

DEFINE_string(mean_file, "",
"The mean file used to subtract from the input image.");
DEFINE_string(mean_value, "104,117,123",
"If specified, can be one value or can be same as image channels"
" - would subtract from the corresponding channel). Separated by ','."
"Either mean_file or mean_value should be provided, not both.");
DEFINE_string(file_type, "image",
"The file type in the list_file. Currently support image and video.");
DEFINE_string(out_file, "",
"If provided, store the detection results in the out_file.");
DEFINE_double(confidence_threshold, 0.01,
"Only store detections with score higher than the threshold.");

/*********************************************************//*********************************************************/

//string num2label(int num)
//{
//string labels[25]{"", "airplane", "bicycle", "bird", "boat", "bottle", "bus", "car",
//"cat", "chair", "cow", "table", "dog", "horse", "motobike",
//"person", "pottedplant", "sheep", "sofa", "train", "tvmonitor"};
//return labels[num];
//}

string num2label(int num)
{
string labels[3]{"", "target","person"};
//string labels[2]{"", "car"};
return labels[num];
}
cv::Point pt1;
cv::Point pt2;

/*********************************************************//*********************************************************/
/*********************************************************//*********************************************************/

int main(int argc, char** argv)
{
::google::InitGoogleLogging(argv[0]);
// Print output to stderr (while still logging)
FLAGS_alsologtostderr = 1;

#ifndef GFLAGS_GFLAGS_H_
namespace gflags = google;
#endif


const string& model_file("deploy.prototxt");  //prototxt文件
const string& weights_file("VGG_Person_SSD_300x300_iter_12000.caffemodel");;  //caffemodel文件


const string& mean_file = FLAGS_mean_file;
const string& mean_value = FLAGS_mean_value;
const float confidence_threshold = FLAGS_confidence_threshold;

// 加载网络
Detector detector(model_file, weights_file, mean_file, mean_value);


//////////
cv::VideoCapture cap("DL.avi");

if (!cap.isOpened())
{
LOG(FATAL) << "Failed to open video: ";
}
cv::Mat img;
int frame_count = 0;

//
double fps;
char string[10];  // 用于存放帧率的字符串
cvNamedWindow("SSD", 0);  //0表示窗口大小可调
double t = 0;
//
while (true)
{
//求FPS
t = (double)cv::getTickCount();
bool success = cap.read(img);
if (!success)
{
LOG(INFO) << "Process " << frame_count << " frames from ";
break;
}
CHECK(!img.empty()) << "Error when read frame";

if (cv::waitKey(1) == 30){ break; }
//
std::vector<vector<float> > detections = detector.Detect(img);

/* Print the detection results. */
for (int i = 0; i < detections.size(); ++i)
{
const vector<float>& d = detections[i];
// Detection format: d[image_id, label, score, xmin, ymin, xmax, ymax].
CHECK_EQ(d.size(), 7);
const float score = d[2];

if (score >= 0.5)  //【置信度】
{

int posx = static_cast<int>(d[3] * img.cols);
int posy = static_cast<int>(d[4] * img.rows);
int posw = static_cast<int>(d[5] * img.cols) - posx;
int posh = static_cast<int>(d[6] * img.rows) - posy;
cv::Rect pos(posx, posy, posw, posh);

cv::rectangle(img, pos, cv::Scalar(0, static_cast<int>(d[1])*120, 255),2);  //细、橙色框

char buffer[50];
_gcvt(score, 2, buffer);
std::string words = std::string(buffer);
words = num2label(static_cast<int>(d[1])) + " : " + words;
cv::putText(img, words, cv::Point(posx, posy), CV_FONT_HERSHEY_COMPLEX, 1, cv::Scalar(0, static_cast<int>(d[1]) * 120, 255), 2);
}
}

//FPS
t = ((double)cv::getTickCount() - t) / cv::getTickFrequency();
fps = 1.0 / t;
sprintf(string, "%.2f", fps);      // 帧率保留两位小数
std::string fpsString("FPS:");
fpsString += string;                    // 在"FPS:"后加入帧率数值字符串
// 将帧率信息写在输出帧上
putText(img, // 图像矩阵
fpsString,                  // string型文字内容
cv::Point(5, 50),           // 文字坐标,以左下角为原点
cv::FONT_HERSHEY_SIMPLEX,   // 字体类型
2, // 字体大小
cv::Scalar(0, 0, 255), 2, 10);       // 字体颜色
std::cout << "FPS=" << fps << std::endl;

cv::imshow("SSD", img);
++frame_count;
}
//////////
if (cap.isOpened()) //释放相机cap
{
cap.release();
}

return 0;
}
#else
int main(int argc, char** argv) {
LOG(FATAL) << "This example requires OpenCV; compile with USE_OPENCV.";
}
#endif  // USE_OPENCV

head.h文件

#ifndef CAFFE_REG_H  
#define CAFFE_REG_H  
#include "caffe/common.hpp"    
#include "caffe/layers/input_layer.hpp"    
#include "caffe/layers/inner_product_layer.hpp"    
#include "caffe/layers/dropout_layer.hpp"    
#include "caffe/layers/conv_layer.hpp"    
#include "caffe/layers/relu_layer.hpp"    
#include "caffe/layers/pooling_layer.hpp"    
#include "caffe/layers/lrn_layer.hpp"    
#include "caffe/layers/softmax_layer.hpp"    
#include "caffe/layers/normalize_layer.hpp"  
#include "caffe/layers/permute_layer.hpp"  
#include "caffe/layers/flatten_layer.hpp"  
#include "caffe/layers/prior_box_layer.hpp"  
#include "caffe/layers/concat_layer.hpp"  
#include "caffe/layers/reshape_layer.hpp"  
#include "caffe/layers/softmax_layer.hpp"  
#include "caffe/layers/detection_output_layer.hpp"  
namespace caffe
{
extern INSTANTIATE_CLASS(InputLayer);
extern INSTANTIATE_CLASS(ConvolutionLayer);
REGISTER_LAYER_CLASS(Convolution);
extern INSTANTIATE_CLASS(InnerProductLayer);
extern INSTANTIATE_CLASS(DropoutLayer);
extern INSTANTIATE_CLASS(ReLULayer);
REGISTER_LAYER_CLASS(ReLU);
extern INSTANTIATE_CLASS(PoolingLayer);
REGISTER_LAYER_CLASS(Pooling);
extern INSTANTIATE_CLASS(LRNLayer);
//REGISTER_LAYER_CLASS(LRN);  
extern INSTANTIATE_CLASS(SoftmaxLayer);
//REGISTER_LAYER_CLASS(Softmax);  
extern INSTANTIATE_CLASS(NormalizeLayer);
//REGISTER_LAYER_CLASS(Normalize);  
extern INSTANTIATE_CLASS(PermuteLayer);
//REGISTER_LAYER_CLASS(Permute);  
extern INSTANTIATE_CLASS(FlattenLayer);
extern INSTANTIATE_CLASS(PriorBoxLayer);
extern INSTANTIATE_CLASS(ConcatLayer);
extern INSTANTIATE_CLASS(ReshapeLayer);
extern INSTANTIATE_CLASS(SoftmaxLayer);
REGISTER_LAYER_CLASS(Softmax);
extern INSTANTIATE_CLASS(DetectionOutputLayer);
}
#endif  

在这里插入图片描述

若遇到如下错误 http://blog.csdn.net/guoyk1990/article/details/63250534?locationNum=8&fps=1
WARNING: Logging before InitGoogleLogging() is written to STDERR
F0317 21:30:50.978056 19544 layer_factory.hpp:69] Check failed: registry.count(type) == 0 (1 vs. 0) Layer type Convolution already registered.
*** Check failure stack trace: ***

将layer_factory.hpp中对应的
CHECK_EQ(registry.count(type), 0)
<< “Layer type " << type << " already registered.”;

这两行代码注释掉。

猜你喜欢

转载自blog.csdn.net/weixin_36750623/article/details/83536766
今日推荐