opencv02-图像加载、显示、修改和保存

opencv01-图像加载、显示、修改和保存

图像加载

方法的定义:

// filename 要加载的文件名
// flags 标识,参考cv::ImreadModes
CV_EXPORTS_W Mat imread( const String& filename, int flags = IMREAD_COLOR );

方法的解释说明:

  1. 从指定文件里面加载图片,并返回
  2. 如果文件不能被读取(因为文件不存在,没有合适的权限,或无效的文件格式),返回空matrix,即Mat::data==NULL
  3. 支持的文件格式有很多:bitmaps, JPEG, TIFF, png

注意事项:

  1. 方法决定了image的类型,不是通过文件的扩展名

  2. 如果是color images,那么decode之后的images存储的通道顺序为:B G R

  3. 当使用 IMREAD_GRAYSCALE,那么the codec’s internal grayscale conversion will be used, if available

  4. 结果可能和cvtColor()不一样

  5. 在Windows系统和MacOSX上,the codecs shipped with an OpenCV image (libjpeg,libpng, libtiff, and libjasper) are used by default. So, OpenCV can always read JPEGs, PNGs,and TIFFs. On MacOSX, there is also an option to use native MacOSX image readers. But beware that currently these native image loaders give images with different pixel values because of the color management embedded into MacOSX.

  6. 在Linux,BSD flavors和其他类Unix开源的操作系统上,OpenCV寻找操作系统镜像提供的codecs,通过安装相关的packages(不能忘记开发的文件,例如:在 Debian或者Ubuntu系统中的"libjpeg-dev")来获得codec的支持,或者在CMake中打开OPENCV_BUILD_3RDPARTY_LIBS标志位

  7. 在这种情况下,在CMake中设置WITH_GDAL为 true并且加载图片设置IMREAD_LOAD_GDAL,那么为了解码image,[GDAL]驱动将会被使用,支持如下的格式:[Raster] 和 [Vector]

  8. 如果图片中嵌入了EXIF信息, 那么EXIF的方向也会被考虑,因为图像将会旋转,除非flags设置IMREAD_IGNORE_ORIENTATION 或者 传递IMREAD_UNCHANGED参数

  9. 使用 IMREAD_UNCHANGED 标志位,保持PFM格式的图片数值为浮点类型

  10. 默认情况下,图片的像素必须小于2^30,该限制可以通过系统变量OPENCV_IO_MAX_IMAGE_PIXELS来设置:

flags标识:

enum ImreadModes {
    
    
       IMREAD_UNCHANGED            = -1, //按原样加载图片(with alpha channel, otherwise it gets cropped). 忽略EXIF orientation.
       IMREAD_GRAYSCALE            = 0,  //总是转换图片为单通道的grayscale图片(codec 内转换).
       IMREAD_COLOR                = 1,  //总是转换图片为3通道的BGR彩色图片, 默认值
       IMREAD_ANYDEPTH             = 2,  //!< If set, return 16-bit/32-bit image when the input has the corresponding depth, otherwise convert it to 8-bit.
       IMREAD_ANYCOLOR             = 4,  //如果设置了,那么以任意可能的颜色格式读取图片
       IMREAD_LOAD_GDAL            = 8,  //如果设置了,那么使用gdal驱动加载图片
       IMREAD_REDUCED_GRAYSCALE_2  = 16, //总是转换图片为单通道的grayscale图片,图片size减少为1/2
       IMREAD_REDUCED_COLOR_2      = 17, //总是转换图片为3通道的BGR彩色图片,图片size减少为1/2
       IMREAD_REDUCED_GRAYSCALE_4  = 32, //总是转换图片为单通道的grayscale图片,图片size减少为1/4
       IMREAD_REDUCED_COLOR_4      = 33, //总是转换图片为3通道的BGR彩色图片,图片size减少为1/4
       IMREAD_REDUCED_GRAYSCALE_8  = 64, //总是转换图片为单通道的grayscale图片,图片size减少为1/8
       IMREAD_REDUCED_COLOR_8      = 65, // 总是转换图片为3通道的BGR彩色图片,图片size减少为1/8
       IMREAD_IGNORE_ORIENTATION   = 128 //不根据EXIF's orientation flag来旋转图片
     };

创建窗口和图像显示

方法的定义:

// filename window的标题,可以用来作为window的标识符
// flags window的标识,The supported flags are: (cv::WindowFlags)
CV_EXPORTS_W void namedWindow(const String& winname, int flags = WINDOW_AUTOSIZE);

方法的解释说明:

  1. 创建一个window可以作为images和trackbars的占位符(placeholder)
  2. 如果有一个相同的window名称已经存在了,那么该方法does nothing
  3. 可以使用cv::destroyWindow或者cv::destroyAllWindows来关闭所有的window并且de-allocate 任意相关的内存。对于一个简单的程序,不需要显式的调用这些函数,因为所有的资源和应用都会在操作系统退出的时候自动关闭

注意事项:

  1. Qt支持的额外的flags
  • WINDOW_NORMAL or WINDOW_AUTOSIZE:WINDOW_NORMAL允许重新调整window的大小,然而WINDOW_AUTOSIZE自动调整window的大小来适配显示图片的大小,不能手动调整window的大小
  • WINDOW_FREERATIO or WINDOW_KEEPRATIO:WINDOW_FREERATIO 调整图片的大小和比例无关, 然而WINDOW_KEEPRATIO保持图片的比例
  • WINDOW_GUI_NORMAL or WINDOW_GUI_EXPANDED:WINDOW_GUI_NORMAL是老旧的方式来画一个没有statusbar和toolbar的window,然而WINDOW_GUI_EXPANDED是一个新的增强的GUI
  1. 默认情况下,flags == WINDOW_AUTOSIZE | WINDOW_KEEPRATIO | WINDOW_GUI_EXPANDED

flags标识:

enum WindowFlags {
    
    
       WINDOW_NORMAL     = 0x00000000, //可以调整窗口大小
       WINDOW_AUTOSIZE   = 0x00000001, //不能调整窗口大小
       WINDOW_OPENGL     = 0x00001000, //opengl 支持的窗口,需要编译opencv的时候支持opengl

       WINDOW_FULLSCREEN = 1,          //!< change the window to fullscreen.
       WINDOW_FREERATIO  = 0x00000100, //!< the image expends as much as it can (no ratio constraint).
       WINDOW_KEEPRATIO  = 0x00000000, //!< the ratio of the image is respected.
       WINDOW_GUI_EXPANDED=0x00000000, //!< status bar and tool bar
       WINDOW_GUI_NORMAL = 0x00000010, //!< old fashious way
    };

方法的定义:

// winname window的标识符
// mat 要显示的图片
CV_EXPORTS_W void imshow(const String& winname, InputArray mat);

方法的解释说明:

  1. 函数将在指定的窗口上展示图片,如果window创建使用cv::WINDOW_AUTOSIZE,那么图片展示原始的大小,然而也仍旧受到屏幕分辨率的限制。其他情况,图片适应窗口的大小。该函数将scale这个图片, 依赖图片的depth:
  • 如果图片是8-bit unsigned,那么将原样展示
  • 如果图片是16-bit unsigned,那么像素将除以256,即取值范围 [0,255*256] 被映射到[0,255]
  • 如果图片是32-bit 或者64-bit floating-point, 那么像素值将乘以255,即取值范围 [0,1] 被映射到[0,255]
  • 32-bit integer的图片将不再处理,因为所需要的转换模棱两可
  • 转换8-bit unsigned 矩阵使用custom preprocessing specific to image’s context.
  • 如果创建了OpenGL支持的window,那么cv::imshow也支持ogl::Buffer , ogl::Texture2D and cuda::GpuMat 做为输入
  • 如果该函数调用之前没有创建window,那么将创建一个cv::WINDOW_AUTOSIZE的window
  • 如果想展示一个超过屏幕分辨率的图片,那么需要在imshow之前调用namedWindow(“”, WINDOW_NORMAL)

注意事项:

  1. 紧接着该函数应该调用cv::waitKey或者cv::pollKey,来执行GUI的housekeeping任务,
    这通常是非常有必要的为了展示给定的图片并且使得window等待鼠标和键盘事件。否则,将不会展示图片,window也可能锁住。
    例如:waitKey(0)将会无限的展示window直到任意键被按下(适合图片展示),waitKey(25)将展示一帧,并且等待大约25ms(适合一帧一帧的展示视频流),调用cv::destroyWindow来移除window。
  2. [Windows Backend Only] Ctrl+C 复制image到 clipboard.
  3. [Windows Backend Only] Ctrl+S 弹出保存图片的对话框.
#include <iostream>
#include <opencv2/highgui/highgui.hpp>


using namespace std;
using namespace cv;

/**
 * 图像的加载,修改图像和保存图像
 *
 * @return
 */
int main() {
    
    
    string filename= "D:/workspace/cpp_workspace/my-cv/data/img/lena.jpg";
    Mat img = imread(filename, ImreadModes::IMREAD_COLOR);
    if (img.data == nullptr) {
    
    
        cout << "can not load image" << endl;
        return -1;
    }

    //cout << img << endl;

    namedWindow("test", WINDOW_AUTOSIZE);
    imshow("test", img);
    waitKey();
    return 0;
}

图像修改

方法的定义:

//将图像从一个色彩空间转换为另一个色彩空间
// @param src 输入图像:8 位无符号、16 位无符号 ( CV_16UC...) 或单精度浮点数。
// @param dst 与SRC大小和深度相同的输出图像。
// @param code 代码颜色空间转换代码(请参阅 #ColorConversionCodes)。
// @param dstCn 目标图像中的dstCn通道数;如果参数为 0,则通道是从 SRC 和代码自动推导出来
// 可以参考:imgproc_color_conversions
CV_EXPORTS_W void cvtColor( InputArray src, OutputArray dst, int code, int dstCn = 0 );

方法的解释说明:

  1. 该函数将输入图像从一个颜色空间转换为另一个颜色空间。在转换的情况下 从 to-from RGB 色彩空间,应显式指定通道的顺序(RGB 或 BGR,注意:OpenCV 中的默认颜色格式通常称为 RGB,但它实际上是 BGR(字节颠倒),因此,标准(24 位)彩色图像中的第一个字节将是 8 位蓝色组件,第二个字节将为绿色,第三个字节将为红色。第四、第五和然后,第六个字节将是第二个像素(蓝色,然后是绿色,然后是红色),依此类推。
    R、G 和 B 通道值的常规范围为:
  • 0 到 255 表示 CV_8U 图像
  • 0 到 65535 表示CV_16U图像
  • 0 到 1 表示 CV_32F 图像
    对于线性变换,范围无关紧要。但在非线性的情况下转换时,应将输入 RGB 图像normalized到适当的值范围以获得正确的值结果,例如:RGB -> Luv转换,例如,如果您有32 位浮点图像直接从 8 位图像转换而来,无需任何缩放,那么它将具有 0…255 的值范围,而不是函数假定的 0…1。所以在调用cvtColor之前,你需要scale图片:
img *= 1./255;
cvtColor(img, img, COLOR_BGR2Luv);
  1. 如果将 #cvtColor 与 8 位图像一起使用,则转换将丢失一些信息.对于许多应用程序,这不会很明显,但建议在应用程序中使用 32 位图像,这样可以保证有全部颜色的范围或者在一个操作前转换一个图片然后在转换回来。

  2. 如果转化添加了 alpha 通道,则其值将设置为相应通道的最大值. 范围:CV_8U是255,CV_16U是65535,CV_32F是1。

代码示例:

#include <opencv2/opencv.hpp>
int main() {
    
    
    string filename = "D:/workspace/cpp_workspace/my-cv/data/img/lena.jpg";
    Mat srcImage = imread(filename, ImreadModes::IMREAD_COLOR);
    Mat dstImage;
    cvtColor(srcImage, dstImage, COLOR_BGR2Lab);
    imshow("原图", srcImage);
    imshow("效果图", dstImage);

    waitKey(0);
    return 0;
}

图像保存

方法的定义:

// 保存图片到指定的文件
// img 待保存的图片(一个或多个)(Mat or vector of Mat)
// params 用来设置对应图片格式的参数的,因为一般情况下这些图片格式都是经过了压缩的,这里就是设置这些压缩参数来控制图片的质量。该参数是一个vector<int>类型,里面分别存入paramId_1, paramValue_1, paramId_2, paramValue_2, ... 也就是说存入一对属性值。如果不设置该参数的话,则程序会自动根据所保存的图像格式采用一个默认的参数。参考: cv::ImwriteFlags
CV_EXPORTS_W bool imwrite( const String& filename, InputArray img,
              const std::vector<int>& params = std::vector<int>());

方法的解释说明:

  1. 该函数imwrite保存图片到指定的文件,图片的格式基于文件的扩展名(可以查看扩展名列表)。通常,仅仅8-bit无符号单通道的图片(CV_8U)或者3通道(按照’BGR’通道顺序)的图片才能被该函数保存。也有例外:

    • 使用OpenEXR编码器,仅仅32-bit浮点(CV_32F) 图片可以保存,不支持(CV_8U)

    • 使用Radiance HDR编码器,可以保存非64位浮点(CV_64F)图像,所有图像将转换为 32 位浮点数 (CV_32F)

    • 使用 JPEG 2000 编码器,可以保存 8 位无符号 (CV_8U) 和 16 位无符号 (CV_16U) 图像

    • 使用 PAM 编码器,可以保存 8 位无符号 (CV_8U) 和 16 位无符号 (CV_16U) 图像

    • 使用 PNG 编码器,可以保存 8 位无符号 (CV_8U) 和 16 位无符号 (CV_16U) 图像, 可以使用此功能保存带有 alpha 通道的 PNG 图像。为此,请创建

      • 8 位(或 16 位)4 通道图像 BGRA,其中 alpha 通道最后。完全透明的像素
      • 2 应将 Alpha 设置为 0,完全不透明像素的 Alpha 应设置为 255/65535(请参阅下面的代码示例)。
    • 使用 PGM/PPM 编码器,可以保存 8 位无符号 (CV_8U) 和 16 位无符号 (CV_16U) 图像

    • 7 带 TIFF 编码器,8 位无符号 (CV_8U),16 位无符号 (CV_16U),可以保存 32 位浮点 (CV_32F) 和 64 位浮点 (CV_64F) 图像。多个图像(Mat的矢量)可以保存为TIFF格式(请参阅下面的代码示例)。将保存 32 位浮点 3 通道 (CV_32FC3) TIFF 图像,使用 LogLuv 高动态范围编码(每像素 4 个字节)

  2. 如果不支持图像格式,图像将转换为 8 位无符号 (CV_8U) 并以这种方式保存。

  3. 如果格式、深度或频道顺序不同,请使用 Mat::convertTocv::cvtColor在保存之前对其进行转换。或者,使用通用文件存储 I/O函数将图像保存为 XML 或 YAML 格式。

  4. 下面的示例展示了如何创建 BGRA 图像,如何设置自定义压缩参数并将其保存到 PNG 文件。它还演示了如何在 TIFF 文件中保存多个图像:
    @include snippets/imgcodecs_imwrite.cpp

enum ImwriteFlags {
    
    
       IMWRITE_JPEG_QUALITY        = 1,  //!< For JPEG, it can be a quality from 0 to 100 (the higher is the better). Default value is 95.
       IMWRITE_JPEG_PROGRESSIVE    = 2,  //!< Enable JPEG features, 0 or 1, default is False.
       IMWRITE_JPEG_OPTIMIZE       = 3,  //!< Enable JPEG features, 0 or 1, default is False.
       IMWRITE_JPEG_RST_INTERVAL   = 4,  //!< JPEG restart interval, 0 - 65535, default is 0 - no restart.
       IMWRITE_JPEG_LUMA_QUALITY   = 5,  //!< Separate luma quality level, 0 - 100, default is -1 - don't use.
       IMWRITE_JPEG_CHROMA_QUALITY = 6,  //!< Separate chroma quality level, 0 - 100, default is -1 - don't use.
       IMWRITE_JPEG_SAMPLING_FACTOR = 7, //!< For JPEG, set sampling factor. See cv::ImwriteJPEGSamplingFactorParams.
       IMWRITE_PNG_COMPRESSION     = 16, // 针对PNG的压缩等级,值0 to 9,值越大,压缩之后图片越小,更长的压缩时间。如果指定了该参数,IMWRITE_PNG_STRATEGY应该修改为IMWRITE_PNG_STRATEGY_DEFAULT (Z_DEFAULT_STRATEGY),默认为1(最好的速度设置).
       IMWRITE_PNG_STRATEGY        = 17, //默认值为:IMWRITE_PNG_STRATEGY_RLE.
       IMWRITE_PNG_BILEVEL         = 18, //!< Binary level PNG, 0 or 1, default is 0.
       IMWRITE_PXM_BINARY          = 32, //!< For PPM, PGM, or PBM, it can be a binary format flag, 0 or 1. Default value is 1.
       IMWRITE_EXR_TYPE            = (3 << 4) + 0, /* 48 */ //!< override EXR storage type (FLOAT (FP32) is default)
       IMWRITE_EXR_COMPRESSION     = (3 << 4) + 1, /* 49 */ //!< override EXR compression type (ZIP_COMPRESSION = 3 is default)
       IMWRITE_EXR_DWA_COMPRESSION_LEVEL = (3 << 4) + 2, /* 50 */ //!< override EXR DWA compression level (45 is default)
       IMWRITE_WEBP_QUALITY        = 64, //!< For WEBP, it can be a quality from 1 to 100 (the higher is the better). By default (without any parameter) and for quality above 100 the lossless compression is used.
       IMWRITE_HDR_COMPRESSION     = (5 << 4) + 0, /* 80 */ //!< specify HDR compression
       IMWRITE_PAM_TUPLETYPE       = 128,//!< For PAM, sets the TUPLETYPE field to the corresponding string value that is defined for the format
       IMWRITE_TIFF_RESUNIT        = 256,//!< For TIFF, use to specify which DPI resolution unit to set; see libtiff documentation for valid values
       IMWRITE_TIFF_XDPI           = 257,//!< For TIFF, use to specify the X direction DPI
       IMWRITE_TIFF_YDPI           = 258,//!< For TIFF, use to specify the Y direction DPI
       IMWRITE_TIFF_COMPRESSION    = 259,//!< For TIFF, use to specify the image compression scheme. See libtiff for integer constants corresponding to compression formats. Note, for images whose depth is CV_32F, only libtiff's SGILOG compression scheme is used. For other supported depths, the compression scheme can be specified by this flag; LZW compression is the default.
       IMWRITE_JPEG2000_COMPRESSION_X1000 = 272,//!< For JPEG2000, use to specify the target compression rate (multiplied by 1000). The value can be from 0 to 1000. Default is 1000.
       IMWRITE_AVIF_QUALITY        = 512,//!< For AVIF, it can be a quality between 0 and 100 (the higher the better). Default is 95.
       IMWRITE_AVIF_DEPTH          = 513,//!< For AVIF, it can be 8, 10 or 12. If >8, it is stored/read as CV_32F. Default is 8.
       IMWRITE_AVIF_SPEED          = 514 //!< For AVIF, it is between 0 (slowest) and (fastest). Default is 9.
     };

ImwritePNGFlags, PNG图片格式参数:

//1. Imwrite 用于调整压缩算法的 PNG 特定标志。这些标志将修改 PNG 图像压缩的方式,并将传递到底层 zlib 处理阶段。
//2. IMWRITE_PNG_STRATEGY_FILTERED的效果是强制更多的霍夫曼编码和更少的字符串匹配;它介于IMWRITE_PNG_STRATEGY_DEFAULT和IMWRITE_PNG_STRATEGY_HUFFMAN_ONLY之间。
//3. IMWRITE_PNG_STRATEGY_RLE 设计为几乎与 IMWRITE_PNG_STRATEGY_HUFFMAN_ONLY 一样快,但为 PNG 图像数据提供更好的压缩。策略参数仅影响压缩比,而不影响压缩输出的正确性,即使设置不当也是如此。
//4. IMWRITE_PNG_STRATEGY_FIXED防止使用动态霍夫曼代码,允许为特殊应用提供更简单的解码器。
enum ImwritePNGFlags {
    
    
       IMWRITE_PNG_STRATEGY_DEFAULT      = 0, //!< Use this value for normal data.
       IMWRITE_PNG_STRATEGY_FILTERED     = 1, //!< Use this value for data produced by a filter (or predictor).Filtered data consists mostly of small values with a somewhat random distribution. In this case, the compression algorithm is tuned to compress them better.
       IMWRITE_PNG_STRATEGY_HUFFMAN_ONLY = 2, //!< Use this value to force Huffman encoding only (no string match).
       IMWRITE_PNG_STRATEGY_RLE          = 3, //!< Use this value to limit match distances to one (run-length encoding).
       IMWRITE_PNG_STRATEGY_FIXED        = 4  //!< Using this value prevents the use of dynamic Huffman codes, allowing for a simpler decoder for special applications.
     };

代码示例:

int main() {
    
    
    string filename = "D:/workspace/cpp_workspace/my-cv/data/img/lena.jpg";
    Mat img = imread(filename, ImreadModes::IMREAD_COLOR);
    vector<int> params;
    params.push_back(IMWRITE_PNG_COMPRESSION);
    params.push_back(9);
    params.push_back(IMWRITE_PNG_STRATEGY);
    params.push_back(IMWRITE_PNG_STRATEGY_DEFAULT);
    
    imwrite(R"(D:\workspace\cpp_workspace\my-cv\1.png)", img);
    imwrite(R"(D:\workspace\cpp_workspace\my-cv\2.png)", img, params);
    return 0;
}

备注:

1、RGB和BGR(opencv默认的彩色图像的颜色空间是BGR)颜色空间的转换

cv::COLOR_BGR2RGB
cv::COLOR_RGB2BGR
cv::COLOR_RGBA2BGRA
cv::COLOR_BGRA2RGBA

2、向RGB和BGR图像中增添alpha通道

cv::COLOR_RGB2RGBA
cv::COLOR_BGR2BGRA

3、从RGB和BGR图像中去除alpha通道

cv::COLOR_RGBA2RGB
cv::COLOR_BGRA2BGR

4、从RBG和BGR颜色空间转换到灰度空间

cv::COLOR_RGB2GRAY
cv::COLOR_BGR2GRAY

cv::COLOR_RGBA2GRAY
cv::COLOR_BGRA2GRAY

5、从灰度空间转换到RGB和BGR颜色空间

cv::COLOR_GRAY2RGB
cv::COLOR_GRAY2BGR

cv::COLOR_GRAY2RGBA
cv::COLOR_GRAY2BGRA

6、RGB和BGR颜色空间与BGR565颜色空间之间的转换

cv::COLOR_RGB2BGR565
cv::COLOR_BGR2BGR565
cv::COLOR_BGR5652RGB
cv::COLOR_BGR5652BGR
cv::COLOR_RGBA2BGR565
cv::COLOR_BGRA2BGR565
cv::COLOR_BGR5652RGBA
cv::COLOR_BGR5652BGRA

7、灰度空间域BGR565之间的转换

cv::COLOR_GRAY2BGR555
cv::COLOR_BGR5552GRAY

8、RGB和BGR颜色空间与CIE XYZ之间的转换

cv::COLOR_RGB2XYZ
cv::COLOR_BGR2XYZ
cv::COLOR_XYZ2RGB
cv::COLOR_XYZ2BGR

9、RGB和BGR颜色空间与uma色度(YCrCb空间)之间的转换

cv::COLOR_RGB2YCrCb
cv::COLOR_BGR2YCrCb
cv::COLOR_YCrCb2RGB
cv::COLOR_YCrCb2BGR

10、RGB和BGR颜色空间与HSV颜色空间之间的相互转换

cv::COLOR_RGB2HSV
cv::COLOR_BGR2HSV
cv::COLOR_HSV2RGB
cv::COLOR_HSV2BGR

11、RGB和BGR颜色空间与HLS颜色空间之间的相互转换

cv::COLOR_RGB2HLS
cv::COLOR_BGR2HLS
cv::COLOR_HLS2RGB
cv::COLOR_HLS2BGR

12、RGB和BGR颜色空间与CIE Lab颜色空间之间的相互转换

cv::COLOR_RGB2Lab
cv::COLOR_BGR2Lab
cv::COLOR_Lab2RGB
cv::COLOR_Lab2BGR

13、RGB和BGR颜色空间与CIE Luv颜色空间之间的相互转换

cv::COLOR_RGB2Luv
cv::COLOR_BGR2Luv
cv::COLOR_Luv2RGB
cv::COLOR_Luv2BGR

14、Bayer格式(raw data)向RGB或BGR颜色空间的转换

cv::COLOR_BayerBG2RGB
cv::COLOR_BayerGB2RGB
cv::COLOR_BayerRG2RGB
cv::COLOR_BayerGR2RGB
cv::COLOR_BayerBG2BGR
cv::COLOR_BayerGB2BGR
cv::COLOR_BayerRG2BGR
cv::COLOR_BayerGR2BGR

猜你喜欢

转载自blog.csdn.net/guo20082200/article/details/132017374