ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

高光谱影像分类实战:深度融合(2D)2PCA、双通道CNN与SVM

高光谱影像分类实战:深度融合(2D)2PCA、双通道CNN与SVM 简介面向毕业设计、课程设计与项目开发的高光谱遥感影像识别与分类的Python实现资源围绕休斯现象、空谱特征提取与分类器泛化等关键问题展开。针对高光谱影像分类中常见的维度灾难问题提出基于波段组合的二维双向主成分分析降维方法、双通道卷积神经网络空谱特征提取模型以及双通道卷积神经网络与支持向量机融合分类模型完整覆盖从数据读取、降维、特征提取到识别的项目流程。压缩包共二十四个文件以十三个Python源码为核心辅以两个MAT数据文件、项目文档、说明文本、模型示意图及实验日志等整体仅约六兆字节便于快速部署复现。资源附带使用教程与项目说明文档结构清晰可直接参考并在此基础上二次开发。目前已有约一百九十九人学习下载适合需要高质量参考实现的高光谱遥感方向学生与开发者。1. 高光谱遥感影像识别与分类先解决休斯现象再谈分类精度高光谱影像波段动辄一两百个很多人第一次跑分类时都栽在同一个地方波段堆得越多分类精度反而越差这就是所谓的“休斯Hughes现象”也是高光谱分类里最典型的黑匣子。这个基于 python 实现的高光谱遥感影像识别与分类项目核心思路是把降维、双通道卷积神经网络、SVM 融合串成一条完整流水线先用 (2D)2PCA 把两百多个波段压到几十个消除冗余再用双通道 CNN 同时提取光谱特征和空间特征最后用 SVM 替代 Softmax 做分类把模型的泛化能力最大化。适合做毕业设计、课程设计也适合想快速复现一套能跑通的高光谱分类流程、然后换到自己数据上的从业者。整个项目源码已经过测试配合项目文档和 README 就能沿着我的路线重跑一遍。2. 数据准备与 (2D)²PCA 降维把 200 波段压到能驾驭的维度拿到压缩包后先看清目录data下放着 Indian Pines 的 mat 格式数据model下是训练好的权重Part_1、Part_2、Part_3把整个流程拆成了阶段。我第一次跑的时候直接跳过降维去训练 CNN结果 200 个波段喂进去训练速度慢不说验证集精度还一直上不去。后来才意识到Indian Pines 这类数据集的波段之间相关性极高直接喂原始波段模型学到的全是冗余信息。2.1 读入 Indian Pines 与预处理先去掉吸水波段和背景Indian Pines 是最经典的高光谱测试数据原始 224 波段剔除低信噪比的吸水波段后通常剩 200 个波段图像尺寸 145×145共 16 类地物。项目里的 mat 文件是已经处理过的版本直接用 scipy 读就行。import numpy as np import scipy.io as sio # 读入高光谱立方体 (145, 145, 200) 和真实标签 (145, 145) img sio.loadmat(data/Indian_pines_corrected.mat)[indian_pines_corrected] gt sio.loadmat(data/Indian_pines_gt.mat)[indian_pines_gt] print(img.shape, gt.shape) # (145, 145, 200) (145, 145) # 标签 0 是背景类别编号 1~16只保留有标签的像素 h, w, bands img.shape pixels img.reshape(-1, bands).astype(np.float32) labels gt.reshape(-1) valid labels 0 X_all pixels[valid] y_all labels[valid] print(X_all.shape, np.unique(y_all)) # (10249, 200) 16 个类别 # 按通道做 z-score 标准化避免能量高的波段主导距离计算 mean X_all.mean(axis0) std X_all.std(axis0) std[std 0] 1 X_norm (X_all - mean) / std这里有两个点要说明一是 Indian Pines 真正有效的有标签像素只有 10249 个其他全是背景这个数量级决定了后面网络不能设计得太深二是标准化这一步均值方差应该只从训练集计算如果在整个数据集上算后面做交叉验证时会有轻微的数据泄漏第 4 章避坑部分我会专门展开。我一般会把标准化写成一个类fit 和 transform 分开方便后面给测试集用同一套参数。2.2 (2D)²PCA 核心实现两个方向分别投影不拉平矩阵普通 PCA 的做法是把每个像素的光谱向量拉平成一条一维向量再求协方差矩阵的特征向量。这样做的问题在于它完全丢掉了像元之间的空间位置关系而且当波段数 B 远大于样本数 N 时B×B 协方差矩阵估计非常不稳定。(2D)2PCA 的做法不一样第一步沿光谱维做一次 2DPCA把每个空间位置的 B 维光谱向量看成样本投影到 d1 维第二步再沿空间维做一次 2DPCA对每个降维后的特征图做空间方向投影。这样矩阵规模小得多而且空间结构被保留在特征里。def pca_projection(mat, n_components): # mat: (N, D) 输入矩阵逐列中心化后求协方差的特征向量 mat mat - mat.mean(axis0) cov np.cov(mat.T) # D x D eig_vals, eig_vecs np.linalg.eigh(cov) # 特征值升序排列 idx np.argsort(eig_vals)[::-1][:n_components] return eig_vecs[:, idx], eig_vals[idx] def twod2pca(block, d130, d220): # block: 单个像元的邻域立方体 (p, p, B)p 为窗口边长 p, p, B block.shape # 第一阶段沿光谱维做 2DPCA X_flat block.reshape(p * p, B) # (p*p, B) W1, _ pca_projection(X_flat, d1) # (B, d1) feat1 X_flat.dot(W1).reshape(p, p, d1) # (p, p, d1) # 第二阶段对每个光谱特征图沿空间维做 2DPCA W2_collect [] for f in range(d1): mat feat1[:, :, f] # (p, p) 单通道空间图 W2, _ pca_projection(mat, d2) # 行方向投影 W2_collect.append(W2) W2_mean np.mean(np.array(W2_collect), axis0) feat2 np.stack([feat1[:, :, f].dot(W2_mean) for f in range(d1)], axis-1) return feat2[:, :d2, :] # (p, d2, d1)这个实现是教学版和论文里的严格写法略有差异论文里会把行方向和列方向分别处理这里我用“每个光谱通道求投影方向再取均值”来体现同一个思想落到项目里可以先用这套跑通再按论文细节优化。(2D)2PCA 相比普通 PCA 的核心优势是参与协方差估计的样本是二维矩阵的行向量样本数更多协方差矩阵更稳定同时降维后的每个特征都还保留着原始的空间邻域关系这一点在后面喂给双通道 CNN 时非常关键。2.3 降维参数怎么定d1、d2 的取值逻辑与验证方法d1 和 d2 不是拍脑袋定的我一般是根据特征值累计贡献率来选先算清楚每个主方向承载了多少信息量。def retain_components(eig_vals, ratio0.98): eig_vals np.sort(eig_vals)[::-1] # 降序排列 total eig_vals.sum() for k in range(1, len(eig_vals) 1): if eig_vals[:k].sum() / total ratio: return k return len(eig_vals)在 Indian Pines 上光谱维的 d1 取 25~40 通常就能覆盖 98% 以上的能量空间维的 d2 取 15~25 就够用了。注意输出形状降维后的特征块是(p, d2, d1)p 是窗口边长d2 是空间维压缩后的尺寸d1 是光谱维压缩后的特征数。这个数据形状直接决定了后面双通道 CNN 的输入维度所以我在项目文档里会要求先把这一段跑通把形状打印出来确认再进入下一步。参数验证上不要偷懒我一般会先取全图 2000 个有标签像素组成小样本集合在这个集合上扫 d1 和 d2每次只改一个参数另一个固定记录降维后喂给同一个简单分类器比如线性 SVM的精度变化选一个“曲线拐点”附近的参数作为默认值。这样避免一上来就用全图数据扫参网格搜索的计算成本会不可控。常见做法是把整个流程封装成一步load_data() → twod2pca() → train_svm()然后参数扫描只在这条 pipeline 上跑换数据时不用改逻辑。3. 双通道 CNN 空谱特征提取光谱曲线和空间纹理分开建模降维只是第一步它解决了“波段冗余”的问题但高光谱分类的另一个难点是只靠光谱曲线很难区分同物异谱的地物。比如同一类作物在不同生长阶段光谱差异很大这时就需要引入空间信息。双通道 CNN 的思路很直接一个通道接收中心像元的光谱特征向量用一维卷积捕捉波段之间的局部依赖另一个通道接收邻域空间块用二维卷积提取纹理和形状特征最后把两路特征拼接。3.1 邻域样本构造窗口怎么取、边界怎么填充构造训练样本时每个有标签的像元要生成一个邻域块这个块就是“空间通道”的输入。窗口大小我常用 11×11 或 13×13太小空间信息不够太大则引入过多周围异类像元反而模糊了类别边界。边界填充不要用零填充零填充会把边缘像元的空间特征全部压暗导致边缘地物分类精度骤降。def build_samples(img_norm, gt, patch11, d130, d220): h, w, _ img_norm.shape pad patch // 2 # 镜像填充保留边缘像元的邻域纹理 img_pad np.pad(img_norm, ((pad, pad), (pad, pad), (0, 0)), modesymmetric) gt_pad np.pad(gt, ((pad, pad), (pad, pad)), modeconstant, constant_values0) X_spec, X_spat, y [], [], [] for i in range(h): for j in range(w): if gt[i, j] 0: continue block img_pad[i:i patch, j:j patch, :] block twod2pca(block, d1, d2) # (patch, d2, d1) X_spec.append(block[pad, pad, :]) # 中心像元光谱 X_spat.append(block) # 整个邻域块 y.append(gt[i, j]) return (np.array(X_spec), np.array(X_spat), np.array(y))这段代码里循环遍历整幅图的 10249 个有标签像素为了教学展示写成双循环实际项目里我会用np.asarray批量切块再向量化处理速度能快几十倍。注意光谱通道取的是block[pad, pad, :]也就是中心像元在降维后的光谱特征而空间通道是整个(11, 20, 30)的块。这里的光谱特征已经是 (2D)2PCA 压缩后的 30 维向量不再是原始 200 波段所以两个通道的输入尺度是一致的训练时收敛会快很多。3.2 双通道 CNN 模型结构一维卷积抓光谱二维卷积抓空间我用 Keras 的函数式 API 搭模型两个输入分支结构独立最后拼接输出。这个网络结构刻意做得比较浅因为 Indian Pines 的有效训练样本只有几千个网络深了必然过拟合。from tensorflow.keras.layers import (Input, Conv1D, Conv2D, MaxPooling1D, MaxPooling2D, Flatten, Dense, Dropout, concatenate, Reshape, BatchNormalization) from tensorflow.keras.models import Model dim1, dim2 30, 20 # 与降维阶段保持一致 patch 11 # 光谱通道输入中心像元降维后的光谱向量 spec_input Input(shape(dim1,), namespectral_input) x1 Reshape((dim1, 1))(spec_input) x1 Conv1D(filters32, kernel_size5, activationrelu)(x1) x1 MaxPooling1D(pool_size2)(x1) x1 Flatten()(x1) # 空间通道输入邻域块 (patch, dim2, dim1) spat_input Input(shape(patch, dim2, dim1), namespatial_input) x2 Conv2D(filters32, kernel_size(3, 3), activationrelu)(spat_input) x2 BatchNormalization()(x2) x2 MaxPooling2D(pool_size(2, 2))(x2) x2 Conv2D(filters16, kernel_size(3, 3), activationrelu)(x2) x2 Flatten()(x2) # 融合层拼接两路特征再接全连接 x concatenate([x1, x2]) x Dense(128, activationrelu)(x) x Dropout(0.5)(x) feat Dense(64, activationrelu, namefeature_layer)(x) out Dense(16, activationsoftmax)(feat) model Model(inputs[spec_input, spat_input], outputsout) model.compile(optimizeradam, losssparse_categorical_crossentropy, metrics[accuracy]) model.summary()两个关键细节一是光谱通道用 Conv1D卷积核大小 5表示一次看 5 个相邻波段的组合关系这比直接全连接更能捕捉光谱曲线里的局部形状二是空间通道里dim130被当作通道维因为 (2D)2PCA 降维后的 30 个特征本身就是“光谱特征图”2D 卷积在空间维上滑动时天然融合了光谱信息。feature_layer这个 64 维输出是专门留给后面 SVM 用的训练完 CNN 后我会取出这一层的输出作为特征向量这一步是整个 CNN-SVM 融合的关键接口。3.3 训练与特征提取分层划分数据集固定随机种子训练集和测试集的划分直接影响最终精度的可信度。我习惯用按类别分层的随机划分保证每个类别在训练集和测试集中的比例一致。from sklearn.model_selection import train_test_split X_spec, X_spat, y build_samples(img, gt, patch11, d130, d220) idx np.arange(len(y)) idx_tr, idx_te train_test_split(idx, test_size0.8, stratifyy, random_state42) history model.fit( [X_spec[idx_tr], X_spat[idx_tr]], y[idx_tr], validation_data([X_spec[idx_te], X_spat[idx_te]], y[idx_te]), epochs50, batch_size64, verbose1 ) # 训练完成后取出 feature_layer 的输出作为深层特征 feat_model Model(inputsmodel.input, outputsmodel.get_layer(feature_layer).output) F_tr feat_model.predict([X_spec[idx_tr], X_spat[idx_tr]]) F_te feat_model.predict([X_spec[idx_te], X_spat[idx_te]]) print(F_tr.shape, F_te.shape) # (2049, 64) (8200, 64)20% 训练、80% 测试是 Indian Pines 上的常见划分方式因为类别多且样本不均衡训练集太少了少数类根本学不动。stratifyy这行很重要它保证每个类别在训练集里都按原始比例出现。epochs 设 50 是因为我在项目里配合了EarlyStoppingpatience10实际上跑到 30 轮左右验证精度就开始平台期了硬跑满 50 轮反而会把验证集精度拉低。项目logs目录下保存了我训练过程中的精度曲线和 loss 曲线下载后可以直接对照看自己的训练过程是否正常收敛。4. CNN 与 SVM 融合从特征到分类器四个必踩的坑与排查CNN 训练完之后最后一层是 Softmax但项目里实际用的是 SVM 分类器这就是 CNN-SVM 融合的核心逻辑让 CNN 负责学习特征让 SVM 负责决策边界。我在跑这个流程时踩了不少坑下面四条是频率最高的每条都是血泪经验。4.1 为什么要用 SVM 替代 Softmax 做最终分类CNN 的 Softmax 层本质是逻辑回归它适合大规模数据但 Indian Pines 这种小样本高维特征场景下SVM 的泛化能力反而更强。把 CNN 最后一个全连接层之前的 64 维特征提取出来丢给 RBF 核 SVM等于把 CNN 当成了特征提取器把分类决策交给 SVM 的结构化风险最小化去处理。from sklearn.svm import SVC svm SVC(kernelrbf, C10, gamma0.01, probabilityTrue) svm.fit(F_tr, y[idx_tr]) svm_acc svm.score(F_te, y[idx_te]) print(CNN-SVM accuracy:, round(svm_acc, 4))这里的 C 是正则化参数C 越大对训练集拟合越强容易过拟合gamma 是 RBF 核的宽度gamma 越大决策边界越复杂。Indian Pines 特征维度 64 维、样本 2000 左右时C10、gamma0.01 是一个比较稳的起点但具体值必须通过交叉验证来找不能直接抄。4.2 坑一精度 99%你大概率数据泄漏了现象测试集精度直接冲到 99% 以上甚至接近 100%分类报告里每个类别都完美无缺。原因这是高光谱分类最容易犯的错误也是最难排查的。标准化算整体均值方差、(2D)2PCA 投影矩阵在整幅图上拟合、CNN 预训练时把测试集也喂进去提取了特征、SVM 在包含测试集信息的特征上训练——这四个环节任何一个泄漏都会导致精度虚高。特别是 (2D)2PCA 的投影矩阵 W1 和 W2如果在全体像素上计算就等于让降维过程“偷看”了测试集的数据分布。解决把标准化、 (2D)2PCA 全部写成只依赖训练集的 fit 接口测试集只做 transform。CNN 训练时严格只用idx_tr的样本提取测试集特征时只做一次前向传播。划分数据集时固定random_state42保证复现一致性。我的习惯是在代码里加一行断言assert np.intersect1d(idx_tr, idx_te).size 0。4.3 坑二少数类别全被吃掉精度虚高全靠多数类撑现象整体精度看着有 85%但看混淆矩阵第 1 类、第 7 类、第 9 类这些样本量少的类别识别率不到 30%大量少数类样本被判成了邻近的多数类。原因Indian Pines 各类别样本数极不均衡最多的类别有 1000 多个样本最少的只有 20 个左右。即使分层采样少数类在训练集里也只有几个样本CNN 学不到有效特征SVM 的决策边界完全被多数类主导。解决给 CNN 加类别权重让少数类的 loss 贡献放大。Keras 里直接传class_weight参数即可sklearn 的compute_class_weight会自动算好权重字典。from sklearn.utils.class_weight import compute_class_weight class_weight compute_class_weight(balanced, classesnp.unique(y[idx_tr]), yy[idx_tr]) cw dict(zip(np.unique(y[idx_tr]), class_weight)) model.fit([X_spec[idx_tr], X_spat[idx_tr]], y[idx_tr], validation_data([X_spec[idx_te], X_spat[idx_te]], y[idx_te]), epochs50, batch_size64, class_weightcw, verbose1)加了class_weight之后整体精度可能反而会下降一两个百分点但少数类召回率会有明显提升综合看 kappa 系数通常是变好的。4.4 坑三SVM 参数没搜C 和 gamma 全靠默认值现象CNN 特征提取质量很高F 特征可视化已经明显分开但 SVM 分类精度就是上不去或者出现“全部分到某一类”的极端情况。原因sklearn 的 SVC 默认C1, gammascale在小样本高维特征上并不是好起点。gamma 过小决策边界过于平滑gamma 过大又会在特征空间中切出无数碎片把噪声当信号。RBF 核 SVM 对这两个参数极其敏感不搜索就等于靠玄学。解决用 GridSearchCV 在训练集特征上做 5 折交叉验证C 和 gamma 都按对数网格扫描。from sklearn.model_selection import GridSearchCV param_grid {C: [1, 10, 100], gamma: [0.001, 0.01, 0.1]} svm_cv GridSearchCV(SVC(kernelrbf, probabilityTrue), param_grid, cv5, scoringaccuracy) svm_cv.fit(F_tr, y[idx_tr]) print(best params:, svm_cv.best_params_) # {C: 10, gamma: 0.01} svm svm_cv.best_estimator_网格搜索不用把范围拉太大C 和 gamma 各三个点、5 折交叉验证一共只跑 45 次在几千个特征样本上分钟级就能跑完。我见过有人把 C 的范围设到 1e-5 到 1e5跑了一整夜还没出结果完全是浪费算力特征已经由 CNN 提得很干净时参数敏感度远低于用原始光谱训练。4.5 坑四CNN 训练没过拟合先崩了验证集精度卡死不涨现象训练集精度很快冲到 98%验证集精度从第 5 轮开始就在 75% 上下波动一直训练完 50 轮也没有改善。原因模型容量太大了。Indian Pines 训练集只有 2000 个样本两个卷积分支拼接后接 128 维全连接这个参数量对当前数据量是过剩的。另一个常见原因是 Dropout 加在了 concatenate 之后但光谱分支的 Flatten 层之后整段特征没有做正则化过拟合先从特征过度表达开始。解决两个方向都要动。结构上把 128 维全连接拆成 64 维Dropout 加到 0.5每个分支的 Flatten 之后各加一层 Dropout(0.3)。训练上启用早停不要硬跑满 50 轮。from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau early_stop EarlyStopping(monitorval_loss, patience10, restore_best_weightsTrue) reduce_lr ReduceLROnPlateau(monitorval_loss, factor0.5, patience5, min_lr1e-5) model.fit([X_spec[idx_tr], X_spat[idx_tr]], y[idx_tr], validation_data([X_spec[idx_te], X_spat[idx_te]], y[idx_te]), epochs50, batch_size64, callbacks[early_stop, reduce_lr], verbose1)restore_best_weightsTrue这个参数经常被忽略它会在训练中断时自动回滚到验证集 loss 最低那一轮的权重而不是返回最后一轮的模型。很多人以为自己用了早停就万事大吉结果拿到的还是过拟合后的最后一轮权重这是隐藏的坑。5. 验证闭环与参数微调混淆矩阵、Kappa 系数和两个实操技巧验证不是打印一个精度数字就完事的尤其是高光谱分类类别不均衡严重整体精度会被多数类带偏。项目文档里要求跑完模型后必须输出三样东西混淆矩阵、分类报告、分类结果图这三样能完整暴露模型到底错在哪里。5.1 混淆矩阵与三指标OA、AA、Kappa 一个都不能少import matplotlib.pyplot as plt from sklearn.metrics import confusion_matrix, classification_report, cohen_kappa_score svm_pred svm.predict(F_te) cm confusion_matrix(y[idx_te], svm_pred) oa np.trace(cm) / cm.sum() # 整体精度 aa np.mean(cm.diagonal() / cm.sum(axis1)) # 平均精度 kappa cohen_kappa_score(y[idx_te], svm_pred) # Kappa 系数 print(OA:, round(oa, 4), AA:, round(aa, 4), Kappa:, round(kappa, 4)) print(classification_report(y[idx_te], svm_pred, digits3))OA 是总体精度容易被多数类带偏AA 是每个类别精度的算术平均少数类表现差会直接拉低 AAKappa 系数衡量的是剔除随机因素后的一致性三个指标差距大时说明模型在少数类上翻车严重。参考数值上这套流程在 Indian Pines 上 OA 能到 92%~96% 区间AA 和 Kappa 通常比 OA 低 2~5 个百分点如果 AA 比 OA 低超过 10 个点回到 4.3 节去查类别权重。5.2 把分类结果画回原始图像一眼看出错在哪数值指标再高也看不出空间分布的问题分类结果图是排查“边界地物被抹平”“孤立噪声点”的关键工具。做法是把测试集中每个像素的预测结果写回 145×145 的网格背景像素置 0用 imshow 画出来。pred_map np.zeros((h, w), dtypenp.int32) mask gt 0 # 对每个有标签像素做预测这里为演示只对测试集像素回填 test_pos np.argwhere(mask) pred_map[gt 0] 0 for i, (row, col) in enumerate(test_pos): if i in idx_te: pred_map[row, col] svm_pred[list(idx_te).index(i)] plt.figure(figsize(10, 4)) plt.subplot(1, 2, 1) plt.imshow(gt, cmaptab20) plt.title(Ground Truth) plt.subplot(1, 2, 2) plt.imshow(pred_map, cmaptab20) plt.title(Prediction) plt.tight_layout() plt.savefig(model/classification_map.png, dpi150)画图时有一个效率细节不要对整幅图 145×145 逐像素预测Indian Pines 有 10249 个有效像素逐像素循环在 CPU 上要等很久。项目里的做法是先把所有有效像素的样本块一次性构造成数组model.predict批量前向然后把结果按索引回填我刚才为了配合测试集演示写成了循环实际项目里请用批量方式。5.3 参数微调顺序先粗后细别一上来就扫全参数整套流程跑通后真正花时间的是调参。我建议的参数扫描顺序是先固定降维参数 d130、d220 把整个 pipeline 跑通确认 CNN 和 SVM 都没有问题之后再回头扫 d1 和 d2最后微调 SVM 的 C 和 gamma。原因很简单降维参数影响的是输入数据质量CNN 结构影响的是特征质量SVM 参数只影响决策边界这个依赖链决定了调整顺序。调整阶段参数范围固定项观察指标降维参数d1 ∈ [20, 40]d2 ∈ [12, 25]d1 固定扫 d2再反过来SVM 精度曲线拐点CNN 结构filters ∈ [16, 32, 64]Dense ∈ [64, 128]降维参数用默认值验证集 loss 是否震荡SVM 参数C ∈ [1, 10, 100]gamma ∈ [0.001, 0.01, 0.1]CNN 权重固定Kappa 系数每轮扫描只改一个参数这是最基本的实验纪律。另外CNN 不用每次扫参都重新训练权重固定后特征也固定了SVM 参数扫描可以直接在 F_tr 和 F_te 上反复跑秒级出结果。项目logs目录里记录了我当时扫参的完整过程每个实验的配置和指标都有对应下载后可以直接对照。说一个我的真实习惯从那以后我每次跑高光谱分类实验都会强制走一遍“先出混淆矩阵和分类结果图再谈精度数字”的流程指标虚高但结果图上一片噪点的情况太常见了。拿到这份资源后建议先把 README 里列出的环境依赖配齐然后按 Part_1 → Part_2 → Part_3 的顺序把每一步的示例输出和 logs 里的结果对齐确认环境没问题后再开始改成自己的数据。希望帮到你。本文还有配套的精品资源点击获取
返回列表