OpenCV Viz 三维直方图可视化实战:基于 opencv_contrib 的 3D 直方图与交互式窗口开发
计算机视觉图像处理深度学习机器学习【免费下载链接】opencv_contribRepository for OpenCVs extra modules项目地址https://gitcode.com/gh_mirrors/op/opencv_contrib点击查看免费下载本篇指南讲解如何利用 opencv_contrib 仓库中的 OpenCV Viz 模块将彩色图像的 3 通道直方图Histogram绘制为可交互的三维立体图形通过cv::calcHist计算三维直方图、用立方体网格WCube在 Viz 窗口中逐格渲染并为窗口注册键盘回调实现实时阈值过滤。读完本文你将掌握 Viz 窗口的键盘事件捕获机制、三维直方图的建模与缩放方法以及如何将 Viz 窗口与 OpenCV 常规图像窗口cv::imshow协同驱动。教程概览与本例目标本教程对应仓库中的官方示例 modules/viz/samples/histo3D.cpp属于 OpenCV Viz 系列教程table_of_content_viz.markdown的进阶篇其前置教程是 创建自定义 Widgetcreating_widgets。学习完本教程后你将能够为 Viz 窗口编写自定义键盘回调函数registerKeyboardCallback在 Viz 窗口中展示一幅三维直方图3D Histogram将 Viz 窗口与imshow图像窗口的事件循环整合到一个主循环中。程序整体结构示例程序的完整源码位于 histo3D.cpp整体流程分为四个阶段命令行解析与图像准备允许通过命令行传入 3 通道彩色图像完整路径若未传入则用cv::RNG生成一幅带高斯分布像素的合成图像。三维直方图计算用cv::calcHist计算 RGB 三通道的三维直方图并normalize归一化到 0~100 之间。图像窗口与滑动条在imshow图像窗口中插入一个阈值滑动条初始值 90。Viz 窗口交互渲染打开 3D 窗口注册键盘回调进入spinOnce与waitKey交替驱动的事件循环。命令行参数与合成图像生成代码通过CommandLineParser定义参数对应文档中的command_line_parser代码片段const String keys {Aide h usage ? help | | print this message } {arg1 | | Full path to color imag (3 channels)} ;参数表中定义了帮助选项h/help以及一个位置参数arg1用于接收图像的完整路径。主函数中的解析逻辑如下CommandLineParser parser(argc, argv, keys); if (parser.has(help)) { parser.printMessage(); return 0; } String nomFic parser.getString(0); Mat img; if (nomFic.length() ! 0) { img imread(nomFic, IMREAD_COLOR); if (img.empty()) { cout Image does not exist!; return 0; } }即传入路径则用imread按彩色模式读取文件不存在时直接提示退出。若不传任何参数则生成一幅512×512的CV_8UC3合成图像对应synthetic_image片段img Mat(512,512,CV_8UC3); parser.printMessage(); RNG r; r.fill(img(Rect(0, 0, 256, 256)), RNG::NORMAL, Vec3b(60, 40, 50), Vec3b(10, 5, 20)); r.fill(img(Rect(256, 0, 256, 256)), RNG::NORMAL, Vec3b(160, 10, 50), Vec3b(20, 5, 10)); r.fill(img(Rect(0, 256, 256, 256)), RNG::NORMAL, Vec3b(90, 100, 50), Vec3b(10, 20, 20)); r.fill(img(Rect(256, 256, 256, 256)), RNG::NORMAL, Vec3b(100, 10, 150), Vec3b(10, 5, 40));cv::RNG::fill以RNG::NORMAL分布把四象限分别填充为高斯点云参数含义为(均值, 标准差)即文档所述的四组中心象限中心B, G, R 均值标准差第一象限 (0,0)-(256,256)(60, 40, 50)(10, 5, 20)第二象限 (256,0)-(512,256)(160, 10, 50)(20, 5, 10)第三象限 (0,256)-(256,512)(90, 100, 50)(10, 20, 20)第四象限 (256,256)-(512,512)(100, 10, 150)(10, 5, 40)这样四个象限的像素在 RGB 颜色空间中各自聚成互不重叠的云团生成的直方图在三维空间中呈现四个清晰分离的山峰非常适合演示 3D 直方图的可视化效果。三维直方图的计算与归一化直方图计算逻辑对应文档中的calchist_for_histo3d片段同时构建了一个承载全部可视化状态的结构体Histo3DDatastruct Histo3DData { Mat histogram; // 三维直方图 int seuil; // 滑动条值0~100 double threshold; // 实际显示阈值 seuil / 1000000.0 Ptrviz::Viz3d fen3D; // 3D 窗口指针 int nbWidget; // 已添加的立方体数量 bool status; // 是否需要重绘 double maxH; // 直方图最大值 int code; // 最近一次按键码 };计算部分Histo3DData h; h.statustrue; h.seuil90; h.threshold h.seuil/1000000.0; float hRange[] { 0, 256 }; const float* etendu[] { hRange, hRange,hRange }; int hBins 32; int histSize[] { hBins, hBins , hBins }; int channel[] { 2, 1,0 }; calcHist(img, 1, channel, Mat(), h.histogram, 3, histSize, etendu, true, false); normalize(h.histogram, h.histogram, 100.0/(img.total()), 0, NORM_MINMAX, -1, Mat()); minMaxIdx(h.histogram,NULL,h.maxH,NULL,NULL);几个关键点值得注意三个通道各分 32 个 binhistSize为{32, 32, 32}每个通道的值域0~256被均匀量化最终直方图是一个32×32×32的三维矩阵共 32768 个单元。通道顺序为{2, 1, 0}文档特别说明这一顺序是为了与viz::WCoordinateSystem坐标轴的显示颜色同步——Viz 坐标轴约定 X 轴为红、Y 轴为绿、Z 轴为蓝因此把 R 通道对应到 X第 0 维、G 对应到 Y、B 对应到 Z颜色编码Color(j/plans*255, i/rows*255, k/cols*255)才能与三维坐标一一对应直方图中每个立方体在空间中的位置直接反映其 RGB 分量。归一化到总像素数normalize(..., 100.0/(img.total()), 0, NORM_MINMAX, ...)将直方图缩放到0 ~ 100/262144512×512 图像总像素数区间使阈值含义与像素数量挂钩。minMaxIdx取出最大值maxH后续DrawHistogram3D中用x / maxH计算每个立方体的相对尺寸。图像窗口与阈值滑动条代码在imshow窗口中插入滑动条对应slide_bar_for_thresh片段namedWindow(Image); imshow(Image,img); AddSlidebar(threshold,Image,0,100,h.seuil,h.seuil, UpdateThreshold,h); waitKey(30);封装函数AddSlidebar通过createTrackbarsetTrackbarMinsetTrackbarMaxsetTrackbarPos精确控制滑动范围void AddSlidebar(String sliderName, String windowName, int sliderMin, int sliderMax, int defaultSlider, int *sliderVal, void(*f)(int, void *), void *r) { createTrackbar(sliderName, windowName, sliderVal, 1, f, r); setTrackbarMin(sliderName, windowName, sliderMin); setTrackbarMax(sliderName, windowName, sliderMax); setTrackbarPos(sliderName, windowName, defaultSlider); }滑动条初始值为 90文档明确指出这意味着只有直方图单元值大于9/100000.0对 512×512 图像即约 23 个像素的 bin 才会被显示。滑动条回调UpdateThreshold把界面值转换为实际阈值void UpdateThreshold(int , void * r) { Histo3DData *h (Histo3DData *)r; h-statustrue; h-threshold h-seuil/1000000.0; coutWidget : h-nbWidget, h-threshold\n; }滑动条值每变化 1阈值就变化1e-6。阈值越高满足条件的直方图单元越少、显示的三维立方体越稀疏。创建 Viz 窗口并注册键盘回调初始化 Viz 窗口并注册键盘回调对应manage_viz_imshow_window片段h.fen3D makePtrviz::Viz3d(3D Histogram); h.nbWidget0; h.fen3D-registerKeyboardCallback(KeyboardViz3d,h); DrawHistogram3D(h); while (h.code!27) { h.fen3D-spinOnce(1); if (h.status) DrawHistogram3D(h); if (h.code!27) h.code waitKey(30); }这里的关键设计是用viz::Viz3d::spinOnce交替驱动两个窗口每次循环先让 Viz 窗口处理一次事件参数1表示事件循环运行 1 毫秒再调用waitKey(30)处理imshow窗口的键盘事件。由于两个窗口被放在同一个 while 循环中在 Viz 窗口中按下的键可以被imshow窗口捕获反之亦然这正是文档强调的 UsingViz3d::spinOnceenable keyboard event to be capture inimshowwindow too。按下 ESC键码 27时循环退出程序结束。从 viz3d.hpp 的声明可以看到Viz3d类提供了完整的交互接口本示例用到的核心成员包括void registerKeyboardCallback(KeyboardCallback callback, void* cookie 0)——注册键盘回调回调类型为void (*)(const KeyboardEvent, void*)void spinOnce(int time 1, bool force_redraw false)——短时运行事件循环并渲染void showWidget(const String id, const Widget widget, const Affine3d pose Affine3d::Identity())——向场景添加带唯一 id 的 Widgetvoid removeAllWidgets()——清空场景中全部 WidgetString getWindowName() const——返回窗口名自动加 Viz - 前缀。DrawHistogram3D把直方图矩阵渲染成三维立方体DrawHistogram3D负责把直方图Mat处理并显示到 Viz 窗口。首先是获取三维矩阵的规模get_cube_size片段int planSize (int)h.histogram.step1(0); int cols (int)h.histogram.step1(1); int rows (int)planSize / cols; int plans (int)h.histogram.total() / planSize; h.fen3D-removeAllWidgets(); h.nbWidget0; if (h.nbWidget0) h.fen3D-showWidget(Axis, viz::WCoordinateSystem(10));step1(0)是一个平面plane的步长step1(1)是一行row的步长由此推算出plans面数、rows行数、cols列数三个维度的大小即文档所述 Number of plan, row and column in three dimensional Mat每次重绘先removeAllWidgets()清空场景并复位计数器然后添加一个尺度为 10 的viz::WCoordinateSystem坐标轴 Widget见 widgets.hpp 中WCoordinateSystem(double scale 1.0)的声明作为直方图的空间参照系。随后三重循环遍历每个直方图单元并取数get_cube_values片段for (int k 0; k plans; k) { for (int i 0; i rows; i) { for (int j 0; j cols; j) { double x h.histogram.atfloat(k, i, j); if (x h.threshold) { double rstd::max(x/h.maxH,0.1); viz::WCube s(Point3d(k - r / 2, i - r / 2, j - r / 2), Point3d(k r / 2, i r / 2, j r / 2), false, viz::Color(j / double(plans) * 255, i / double(rows) * 255, k / double(cols) * 255)); h.fen3D-showWidget(format(I3d%d, h.nbWidget), s); } } } } h.status false;取数值使用cv::Mat::at(int i0, int i1, int i2)三参数重载即文档所述 kis plane number,irow number andjcolumn number只有x threshold的单元才会生成立方体从而实现阈值过滤立方体边长r max(x / maxH, 0.1)以直方图值相对于全局最大值的比例决定边长同时设下限 0.1 保证即使值很小的 bin 也可见viz::WCube构造参数依次为min_point、max_point、wire_frame与color见 widgets.hpp 中WCube的声明此处传入false表示用实体填充而非线框颜色按Color(j/plans*255, i/rows*255, k/cols*255)随三个维度渐变使每个立方体的颜色与其在空间中的位置形成直观映射每个立方体通过showWidget以唯一 id如I3d0、I3d1……加入场景。键盘回调交互式调节阈值键盘回调函数viz_keyboard_callback片段的原理与鼠标回调类似——按键信息封装在cv::viz::KeyboardEvent中void KeyboardViz3d(const viz::KeyboardEvent w, void *t) { Histo3DData *x(Histo3DData *)t; if (w.action) cout you pressed w.symbol in viz window x-fen3D-getWindowName()\n; x-code w.code; switch (w.code) { case /: x-statustrue; x-threshold * 0.9; break; case *: x-status true; x-threshold * 1.1; break; } if (x-status) { cout x-threshold \n; DrawHistogram3D(*x); } }从 types.hpp 中KeyboardEvent类的定义可知其关键字段为字段类型含义actionAction按键动作KEY_UP 0或KEY_DOWN 1示例中以非零判断为按下symbolString按键的名称如 /、*codeunsigned char按键码如 27 代表 ESCmodifiersint修饰键位标志NONE/ALT/CTRL/SHIFT的组合回调中通过switch (w.code)实现交互逻辑按/阈值乘以 0.9降低阈值显示更多立方体按*阈值乘以 1.1提高阈值过滤掉低频 bin每次按键把x-code记录下来并置status true触发主循环调用DrawHistogram3D重绘同时把当前阈值打印到控制台。由此用户既可以通过imshow窗口的滑动条粗调阈值也可以直接在 Viz 窗口中用/和*键细调两种交互方式共用同一个status标志驱动重绘。运行与结果编译时确保 OpenCV 开启了viz模块该模块基于 VTK 实现构建时需链接 VTK 库。运行示例# 无参数使用合成图像 ./histo3D # 传参使用真实彩色图像 ./histo3D /path/to/your/image.jpg # 查看帮助 ./histo3D -h本文开头展示的 histo50.png 即为文档给出的标准结果无参数运行、阈值设为 50时合成图像的 3D 直方图在 Viz - 3D Histogram 窗口中按颜色分块呈现。可以看到四个象限对应的高斯像素云在 RGB 空间中形成了四个颜色各异的立体簇坐标轴X 红 / Y 绿 / Z 蓝与直方图各维度的颜色渐变严格对应——这正是通道顺序取{2, 1, 0}的用意所在。你可以通过以下方式进一步体验拖动 Image 窗口中的 threshold 滑动条观察三维直方图随阈值升高而逐渐稀疏在 Viz 窗口中按/与*键连续缩放阈值用鼠标在 Viz 窗口中旋转、缩放视角从任意角度观察直方图的空间分布传入自己的彩色照片观察其颜色分布在 RGB 空间中的三维形态。小结本教程以 histo3D.cpp 为完整示例串起了 OpenCV Viz 模块的核心交互能力calcHist计算三维直方图 →WCube立方体 Widget 逐 bin 渲染 →registerKeyboardCallback捕获 Viz 键盘事件 →spinOnce与waitKey双窗口协同驱动。这套数据矩阵 → 三维 Widget 场景 → 事件回调的模式同样适用于点云、体素数据、高维统计结果的交互式可视化是 Viz 模块进阶开发如 widget_pose、transformations 等后续教程的基础。赞分享计算机视觉图像处理深度学习机器学习【免费下载链接】opencv_contribRepository for OpenCVs extra modules项目地址https://gitcode.com/gh_mirrors/op/opencv_contrib点击查看免费下载相关推荐OpenCV Viz 三维可视化实战从窗口启动、控件位姿控制到自定义控件与 3D 直方图OpenCV Viz 三维可视化实战从窗口启动、控件位姿控制到自定义控件与 3D 直方图 导读 本文以 opencv_contrib 仓库中 viz 模块教计算机视觉图像处理深度学习机器学习OpenCV 二维直方图实战用 cv.calcHist 与 np.histogram2d 计算并可视化 HSV 色彩直方图OpenCV 二维直方图实战用 cv.calcHist 与 np.histogram2d 计算并可视化 HSV 色彩直方图 本文围绕 OpenCV 教程《Hi计算机视觉图像处理深度学习机器学习OpenCV 直方图计算实战从 calcHist 的 bins/dims/range 原理到 BGR 三通道直方图可视化OpenCV 直方图计算实战从 calcHist 的 bins/dims/range 原理到 BGR 三通道直方图可视化 导读 本文围绕 OpenCV 官方计算机视觉图像处理深度学习机器学习上一篇Spacedrive 引用侧车Reference Sidecar机制解析为 Live Photo 支持的底层基础设施下一篇ExifToolGUI照片元数据批量管理的终极图形化解决方案创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

TorchNPU LLM性能基准实战:如何快速测试热门大模型在昇腾NPU上的真实表现

TorchNPU LLM性能基准实战:如何快速测试热门大模型在昇腾NPU上的真实表现

TorchNPU LLM性能基准实战:如何快速测试热门大模型在昇腾NPU上的真实表现 【免费下载链接】pytorch 作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU&…

2026/9/24 15:47:02 阅读更多 →
palera1n 越狱指南:A8 到 A11 设备三步拿回可写文件系统

palera1n 越狱指南:A8 到 A11 设备三步拿回可写文件系统

palera1n 越狱指南:A8 到 A11 设备三步拿回可写文件系统 【免费下载链接】palera1n Jailbreak for A8 through A11, T2 devices, on iOS/iPadOS/tvOS 15.0, bridgeOS 5.0 and higher. 项目地址: https://gitcode.com/GitHub_Trending/pa/palera1n palera1n 越…

2026/9/24 15:47:02 阅读更多 →
Unity HDRP 渲染管线 Camera 输出到RenderTexture没有Alpha通道

Unity HDRP 渲染管线 Camera 输出到RenderTexture没有Alpha通道

项目属性渲染管线:HDRPUnity版本:2022.3.62f3项目背景将Camera指定输出,TargetTexture及将背景颜色改为(0,0,0,0)将UI指定RenderTexture,但没有透明通道修改方式修改项目…

2026/9/24 15:47:02 阅读更多 →

最新新闻

AI工程全景地图:六步构建从数据到价值的落地路径

AI工程全景地图:六步构建从数据到价值的落地路径

1. 为什么突然都在说 AI 工程这几年“AI 工程”这个词出现频率越来越高,但你要是真去问一句“AI 工程到底是什么”,能一句话说清楚的人其实不多。我见过不少团队,模型训练得挺溜,一到上线就翻车,不是推理延迟压不下来&…

2026/9/24 20:48:59 阅读更多 →
jsonschema实战:为JSON数据立规矩的Python校验库

jsonschema实战:为JSON数据立规矩的Python校验库

我们天天和数据打交道,但真正让你头疼的往往不是“数据对不对”,而是“数据是不是你要的那个结构”。JSON 格式灵活得让人又爱又恨,前端传参少个字段、API 响应多了个 null、配置文件类型悄悄从 int 变成 string,这些坑想必大家都…

2026/9/24 20:48:59 阅读更多 →
Codex Token消耗优化:两个开源工具让账单减半

Codex Token消耗优化:两个开源工具让账单减半

先说结论:Codex 确实好用,但它烧起 Token 来也真的一点都不含糊。我重度用了几个月之后,账单上的数字一度让我怀疑是不是把 API Key 泄露了。后来我才意识到,问题不在于 Codex 本身有多能吃,而在于我们喂给它的“上下文…

2026/9/24 20:48:59 阅读更多 →
GPT金融AI量化投资实战:从信息提取到策略辅助的工程化探索

GPT金融AI量化投资实战:从信息提取到策略辅助的工程化探索

1. 从标题出发:这个项目到底在做什么“开启GPT技术与金融AI投资探索之旅”这个标题,乍一看像是某个课程或者训练营的宣传语,但如果你真的动手去拆,会发现它其实指向一个非常具体的技术落地场景:用大语言模型的能力去辅…

2026/9/24 20:48:59 阅读更多 →
AI室内设计会改结构吗?四款工具实测与避坑指南

AI室内设计会改结构吗?四款工具实测与避坑指南

1. 从一张户型图说起:AI室内设计到底动了什么很多人第一次用AI做室内设计,心里都揣着同一个疑问:我把户型图丢进去,它会不会自作主张把承重墙砸了、把窗户挪了、把卫生间改到客厅中间?这个担心不是多余的。我前后用四款…

2026/9/24 20:48:59 阅读更多 →
使用 @openuidev/devtools 调试 OpenUI 应用:Inspect 事件面板与 Debug 工作台实战指南

使用 @openuidev/devtools 调试 OpenUI 应用:Inspect 事件面板与 Debug 工作台实战指南

使用 openuidev/devtools 调试 OpenUI 应用:Inspect 事件面板与 Debug 工作台实战指南 【免费下载链接】openui The Open Standard for Generative UI 项目地址: https://gitcode.com/gh_mirrors/openui1/openui openuidev/devtools 是 OpenUI 生态中的开发期…

2026/9/24 20:47:58 阅读更多 →

日新闻

基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程

基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程

简介:这是一套面向计算机、人工智能、自动化等专业学生与教师的毕业设计级项目资源,围绕YOLOv8实现渔船作业监控系统,可用于毕设、课程设计、大作业或项目立项演示。压缩包共97个文件,约24.21MB,以70个Python源码文件为…

2026/9/24 0:00:19 阅读更多 →
单细胞注释实战:基于Scanpy的标记基因与参考映射流程解析

单细胞注释实战:基于Scanpy的标记基因与参考映射流程解析

简介:一份基于单细胞RNA测序数据的细胞类型注释算法研究Python毕业设计源码,针对计算机相关专业正在做毕设或需要项目实战的学习者,可用于课程设计与期末大作业。项目代码完整、经导师指导评审通过,可直接运行,覆盖数据…

2026/9/24 0:00:19 阅读更多 →
C#源生成器实战:用增量生成器替代反射,告别AOT崩溃

C#源生成器实战:用增量生成器替代反射,告别AOT崩溃

第一次在项目里被反射卡住,是在一个老旧的WinForms模块里:几十个类依赖PropertyChanged通知,运行时反射读属性、发通知,每次启动慢半拍不说,一上.NET Native/AOT裁剪模式几乎全面崩盘。后来我把这段逻辑全部改成C#源生…

2026/9/24 0:00:19 阅读更多 →

周新闻

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡…

2026/9/24 14:34:13 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技…

2026/9/24 9:10:42 阅读更多 →
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&…

2026/9/24 14:33:56 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/24 12:50:34 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/24 14:33:48 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/24 12:49:17 阅读更多 →