FEATURED · 精选文章

基于Qt与C++的前视声纳图像处理软件:从极坐标转换到实时可视化

发布时间 / 2026/9/4 7:00:24
来源 / 创域科博编辑部
栏目 / 资讯中心
基于Qt与C++的前视声纳图像处理软件:从极坐标转换到实时可视化 简介本资源是一款面向水下探测、海洋工程及声纳图像处理方向的C开发实践项目专为具备Qt基础与图像算法认知的中高级开发者设计用于解决前视声纳原始数据可视化弱、噪声干扰强、实时预处理能力不足等实际问题。压缩包共10个文件20KB涵盖3个核心cpp源码含主线程与图像处理线程、2个头文件定义界面逻辑与数据结构、1个UI界面描述文件、1个资源文件qrc、1个Qt工程配置pro、1个用户配置user及1份README说明文档结构精炼便于快速编译运行与二次扩展。已有296人学习下载可直接获取完整可运行的声纳数据显示框架集成直方图均衡化、中值滤波、对比度拉伸等经典图像增强算法并支持原始声纳数据加载、伪彩色映射、动态缩放与局部放大等交互功能是理解声纳成像特性与Qt图形处理协同开发的典型参考案例。1. 项目概述从声纳数据到可视化洞察前视声纳作为水下探测的“眼睛”其产生的原始数据就像一卷未经冲洗的胶片充满了有价值的信息但也布满了噪声、畸变和冗余。直接解读这些数据无异于雾里看花。我最近完成的一个项目就是基于Qt框架和C打造了一款专门服务于前视声纳数据的显示与预处理软件。这不仅仅是一个简单的“看图工具”而是一个集成了经典图形处理算法的数据“净化”与“增强”工作站。它的核心使命是将声纳设备采集到的、难以直接理解的原始回波信号转化为清晰、准确、可供后续分析或目标识别的可视化图像。对于从事水下机器人ROV/AUV、海洋测绘、水下搜救或渔业资源调查的工程师和研究人员来说处理声纳数据是家常便饭。然而市面上通用的图像处理软件如Photoshop或一些通用的科学可视化工具往往难以胜任。原因在于前视声纳图像有其独特的“性格”它本质上是极坐标下的强度分布距离-方位角存在典型的扇区形变数据中充斥着水声信道特有的多径干扰、混响噪声其灰度值代表的是回波强度而非光学亮度处理逻辑完全不同。因此一个量身定制的工具显得至关重要。这款软件正是为了解决这些痛点而生。它利用Qt强大的跨平台GUI能力构建了直观的操作界面依靠C的高效计算性能实时处理庞大的声纳数据流并内置了一系列从图像处理领域借鉴并适配的经典算法专门用于针对声纳图像的预处理。无论你是需要快速查看一次潜航的扫描结果还是要对大批量数据进行批处理以提取特征这个工具都能提供一个可靠的一站式解决方案。接下来我将深入拆解整个开发过程中的核心设计思路、关键技术实现以及那些只有亲手做过才能知道的“坑”与技巧。2. 核心需求分析与技术选型考量开发这样一款专业软件第一步不是急于写代码而是彻底想清楚它要解决什么问题以及为什么选择Qt和C这条技术路径。这决定了软件的骨架和最终的用户体验。2.1 前视声纳数据的独特性与预处理需求前视声纳通常以扇扫或机械扫描的方式工作其原始数据矩阵的行通常对应不同的距离门Range Bins列对应不同的波束方位角Beams。这就带来了几个核心处理需求坐标转换与图像重建原始数据是极坐标距离角度下的强度值。必须将其转换为笛卡尔坐标系X, Y下的图像才能进行直观显示。这个转换过程本身就会引入插值计算和几何形变校正的问题。噪声抑制水声环境复杂噪声主要来源于混响Reverberation、多径效应和电子热噪声。这些噪声在图像上表现为随机斑点散斑噪声或规律的条纹严重干扰目标检测。因此需要集成空域或时域的滤波算法。对比度增强声纳图像的动态范围可能很大但人眼能分辨的灰度级有限。为了突出弱目标或细节必须进行对比度拉伸、直方图均衡化等操作。数据格式解析不同厂商如Kongsberg, Teledyne, Sound Metrics的声纳设备输出数据格式各异可能是自定义的二进制包、CSV或特定的声学数据格式如.jsf, .xsf。软件需要具备灵活、可扩展的解析模块。实时性要求对于在线监测或ROV实时操控场景软件需要具备一定的实时显示和处理能力不能有肉眼可见的卡顿。2.2 为什么是Qt C面对上述需求技术选型几乎是一个必然的结果Qt框架的优势跨平台这是Qt最核心的竞争力之一。海洋领域的软件经常需要在Windows甲板控制站、Linux嵌入式船载机甚至macOS上运行。Qt“一次编写到处编译”的特性极大地减少了移植成本。强大的图形视图框架Graphics View Framework对于显示声纳图像这种需要频繁缩放、平移、叠加图层如轨迹、目标标记的场景Qt的Graphics View比直接使用QPainter更高效、更易于管理。它内置了场景QGraphicsScene、视图QGraphicsView和图形项QGraphicsItem的模型处理大量图形元素时性能出色。丰富的UI控件与布局管理器可以快速构建出包含菜单栏、工具栏、状态栏、停靠窗口、参数设置面板等复杂而专业的桌面软件界面。QCharts等模块也能方便地绘制距离-幅度曲线等辅助图表。信号与槽机制这是Qt的灵魂。它完美地解决了GUI界面响应与后端数据处理线程之间的通信问题。例如当用户点击“开始处理”按钮发出信号可以触发数据处理线程中的槽函数处理完成后线程再通过信号更新UI上的图像显示。这种松耦合的设计让多线程编程变得清晰和安全。C语言的优势性能声纳数据动辄每秒数兆甚至数十兆字节图像处理算法如滤波、变换计算密集。C的零成本抽象和直接内存操作能力能确保处理效率满足实时性要求。成熟的计算库生态虽然我们强调“集成经典算法”但并不意味着所有算法都要手写。我们可以依赖强大的库如OpenCV计算机视觉的基石。它提供了几乎所有我们需要的图像处理函数滤波、形态学操作、几何变换、直方图计算等且与C集成度极高。我们可以直接使用cv::Mat来存储和处理图像数据效率很高。Eigen如果需要涉及复杂的矩阵运算或坐标变换Eigen模板库在性能上无可匹敌。可控的内存与资源管理对于需要长时间运行、处理海量数据的软件精细的内存控制至关重要C的RAII资源获取即初始化理念和智能指针能有效防止内存泄漏。注意在项目初期有人可能会考虑PythonPyQt/PySide以追求更快的开发速度。但对于核心算法密集、且对性能有要求的声纳数据处理Python在循环计算和大型数组操作上的性能瓶颈是难以接受的。C与Qt的结合在性能与开发效率上取得了最佳平衡。3. 软件架构设计与模块拆解基于Qt的MVC模型-视图-控制器思想和软件工程的高内聚低耦合原则我将整个软件划分为以下几个核心模块它们协同工作共同完成从数据到可视化结果的流水线。3.1 数据层解析与管理的基石数据层是软件的“后勤部”负责所有原始数据的输入、解析、存储和供给。数据解析器DataParser职责这是一个抽象工厂模式或策略模式的典型应用场景。定义一个统一的IDataParser接口包含parse(const QString filePath)和getRawData()等方法。实现为每种支持的声纳数据格式如.dat,.jsf,.csv实现一个具体的解析器类如KongsbergParser,CSVParser。这样当需要增加对新格式的支持时只需新增一个解析器类无需修改其他代码。输出解析器将二进制或文本数据转换为软件内部统一的、结构化的数据对象例如一个SonarDataFrame类其中包含距离向量、方位角向量、强度矩阵、时间戳、频率等元数据。数据管理器DataManager职责采用单例模式作为全局唯一的数据中心。它管理所有已加载的SonarDataFrame对象处理数据的缓存、释放和查询。关键设计对于大型数据集如长时间录制的声纳文件不可能一次性全部读入内存。数据管理器需要实现“懒加载”和“缓存”机制。例如只将当前显示时段附近的数据帧保留在内存中其余部分保持在磁盘上根据视图的滑动动态加载和卸载。3.2 处理层算法引擎的核心处理层是软件的“发动机”包含了所有图形处理算法。这里的设计要点是算法的可插拔和可组合性。算法接口IAlgorithm定义一个通用的算法接口包含setParameters(const QVariantMap params)、process(const cv::Mat input, cv::Mat output)和getName()等方法。算法工厂AlgorithmFactory同样使用工厂模式根据算法名称如“MedianFilter”, “HistogramEqualization”创建具体的算法实例。经典算法实现坐标转换PolarToCartesian核心算法之一。使用双线性插值或更高质量的双三次插值将极坐标强度矩阵映射到笛卡尔坐标网格上。这里需要注意插值核的选择避免在图像中心对应声纳正前方产生过度的模糊。滤波去噪中值滤波Median Filter对去除声纳图像中的散斑噪声椒盐噪声的一种非常有效且能较好地保护边缘。OpenCV中的cv::medianBlur可直接调用。均值滤波/高斯滤波用于平滑一般性噪声但可能导致边缘模糊需谨慎使用。非局部均值去噪NL-Means更先进的去噪算法能在去噪的同时更好地保留纹理和细节但计算量较大适合离线处理。对比度增强直方图均衡化Histogram Equalization包括全局均衡化cv::equalizeHist和自适应均衡化CLAHE。CLAHE通过将图像分块并进行局部均衡能有效防止局部过亮或过暗特别适用于水下光照声照不均的场景。灰度拉伸允许用户手动或自动设置输入强度范围到输出显示范围如0-255的线性映射简单直接。形态学操作用于后处理例如用开运算先腐蚀后膨胀去除小亮点噪声用闭运算先膨胀后腐蚀填充小孔洞。实操心得算法的参数设置界面设计很重要。不要只提供一个输入框让用户填数字。对于像滤波器尺寸这样的参数应提供滑块和实时预览功能。对于CLAHE的“块大小”和“对比度限制”需要提供合理的默认值和范围说明因为不合理的参数会严重破坏图像质量。3.3 表示层Qt驱动的用户界面表示层是用户与软件交互的窗口利用Qt的各种组件构建。主视图MainView基于QGraphicsView和QGraphicsScene构建。SonarImageItem继承自QGraphicsPixmapItem或QGraphicsItem作为场景中的主要图形项负责显示处理后的声纳图像。实现鼠标的缩放、平移交互以及右键菜单如保存图片、测量距离。算法面板AlgorithmDock一个可停靠的QDockWidget以树形结构或列表形式展示所有可用的处理算法。用户可以将算法拖拽到“处理流水线”区域形成一个有序的处理链如坐标转换 - 中值滤波 - CLAHE增强。流水线编辑器PipelineEditor可视化地展示和管理算法处理链。每个算法作为一个节点节点之间用箭头连接。用户可以点击节点配置参数调整顺序或启用/禁用某个处理步骤。这极大地提升了操作的灵活性和直观性。数据导航与图层管理提供时间轴或帧滑动条用于浏览连续的数据帧。提供图层管理允许叠加显示轨迹线、目标标注、底图等。3.4 控制层粘合一切的逻辑控制层是软件的“神经系统”负责协调各模块的工作主要体现为各种Controller或ViewModel类。主控制器MainController响应UI事件如打开文件、点击处理按钮。它从数据管理器获取数据调用处理层的算法流水线进行处理最后将结果图像传递给主视图进行更新。多线程管理这是保证UI流畅的关键。绝对不能在GUI主线程中执行耗时的文件读取或算法处理。必须使用QThread或Qt Concurrent框架。标准做法创建一个Worker类将数据处理逻辑放在其槽函数中。主控制器通过信号触发Worker在子线程中开始工作Worker处理完成后通过信号将结果传回主线程由主线程安全地更新UI。4. 关键技术与实现细节剖析有了清晰的架构接下来深入几个最具挑战性也最体现技术含量的实现细节。4.1 极坐标到笛卡尔坐标的高效转换这是声纳图像显示的基石其效率和质量直接影响用户体验。// 伪代码示例展示核心思路 cv::Mat polarToCartesian(const cv::Mat polarImg, float maxRange, float beamWidthRad) { int polarRows polarImg.rows; // 距离门数量 int polarCols polarImg.cols; // 波束数量 float angleStep beamWidthRad / (polarCols - 1); float rangeStep maxRange / polarRows; // 定义输出笛卡尔图像大小和中心点 int cartSize 2 * static_castint(maxRange / rangeStep); // 正方形图像边长为2倍最大距离 cv::Mat cartImg cv::Mat::zeros(cartSize, cartSize, CV_32FC1); int center cartSize / 2; // 遍历笛卡尔图像的每一个像素 for (int y 0; y cartSize; y) { for (int x 0; x cartSize; x) { // 计算当前像素点相对于中心的极坐标 float dx x - center; float dy y - center; float r std::sqrt(dx*dx dy*dy); // 距离 float theta std::atan2(dy, dx); // 方位角范围[-π, π] // 将theta转换到声纳的起始角度范围例如从 -beamWidthRad/2 开始 theta theta beamWidthRad / 2; if (theta 0) theta 2 * M_PI; // 计算在极坐标矩阵中的索引使用插值 float rIndex r / rangeStep; float thetaIndex theta / angleStep; // 边界检查 if (rIndex 0 rIndex polarRows thetaIndex 0 thetaIndex polarCols) { // 使用双线性插值获取强度值 cartImg.atfloat(y, x) bilinearInterpolate(polarImg, rIndex, thetaIndex); } } } // 将浮点型矩阵转换为8位灰度图用于显示 cv::Mat displayImg; cartImg.convertTo(displayImg, CV_8UC1, 255.0); return displayImg; }性能优化上述双循环在大型图像上非常慢。实际项目中必须进行优化使用查找表LUT预先计算好每个笛卡尔像素对应的极坐标索引行、列和插值权重将计算量从O(N²)降至O(N)。利用OpenCV的remap函数cv::remap函数就是为几何变换设计的。我们可以构建两个映射矩阵map_x, map_y分别存储每个输出像素在源图像中的浮点坐标然后让OpenCV调用高度优化的内部函数进行插值这是最高效的实现方式。4.2 基于流水线的实时处理框架实现一个灵活、可实时更新的处理流水线是软件的另一大核心。// 流水线节点基类 class PipelineNode : public QObject { Q_OBJECT public: virtual ~PipelineNode() {} virtual QString name() const 0; virtual void setParameters(const QVariantMap ¶ms) 0; virtual bool process(cv::Mat inOutMat) 0; // 原地处理或返回新图 signals: void parametersChanged(); void nodeUpdated(); // 通知视图更新 }; // 具体的滤波节点 class MedianFilterNode : public PipelineNode { public: QString name() const override { return 中值滤波; } void setParameters(const QVariantMap ¶ms) override { m_kernelSize params.value(kernelSize, 3).toInt(); emit parametersChanged(); } bool process(cv::Mat inOutMat) override { if (inOutMat.empty()) return false; cv::medianBlur(inOutMat, inOutMat, m_kernelSize); emit nodeUpdated(); return true; } private: int m_kernelSize 3; }; // 流水线管理器 class PipelineManager : public QObject { Q_OBJECT public: void addNode(PipelineNode* node) { m_nodes.append(node); } bool execute(cv::Mat image) { for (auto* node : m_nodes) { if (!node-process(image)) { qWarning() Pipeline failed at node: node-name(); return false; } } return true; } private: QListPipelineNode* m_nodes; };在UI上每个PipelineNode对应一个可拖拽的图形块。当用户修改某个节点的参数或调整流水线顺序后控制器会重新执行整个流水线并通过信号槽通知视图刷新。为了实现“实时”效果对于简单的流水线可以在一个独立的QThread中执行PipelineManager::execute并通过信号将结果图像传回主线程。对于复杂流水线需要考虑增量处理或降低预览分辨率。4.3 Qt与OpenCV的无缝集成与图像显示Qt显示图像用的是QImage或QPixmap而OpenCV处理的是cv::Mat。它们之间的高效转换是桥梁。// 将 cv::Mat (灰度图) 转换为 QImage QImage cvMatToQImage(const cv::Mat mat) { if (mat.type() CV_8UC1) { // 8位灰度图 QImage image(mat.data, mat.cols, mat.rows, mat.step, QImage::Format_Grayscale8); // 注意这里返回的QImage共享了mat的数据mat的生命周期必须长于image // 对于需要长期持有的QImage应该使用 copy() 创建深拷贝 return image.copy(); } else if (mat.type() CV_8UC3) { // 8位彩色图 (BGR) cv::Mat rgb; cv::cvtColor(mat, rgb, cv::COLOR_BGR2RGB); QImage image(rgb.data, rgb.cols, rgb.rows, rgb.step, QImage::Format_RGB888); return image.copy(); } // 其他格式需要先转换 return QImage(); } // 在GraphicsView中显示 void SonarImageView::updateDisplay(const cv::Mat sonarImage) { QImage qImg cvMatToQImage(sonarImage); if (!qImg.isNull()) { QPixmap pixmap QPixmap::fromImage(qImg); m_sonarItem-setPixmap(pixmap); // m_sonarItem 是 SonarImageItem m_scene-setSceneRect(pixmap.rect()); // 更新场景范围 } }重要提示内存管理是关键陷阱。cvMatToQImage中如果直接使用mat.data构造QImage而不拷贝那么QImage只是cv::Mat数据的一个“视图”。一旦原始的cv::Mat被释放例如出了作用域QImage将指向无效内存导致程序崩溃或显示乱码。因此在不确定数据生命周期时务必使用.copy()创建深拷贝。虽然牺牲了一点性能但换来了稳定性。5. 开发环境搭建与工程实践工欲善其事必先利其器。一个顺畅的开发环境能极大提升效率。5.1 Qt、C与OpenCV的联调环境我推荐使用Qt Creator作为主IDE它对于Qt项目的支持是最好的。安装Qt从Qt官网下载在线安装器选择最新的LTS版本如Qt 5.15.x或Qt 6.x。安装时务必勾选对应你编译器版本的模块如MSVC 2019 64-bit以及Qt Creator。安装OpenCVWindows推荐使用官方预编译库或使用vcpkg (vcpkg install opencv:x64-windows) 安装非常方便。Linux使用包管理器安装sudo apt install libopencv-dev或从源码编译。关键一步将OpenCV的include目录和lib目录路径记录下来。配置Qt项目 (.pro文件)# 在 .pro 文件中添加 # 包含OpenCV头文件路径 (根据你的实际路径修改) INCLUDEPATH D:/opencv/build/include # 链接OpenCV库文件 (Windows下) LIBS -LD:/opencv/build/x64/vc15/lib \ -lopencv_world455 # 如果是world模块 # 或者分别链接各个模块 # LIBS -lopencv_core455 -lopencv_imgproc455 -lopencv_highgui455 ...Debug vs Release注意链接对应配置的库opencv_world455d.lib用于Debugopencv_world455.lib用于Release。动态库确保程序运行时能找到OpenCV的DLLWindows或.soLinux文件。可以将它们复制到可执行文件目录或将其路径添加到系统环境变量PATH中。5.2 软件打包与部署开发完成后如何将软件交付给用户Qt提供了成熟的工具。动态链接 vs 静态链接动态链接生成的可执行文件小但需要随软件分发Qt和OpenCV的运行时库DLL。这是最常见的方式。静态链接将所有库编译进一个独立的.exe文件体积巨大但部署简单。Qt的开源协议LGPL对静态链接有要求需注意合规性。OpenCV通常也以动态库形式提供。使用 windeployqt (Windows) 或 macdeployqt (macOS)这是Qt官方提供的部署工具。在Release模式下编译好程序后在命令行进入生成.exe的目录执行windeployqt --release YourAppName.exe该命令会自动扫描.exe文件依赖的Qt模块并将对应的DLL、插件、翻译文件等复制到当前目录形成一个基本可运行的包。处理OpenCV依赖手动将OpenCV的DLL如opencv_world455.dll 以及可能需要的opencv_videoio_ffmpeg455_64.dll等复制到与.exe相同的目录。创建安装包使用专业的安装包制作工具如Inno Setup(免费) 或Advanced Installer将整理好的所有文件可执行文件、DLL、资源文件、文档打包成一个标准的安装程序.exe或.msi方便用户安装。6. 常见问题排查与性能调优实录在实际开发和测试中会遇到各种各样的问题。这里记录几个最具代表性的“坑”及其解决方案。6.1 图像显示闪烁或卡顿问题描述在快速更新声纳图像如播放数据流时视图区域出现明显的闪烁或刷新不流畅。原因分析在主线程进行耗时操作数据处理或图像转换阻塞了GUI主线程导致界面无法及时响应重绘事件。频繁的完整重绘每次更新都清除整个场景再重新绘制效率低下。内存频繁分配/释放在渲染循环中不断创建和销毁QImage或QPixmap。解决方案确保数据处理在子线程这是铁律。使用QThread或QtConcurrent::run。使用双缓冲与局部更新在自定义的SonarImageItem中重写paint函数直接绘制一个缓存的QPixmap。只有当数据真正更新时才在后台线程生成新的QImage并转换为QPixmap然后通过信号槽通知SonarImageItem更新这个缓存QPixmap并调用update()请求重绘。update()函数可以传入一个需要重绘的矩形区域只更新变化部分而不是整个项。复用内存在可能的情况下预分配好cv::Mat和QImage所需的内存在循环中复用它们而不是每次都新建。6.2 处理流水线执行速度慢问题描述当添加多个处理算法后处理一帧数据的时间过长无法满足实时性要求。原因分析某些算法本身复杂度高如非局部均值去噪。算法顺序不合理导致重复计算或无效计算。数据在cv::Mat和QImage之间来回转换开销大。优化策略算法级优化降分辨率处理对于实时显示可以先将图像缩放至较小的尺寸进行处理和显示原始数据用于保存或后期分析。条件执行某些算法如复杂的去噪可以设置为每N帧执行一次而不是每帧都执行。寻找更快的替代算法例如在保证效果的前提下用引导滤波Guided Filter替代双边滤波Bilateral Filter。流水线级优化减少不必要的格式转换在整个流水线内部始终保持使用cv::Mat如CV_32FC1进行计算直到最后一步需要显示时才转换为8位的QImage。并行化如果流水线中的某些节点彼此没有依赖关系可以考虑使用QtConcurrent映射到多个线程并行执行。利用硬件加速检查OpenCV是否启用了IPP、OpenCL或CUDA支持。如果硬件支持许多OpenCV函数会自动调用这些加速库获得数倍甚至数十倍的性能提升。在编译OpenCV时可以开启这些选项。6.3 内存泄漏与程序崩溃问题描述软件长时间运行后内存占用持续增长或偶尔发生崩溃。排查方法使用工具在Windows下可以使用Visual Studio的诊断工具或ValgrindLinux来检测内存泄漏。重点关注点手动new/delete在现代C中应尽量避免。使用std::unique_ptr,std::shared_ptr和std::vector等RAII容器来管理资源。Qt对象父子关系Qt有自己的对象树内存管理机制。确保在堆上创建的QObject派生类对象有正确的父对象这样在父对象销毁时子对象会被自动删除。如果没有父对象需要自己负责删除。跨线程的信号槽连接这是崩溃的高发区。如果槽函数在子线程的对象中而该对象在槽函数执行前被主线程销毁就会访问野指针。使用QObject::deleteLater()来安全地请求删除对象并确保连接类型正确如使用Qt::BlockingQueuedConnection或确保对象生命周期安全。OpenCVcv::Mat的浅拷贝cv::Mat a b;是浅拷贝它们共享数据。如果b被释放a的数据指针就悬空了。需要深拷贝时使用b.copyTo(a);或cv::Mat a b.clone();。6.4 多格式数据解析的扩展性问题问题描述每增加一种新的声纳数据格式就需要大改代码添加大量的if-else判断。解决方案如前所述采用策略模式。定义一个抽象解析器接口每种格式对应一个具体实现。在软件配置文件中可以维护一个“格式后缀”到“解析器类名”的映射。当用户打开一个文件时根据后缀名动态加载对应的解析器插件可以使用Qt的插件机制QPluginLoader或简单的工厂模式。这样新增格式就变成了一个独立的、可编译的模块与主程序完全解耦。开发这样一款软件的过程是一个不断在性能、效果、易用性和可维护性之间寻找平衡的过程。每一个技术选型和代码实现的背后都需要对声纳原理、图像处理和软件工程有深入的理解。当看到原本模糊不清的声纳数据经过自己编写的软件处理后清晰地呈现出水下的地形、沉船或鱼群时那种成就感是无可替代的。这个项目也让我深刻体会到将经典的图形算法与特定的领域知识相结合能创造出解决实际痛点的强大工具。本文还有配套的精品资源点击获取
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻