分享

OpenCV环境下CUDA编程示例

 oskycar 2014-11-27

在CUDA平台上对图像算法进行并行加速是目前并行计算方面比较简单易行的一种方式,而同时利用OpenCV提供的一些库函数的话,那么事情将会变得更加easy。以下是我个人采用的一种模板,这个模板是从OpenCV里的算法CUDA源码挖掘出来的,我感觉这个用起来比较傲方便,所以经常采用。首先大牛们写的源码都很鲁棒,考虑的比较全面(如大部分算法将1,3,4通道的图像同时搞定),感觉还有一个比较神奇的地方在于CPU端GpuMat和GPU端PtrStepSzb的转换,让我欲罢不能,一个不太理想的地方在于第一帧的初始化时间比较长,应该是CPU到GPU的数据传输。代码中有考虑流,但貌似没有使用。

我使用的是赵开勇的CUDA_VS_Wizard,主函数还是用的cu文件。以下代码是对Vibe背景建模算法的并行,背景建模算法是目前接触到易于并行的一类,如GMM等,而且加速效果不错,因为一个线程执行的数据就是对应一个像素点。

代码如下:

sample.cu

  1. <span style="font-size:14px;">/******************************************************************** 
  2. *  sample.cu 
  3. *  This is a example of the CUDA program. 
  4. *********************************************************************/  
  5.   
  6. #include <stdio.h>  
  7. #include <stdlib.h>  
  8. #include <cutil_inline.h>  
  9. #include <iostream>  
  10. #include <string>  
  11. #include "opencv2/core/core.hpp"  
  12. #include "opencv2/gpu/gpu.hpp"  
  13. #include "opencv2/highgui/highgui.hpp"  
  14. #include "Vibe_M_kernel.cu"  
  15. #include "Vibe_M.h"  
  16. using namespace std;  
  17. using namespace cv;  
  18. using namespace cv::gpu;  
  19.   
  20. enum Method  
  21. {  
  22.     FGD_STAT,  
  23.     MOG,  
  24.     MOG2,  
  25.     VIBE,  
  26.     GMG  
  27. };  
  28.   
  29. int main(int argc, const char** argv)  
  30. {  
  31.     cv::CommandLineParser cmd(argc, argv,  
  32.         "{ c | camera | flase       | use camera }"  
  33.         "{ f | file   | 768x576.avi | input video file }"  
  34.         "{ m | method | vibe         | method (fgd, mog, mog2, vibe, gmg) }"  
  35.         "{ h | help   | false       | print help message }");  
  36.   
  37.     if (cmd.get<bool>("help"))  
  38.     {  
  39.         cout << "Usage : bgfg_segm [options]" << endl;  
  40.         cout << "Avaible options:" << endl;  
  41.         cmd.printParams();  
  42.         return 0;  
  43.     }  
  44.     bool useCamera = cmd.get<bool>("camera");  
  45.     string file = cmd.get<string>("file");  
  46.     string method = cmd.get<string>("method");  
  47.     if (method != "fgd" && method != "mog" && method != "mog2" && method != "vibe" && method != "gmg")  
  48.     {  
  49.         cerr << "Incorrect method" << endl;  
  50.         return -1;  
  51.     }  
  52.     Method m = method == "fgd" ? FGD_STAT : method == "mog" ? MOG : method == "mog2" ? MOG2 : method == "vibe" ? VIBE : GMG;  
  53.   
  54.     VideoCapture cap;  
  55.     if (useCamera)  
  56.         cap.open(0);  
  57.     else  
  58.         cap.open(file);  
  59.     if (!cap.isOpened())  
  60.     {  
  61.         cerr << "can not open camera or video file" << endl;  
  62.         return -1;  
  63.     }  
  64.     Mat origin, frame;  
  65.     cap >> origin;  
  66.     cvtColor(origin,frame,CV_BGR2GRAY);  
  67.     GpuMat d_frame(frame);  
  68.     Vibe_M vibe;  
  69.     GpuMat d_fgmask;  
  70.   
  71.     Mat fgmask;  
  72.     Mat fgimg;  
  73.     Mat bgimg;  
  74.   
  75.     switch (m)  
  76.     {  
  77.     case VIBE:  
  78.         vibe.initialize(d_frame);  
  79.         break;  
  80.   
  81.     }  
  82.   
  83.     namedWindow("image", WINDOW_NORMAL);  
  84.     namedWindow("foreground mask", WINDOW_NORMAL);  
  85.   
  86.     for(;;)  
  87.     {  
  88.         cap >> origin;  
  89.         if (origin.empty())  
  90.             break;  
  91.   
  92.         cvtColor(origin,frame,CV_BGR2GRAY);  
  93.   
  94.         d_frame.upload(frame);  
  95.   
  96.         //update the model  
  97.         switch (m)  
  98.         {  
  99.         case VIBE:  
  100.             vibe(d_frame, d_fgmask);  
  101.             break;  
  102.         }  
  103.         d_fgmask.download(fgmask);  
  104.   
  105.         imshow("image", frame);  
  106.         imshow("foreground mask", fgmask);  
  107.         int key = waitKey(30);  
  108.         if (key == 27)  
  109.             break;  
  110.         else if(key == ' ')  
  111.         {  
  112.             cvWaitKey(0);  
  113.         }  
  114.     }  
  115.     exit(0);  
  116. }  
  117.   
  118. </span>  
Vibe_M.cpp

  1. <span style="font-size:14px;">#include "Vibe_M.h"  
  2.   
  3. namespace cv { namespace gpu { namespace device  
  4. {  
  5.     namespace vibe_m  
  6.     {  
  7.         void loadConstants(int nbSamples, int reqMatches, int radius, int subsamplingFactor);  
  8.   
  9.         void init_gpu(PtrStepSzb frame, int cn, PtrStepSzb samples, PtrStepSz<unsigned int> randStates, cudaStream_t stream);  
  10.   
  11.         void update_gpu(PtrStepSzb frame, int cn, PtrStepSzb fgmask, PtrStepSzb samples, PtrStepSz<unsigned int> randStates, cudaStream_t stream);  
  12.     }  
  13. }}}  
  14.   
  15. namespace  
  16. {  
  17.     const int defaultNbSamples = 20;  
  18.     const int defaultReqMatches = 2;  
  19.     const int defaultRadius = 20;  
  20.     const int defaultSubsamplingFactor = 16;  
  21. }  
  22.   
  23. Vibe_M::Vibe_M(unsigned long rngSeed) :  
  24. frameSize_(0, 0), rngSeed_(rngSeed)  
  25. {  
  26.     nbSamples = defaultNbSamples;  
  27.     reqMatches = defaultReqMatches;  
  28.     radius = defaultRadius;  
  29.     subsamplingFactor = defaultSubsamplingFactor;  
  30. }  
  31.   
  32. void Vibe_M::initialize(const GpuMat& firstFrame, Stream& s)  
  33. {  
  34.     using namespace cv::gpu::device::vibe_m;  
  35.   
  36.     CV_Assert(firstFrame.type() == CV_8UC1 || firstFrame.type() == CV_8UC3 || firstFrame.type() == CV_8UC4);  
  37.   
  38.     //cudaStream_t stream = StreamAccessor::getStream(s);  
  39.   
  40.     loadConstants(nbSamples, reqMatches, radius, subsamplingFactor);  
  41.   
  42.     frameSize_ = firstFrame.size();  
  43.   
  44.     if (randStates_.size() != frameSize_)  
  45.     {  
  46.         cv::RNG rng(rngSeed_);  
  47.         cv::Mat h_randStates(frameSize_, CV_8UC4);  
  48.         rng.fill(h_randStates, cv::RNG::UNIFORM, 0, 255);  
  49.         randStates_.upload(h_randStates);  
  50.     }  
  51.   
  52.     int ch = firstFrame.channels();  
  53.     int sample_ch = ch == 1 ? 1 : 4;  
  54.   
  55.     samples_.create(nbSamples * frameSize_.height, frameSize_.width, CV_8UC(sample_ch));  
  56.   
  57.     init_gpu(firstFrame, ch, samples_, randStates_, 0);  
  58. }  
  59.   
  60. void Vibe_M::operator()(const GpuMat& frame, GpuMat& fgmask, Stream& s)  
  61. {  
  62.     using namespace cv::gpu::device::vibe_m;  
  63.   
  64.     CV_Assert(frame.depth() == CV_8U);  
  65.   
  66.     int ch = frame.channels();  
  67.     int sample_ch = ch == 1 ? 1 : 4;  
  68.   
  69.     if (frame.size() != frameSize_ || sample_ch != samples_.channels())  
  70.         initialize(frame);  
  71.   
  72.     fgmask.create(frameSize_, CV_8UC1);  
  73.   
  74.     update_gpu(frame, ch, fgmask, samples_, randStates_, StreamAccessor::getStream(s));  
  75. }  
  76.   
  77. void Vibe_M::release()  
  78. {  
  79.     frameSize_ = Size(0, 0);  
  80.   
  81.     randStates_.release();  
  82.   
  83.     samples_.release();  
  84. }  
  85. </span>  
Vibe_M.h

  1. <span style="font-size:14px;">#ifndef _VIBE_M_H_  
  2. #define _VIBE_M_H_  
  3. #ifndef SKIP_INCLUDES  
  4. #include <vector>  
  5. #include <memory>  
  6. #include <iosfwd>  
  7. #endif  
  8. #include "opencv2/core/core.hpp"  
  9. #include "opencv2/core/gpumat.hpp"  
  10. #include "opencv2/gpu/gpu.hpp"  
  11. #include "opencv2/imgproc/imgproc.hpp"  
  12. #include "opencv2/objdetect/objdetect.hpp"  
  13. #include "opencv2/features2d/features2d.hpp"  
  14. using namespace std;  
  15. using namespace cv;  
  16. using namespace cv::gpu;  
  17.   
  18. class  Vibe_M  
  19. {  
  20. public:  
  21.     //! the default constructor  
  22.     explicit Vibe_M(unsigned long rngSeed = 1234567);  
  23.     //! re-initiaization method  
  24.     void initialize(const GpuMat& firstFrame, Stream& stream = Stream::Null());  
  25.     //! the update operator  
  26.     void operator()(const GpuMat& frame, GpuMat& fgmask, Stream& stream = Stream::Null());  
  27.     //! releases all inner buffers  
  28.     void release();  
  29.     int nbSamples;         // number of samples per pixel  
  30.     int reqMatches;        // #_min  
  31.     int radius;            // R  
  32.     int subsamplingFactor; // amount of random subsampling  
  33.   
  34. private:  
  35.     Size frameSize_;  
  36.     unsigned long rngSeed_;  
  37.     GpuMat randStates_;  
  38.     GpuMat samples_;  
  39. };  
  40.   
  41. #endif</span>  
Vibe_M.cu

  1. <span style="font-size:14px;">#include "Vibe_M.h"  
  2. #include "opencv2/gpu/stream_accessor.hpp"  
  3.   
  4. namespace cv { namespace gpu { namespace device  
  5. {  
  6.     namespace vibe_m  
  7.     {  
  8.         void loadConstants(int nbSamples, int reqMatches, int radius, int subsamplingFactor);  
  9.   
  10.         void init_gpu(PtrStepSzb frame, int cn, PtrStepSzb samples, PtrStepSz<unsigned int> randStates, cudaStream_t stream);  
  11.   
  12.         void update_gpu(PtrStepSzb frame, int cn, PtrStepSzb fgmask, PtrStepSzb samples, PtrStepSz<unsigned int> randStates, cudaStream_t stream);  
  13.     }  
  14. }}}  
  15.   
  16. namespace  
  17. {  
  18.     const int defaultNbSamples = 20;  
  19.     const int defaultReqMatches = 2;  
  20.     const int defaultRadius = 20;  
  21.     const int defaultSubsamplingFactor = 16;  
  22. }  
  23.   
  24. Vibe_M::Vibe_M(unsigned long rngSeed) :  
  25. frameSize_(0, 0), rngSeed_(rngSeed)  
  26. {  
  27.     nbSamples = defaultNbSamples;  
  28.     reqMatches = defaultReqMatches;  
  29.     radius = defaultRadius;  
  30.     subsamplingFactor = defaultSubsamplingFactor;  
  31. }  
  32.   
  33. void Vibe_M::initialize(const GpuMat& firstFrame, Stream& s)  
  34. {  
  35.     using namespace cv::gpu::device::vibe_m;  
  36.   
  37.     CV_Assert(firstFrame.type() == CV_8UC1 || firstFrame.type() == CV_8UC3 || firstFrame.type() == CV_8UC4);  
  38.   
  39.     cudaStream_t stream = cv::gpu::StreamAccessor::getStream(s);  
  40.   
  41.     loadConstants(nbSamples, reqMatches, radius, subsamplingFactor);  
  42.   
  43.     frameSize_ = firstFrame.size();  
  44.   
  45.     if (randStates_.size() != frameSize_)  
  46.     {  
  47.         cv::RNG rng(rngSeed_);  
  48.         cv::Mat h_randStates(frameSize_, CV_8UC4);  
  49.         rng.fill(h_randStates, cv::RNG::UNIFORM, 0, 255);  
  50.         randStates_.upload(h_randStates);  
  51.     }  
  52.   
  53.     int ch = firstFrame.channels();  
  54.     int sample_ch = ch == 1 ? 1 : 4;  
  55.   
  56.     samples_.create(nbSamples * frameSize_.height, frameSize_.width, CV_8UC(sample_ch));  
  57.   
  58.     init_gpu(firstFrame, ch, samples_, randStates_, stream);  
  59. }  
  60.   
  61. void Vibe_M::operator()(const GpuMat& frame, GpuMat& fgmask, Stream& s)  
  62. {  
  63.     using namespace cv::gpu::device::vibe_m;  
  64.   
  65.     CV_Assert(frame.depth() == CV_8U);  
  66.   
  67.     int ch = frame.channels();  
  68.     int sample_ch = ch == 1 ? 1 : 4;  
  69.   
  70.     if (frame.size() != frameSize_ || sample_ch != samples_.channels())  
  71.         initialize(frame);  
  72.   
  73.     fgmask.create(frameSize_, CV_8UC1);  
  74.   
  75.     update_gpu(frame, ch, fgmask, samples_, randStates_, cv::gpu::StreamAccessor::getStream(s));  
  76. }  
  77.   
  78. void Vibe_M::release()  
  79. {  
  80.     frameSize_ = Size(0, 0);  
  81.   
  82.     randStates_.release();  
  83.   
  84.     samples_.release();  
  85. }  
  86. </span>  
Vibe_M_kernel.cu

  1. <span style="font-size:14px;">#include "opencv2/gpu/device/common.hpp"  
  2. #include "opencv2/gpu/device/vec_math.hpp"  
  3.   
  4. namespace cv { namespace gpu { namespace device  
  5. {  
  6.     namespace vibe_m  
  7.     {  
  8.         __constant__ int c_nbSamples;  
  9.         __constant__ int c_reqMatches;  
  10.         __constant__ int c_radius;  
  11.         __constant__ int c_subsamplingFactor;  
  12.   
  13.         void loadConstants(int nbSamples, int reqMatches, int radius, int subsamplingFactor)  
  14.         {  
  15.             cudaSafeCall( cudaMemcpyToSymbol(c_nbSamples, &nbSamples, sizeof(int)) );  
  16.             cudaSafeCall( cudaMemcpyToSymbol(c_reqMatches, &reqMatches, sizeof(int)) );  
  17.             cudaSafeCall( cudaMemcpyToSymbol(c_radius, &radius, sizeof(int)) );                          
  18.             cudaSafeCall( cudaMemcpyToSymbol(c_subsamplingFactor, &subsamplingFactor, sizeof(int)) );  
  19.         }  
  20.   
  21.         __device__ __forceinline__ uint nextRand(uint& state)  
  22.         {  
  23.             //const unsigned int CV_RNG_COEFF = 4164903690U;//已经定义  
  24.             state = state * CV_RNG_COEFF + (state >> 16);  
  25.             return state;  
  26.         }  
  27.   
  28.         __constant__ int c_xoff[9] = {-1,  0,  1, -1, 1, -1, 0, 1, 0};  
  29.         __constant__ int c_yoff[9] = {-1, -1, -1,  0, 0,  1, 1, 1, 0};  
  30.   
  31.         __device__ __forceinline__ int2 chooseRandomNeighbor(int x, int y, uint& randState, int count = 8)  
  32.         {  
  33.             int idx = nextRand(randState) % count;  
  34.   
  35.             return make_int2(x + c_xoff[idx], y + c_yoff[idx]);  
  36.         }  
  37.   
  38.         __device__ __forceinline__ uchar cvt(uchar val)  
  39.         {  
  40.             return val;  
  41.         }  
  42.         __device__ __forceinline__ uchar4 cvt(const uchar3& val)  
  43.         {  
  44.             return make_uchar4(val.x, val.y, val.z, 0);  
  45.         }  
  46.         __device__ __forceinline__ uchar4 cvt(const uchar4& val)  
  47.         {  
  48.             return val;  
  49.         }  
  50.   
  51.         template <typename SrcT, typename SampleT>  
  52.         __global__ void init(const PtrStepSz<SrcT> frame, PtrStep<SampleT> samples, PtrStep<uint> randStates)  
  53.         {  
  54.             const int x = blockIdx.x * blockDim.x + threadIdx.x;  
  55.             const int y = blockIdx.y * blockDim.y + threadIdx.y;  
  56.   
  57.             if (x >= frame.cols || y >= frame.rows)  
  58.                 return;  
  59.   
  60.             uint localState = randStates(y, x);  
  61.   
  62.             for (int k = 0; k < c_nbSamples; ++k)  
  63.             {  
  64.                 int2 np = chooseRandomNeighbor(x, y, localState, 9);  
  65.   
  66.                 np.x = ::max(0, ::min(np.x, frame.cols - 1));  
  67.                 np.y = ::max(0, ::min(np.y, frame.rows - 1));  
  68.   
  69.                 SrcT pix = frame(np.y, np.x);  
  70.   
  71.                 samples(k * frame.rows + y, x) = cvt(pix);  
  72.             }  
  73.   
  74.             randStates(y, x) = localState;  
  75.         }  
  76.   
  77.         template <typename SrcT, typename SampleT>  
  78.         void init_caller(PtrStepSzb frame, PtrStepSzb samples, PtrStepSz<uint> randStates, cudaStream_t stream)  
  79.         {  
  80.             dim3 block(32, 8);  
  81.             dim3 grid(divUp(frame.cols, block.x), divUp(frame.rows, block.y));  
  82.   
  83.             cudaSafeCall( cudaFuncSetCacheConfig(init<SrcT, SampleT>, cudaFuncCachePreferL1) );  
  84.   
  85.             init<SrcT, SampleT><<<grid, block, 0, stream>>>((PtrStepSz<SrcT>) frame, (PtrStepSz<SampleT>) samples, randStates);  
  86.             cudaSafeCall( cudaGetLastError() );  
  87.   
  88.             if (stream == 0)  
  89.                 cudaSafeCall( cudaDeviceSynchronize() );  
  90.         }  
  91.   
  92.         void init_gpu(PtrStepSzb frame, int cn, PtrStepSzb samples, PtrStepSz<uint> randStates, cudaStream_t stream)  
  93.         {  
  94.             typedef void (*func_t)(PtrStepSzb frame, PtrStepSzb samples, PtrStepSz<uint> randStates, cudaStream_t stream);  
  95.             static const func_t funcs[] =  
  96.             {  
  97.                 0, init_caller<uchar, uchar>, 0, init_caller<uchar3, uchar4>, init_caller<uchar4, uchar4>  
  98.             };  
  99.   
  100.             funcs[cn](frame, samples, randStates, stream);  
  101.         }  
  102.   
  103.         __device__ __forceinline__ int calcDist(uchar a, uchar b)  
  104.         {  
  105.             return ::abs(a - b);  
  106.         }  
  107.         __device__ __forceinline__ int calcDist(const uchar3& a, const uchar4& b)  
  108.         {  
  109.             return (::abs(a.x - b.x) + ::abs(a.y - b.y) + ::abs(a.z - b.z)) / 3;  
  110.         }  
  111.         __device__ __forceinline__ int calcDist(const uchar4& a, const uchar4& b)  
  112.         {  
  113.             return (::abs(a.x - b.x) + ::abs(a.y - b.y) + ::abs(a.z - b.z)) / 3;  
  114.         }  
  115.   
  116.         template <typename SrcT, typename SampleT>  
  117.         __global__ void update(const PtrStepSz<SrcT> frame, PtrStepb fgmask, PtrStep<SampleT> samples, PtrStep<uint> randStates)  
  118.         {  
  119.             const int x = blockIdx.x * blockDim.x + threadIdx.x;  
  120.             const int y = blockIdx.y * blockDim.y + threadIdx.y;  
  121.   
  122.             if (x >= frame.cols || y >= frame.rows)  
  123.                 return;  
  124.   
  125.             uint localState = randStates(y, x);  
  126.   
  127.             SrcT imgPix = frame(y, x);  
  128.   
  129.             // comparison with the model  
  130.   
  131.             int count = 0;  
  132.             for (int k = 0; (count < c_reqMatches) && (k < c_nbSamples); ++k)  
  133.             {  
  134.                 SampleT samplePix = samples(k * frame.rows + y, x);  
  135.   
  136.                 int distance = calcDist(imgPix, samplePix);  
  137.   
  138.                 if (distance < c_radius)  
  139.                     ++count;  
  140.             }  
  141.   
  142.             // pixel classification according to reqMatches  
  143.   
  144.             fgmask(y, x) = (uchar) (-(count < c_reqMatches));//当count<2时,为前景 当计数器count>=2时,为背景  
  145.   
  146.             if (count >= c_reqMatches)  
  147.             {  
  148.                 // the pixel belongs to the background  
  149.   
  150.                 // gets a random number between 0 and subsamplingFactor-1  
  151.                 int randomNumber = nextRand(localState) % c_subsamplingFactor;  
  152.   
  153.                 // update of the current pixel model  
  154.                 if (randomNumber == 0)  
  155.                 {  
  156.                     // random subsampling  
  157.   
  158.                     int k = nextRand(localState) % c_nbSamples;  
  159.   
  160.                     samples(k * frame.rows + y, x) = cvt(imgPix);  
  161.                 }  
  162.   
  163.                 // update of a neighboring pixel model  
  164.                 randomNumber = nextRand(localState) % c_subsamplingFactor;  
  165.   
  166.                 if (randomNumber == 0)  
  167.                 {  
  168.                     // random subsampling  
  169.   
  170.                     // chooses a neighboring pixel randomly  
  171.                     int2 np = chooseRandomNeighbor(x, y, localState);  
  172.   
  173.                     np.x = ::max(0, ::min(np.x, frame.cols - 1));  
  174.                     np.y = ::max(0, ::min(np.y, frame.rows - 1));  
  175.   
  176.                     // chooses the value to be replaced randomly  
  177.                     int k = nextRand(localState) % c_nbSamples;  
  178.                     samples(k * frame.rows + np.y, np.x) = cvt(imgPix);  
  179.                 }  
  180.             }  
  181.   
  182.             randStates(y, x) = localState;  
  183.         }  
  184.   
  185.         template <typename SrcT, typename SampleT>  
  186.         void update_caller(PtrStepSzb frame, PtrStepSzb fgmask, PtrStepSzb samples, PtrStepSz<uint> randStates, cudaStream_t stream)  
  187.         {  
  188.             dim3 block(32, 8);  
  189.             dim3 grid(divUp(frame.cols, block.x), divUp(frame.rows, block.y));  
  190.   
  191.             cudaSafeCall( cudaFuncSetCacheConfig(update<SrcT, SampleT>, cudaFuncCachePreferL1) );  
  192.   
  193.             update<SrcT, SampleT><<<grid, block, 0, stream>>>((PtrStepSz<SrcT>) frame, fgmask, (PtrStepSz<SampleT>) samples, randStates);  
  194.             cudaSafeCall( cudaGetLastError() );  
  195.   
  196.             if (stream == 0)  
  197.                 cudaSafeCall( cudaDeviceSynchronize() );  
  198.         }  
  199.   
  200.         void update_gpu(PtrStepSzb frame, int cn, PtrStepSzb fgmask, PtrStepSzb samples, PtrStepSz<uint> randStates, cudaStream_t stream)  
  201.         {  
  202.             typedef void (*func_t)(PtrStepSzb frame, PtrStepSzb fgmask, PtrStepSzb samples, PtrStepSz<uint> randStates, cudaStream_t stream);  
  203.             static const func_t funcs[] =  
  204.             {  
  205.                 0, update_caller<uchar, uchar>, 0, update_caller<uchar3, uchar4>, update_caller<uchar4, uchar4>  
  206.             };  
  207.   
  208.             funcs[cn](frame, fgmask, samples, randStates, stream);  
  209.         }  
  210.     }  
  211. }}}  
  212. </span>  



    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多