#include <opencv2/opencv.hpp> #include <iostream> #include <vector> using namespace cv; using namespace std; using namespace cv::ml; // 特征提取函数 vector<float> extractFeatures(Mat img) { resize(img, img, Size(64, 64)); // 统一尺寸 GaussianBlur(img, img, Size(5, 5), 0); // 去噪 Mat hsv; cvtColor(img, hsv, COLOR_BGR2HSV); // 转换色彩空间 // 计算H-S二维直方图 int histSize[] = { 16,16 }; float hRange[] = { 0,180 }, sRange[] = { 0,256 }; const float* ranges[] = { hRange, sRange }; int channels[] = { 0,1 }; Mat hist; calcHist(&hsv, 1, channels, Mat(), hist, 2, histSize, ranges, true, false); normalize(hist, hist, 0, 1, NORM_MINMAX); // 归一化 vector<float> features; features.assign((float*)hist.datastart, (float*)hist.dataend); return features; } int main() { // 加载数据集路径 vector<string> posPaths, negPaths; glob("dataset/qualified/*.jpg", posPaths); glob("dataset/unqualified/*.jpg", negPaths); // 提取特征 vector<vector<float>> features; vector<int> labels; // 处理合格样本 for (auto& path : posPaths) { Mat img = imread(path); if (!img.empty()) { features.push_back(extractFeatures(img)); labels.push_back(1); } } // 处理不合格样本 for (auto& path : negPaths) { Mat img = imread(path); if (!img.empty()) { features.push_back(extractFeatures(img)); labels.push_back(0); } } // 创建训练数据矩阵 Mat trainData(features.size(), features[0].size(), CV_32FC1); for (int i = 0; i < features.size(); ++i) trainData.row(i) = Mat(features[i]).t(); Mat labelsMat(labels); // 创建SVM模型 Ptr<SVM> svm = SVM::create(); svm->setType(SVM::C_SVC); svm->setKernel(SVM::RBF); svm->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER, 1000, 1e-6)); // 训练并保存模型 svm->train(trainData, ROW_SAMPLE, labelsMat); svm->save("peanut_model.xml"); cout << "模型训练完成!" << endl; return 0; }把以上程序改成,数据集有good ,atrophy ,spot ,broken ,peeling ,moldy 六个标签,且已经分为了训练集和测试集两组,更改加载数据集
时间: 2025-06-26 12:02:39 浏览: 19
<think>好的,我需要帮用户将现有的OpenCV C++程序从二分类问题改为多分类问题,特别是处理六个不同的标签。原程序是针对合格和不合格两类,现在数据集分为good、atrophy、spot、broken、peeling、moldy六个类别,并且已经分好了训练集和测试集。用户的主要需求是更改数据集的加载部分,并适应多标签的情况。
首先,我需要理解原代码的结构。原程序通过glob函数加载正样本和负样本的路径,然后提取特征,训练SVM模型。现在需要处理六个类别,并且每个类别有训练集和测试集。可能的数据集目录结构是每个类别下有train和test子目录,或者训练集和测试集分别放在不同的父目录下。用户没有明确说明目录结构,可能需要假设或询问,但作为示例,可能需要根据常见做法进行调整。比如,数据集可能有train和test两个文件夹,每个里面包含六个子文件夹,分别对应六个类别。
接下来,原程序中的标签是0和1,现在需要对应到0到5的六个类别。需要将每个类别的样本路径加载,并为它们分配正确的标签。例如,good对应0,atrophy对应1,依此类推。同时,原程序使用了SVM,而OpenCV的SVM本身是二分类器,但可以通过“一对多”或其他策略进行多分类。不过,用户可能希望保持使用SVM,但需要确认是否支持多类分类,或者是否需要改用其他模型,比如随机森林或KNN。但原代码使用的是SVM,可能用户希望继续使用,所以需要考虑如何调整。
不过,在OpenCV中,SVM的多分类需要通过多个二分类器组合实现,比如一对一或一对多。这可能比较复杂,或者用户可能希望使用其他多分类模型,比如MLP(多层感知机)或随机森林。但用户可能没有提到改变模型,所以可能需要保持SVM,但需要处理多标签。但原代码中的SVM设置是C_SVC,即支持多分类的SVM。查阅OpenCV文档,SVM::C_SVC是用于多类分类的,所以可以处理多个标签。因此,只需要确保标签正确设置为0到5即可。
接下来,修改数据集加载部分。原程序加载posPaths和negPaths,现在需要加载六个类别的训练和测试路径。可能的目录结构如下:
dataset/
train/
good/
*.jpg
atrophy/
*.jpg
...
test/
good/
*.jpg
atrophy/
*.jpg
...
或者训练集和测试集的路径可能需要分别处理,但用户可能希望将训练集和测试集分开处理。不过原程序目前只处理训练集,因为测试部分没有在代码中体现。原程序的目的是训练模型并保存,所以可能需要调整代码以加载训练集的数据,而测试部分可能在另一个程序中进行评估。用户的问题主要是更改加载数据集的部分,所以可能只需要处理训练集的加载,而测试集可能在另一个阶段使用。
因此,在代码中,需要为每个类别加载对应的训练图像路径。例如,训练集的路径可能是"dataset/train/good/*.jpg","dataset/train/atrophy/*.jpg"等。需要为每个类别生成路径,并分配对应的标签。
接下来,需要将原来的二分类标签(0和1)改为0到5的六个标签。例如,good是0,atrophy是1,spot是2,依此类推。因此,在加载每个类别的样本时,需要将对应的标签值赋予。
因此,修改后的代码需要遍历六个类别,对每个类别,使用glob函数加载其训练集路径,然后将这些路径添加到特征和标签中。
可能的步骤:
1. 定义类别名称和对应的标签。例如,使用一个vector或map来存储类别名称和对应的整数标签。
2. 对于每个类别,构造训练数据的路径模式,例如"dataset/train/{class_name}/*.jpg"。
3. 使用glob获取每个类别的所有图像路径。
4. 对每个路径处理,提取特征,并将对应的标签添加到labels数组中。
此外,原程序中的标签是int类型,所以需要将每个类别映射到0到5的整数。
例如,代码可能改为:
vector<string> classNames = {"good", "atrophy", "spot", "broken", "peeling", "moldy"};
vector<string> trainPaths;
vector<int> labels;
for (int i = 0; i < classNames.size(); ++i) {
string pattern = "dataset/train/" + classNames[i] + "/*.jpg";
vector<string> paths;
glob(pattern, paths);
for (auto& path : paths) {
Mat img = imread(path);
if (!img.empty()) {
features.push_back(extractFeatures(img));
labels.push_back(i); // 标签对应0到5
}
}
}
这样,每个类别的训练图像被加载,并赋予相应的标签。
同时,原程序的测试集部分可能也需要处理,但用户的问题可能只需要修改训练数据的加载,因为当前代码只训练模型并保存,测试部分可能不在这个程序中。但用户的问题描述中提到数据集已经分为训练集和测试集两组,所以可能需要在代码中同时处理训练和测试数据,但原程序只处理训练。可能用户希望将训练集用于训练模型,而测试集用于后续评估,但当前代码未涉及评估部分。因此,可能只需要修改训练数据的加载部分,而测试集的加载可能在其他代码中处理。
因此,修改后的代码需要遍历六个类别,加载每个类别的训练图像,并赋予对应的标签。然后继续提取特征,创建训练数据矩阵,训练SVM模型并保存。
此外,原程序中的labelsMat是使用Mat(labels)创建的,但原来的labels是vector<int>,而OpenCV的Mat可能需要特定的类型,例如CV_32SC1。需要确认labelsMat的类型是否正确。原代码中的labelsMat是Mat(labels),但可能需要显式转换为整数矩阵。例如:
Mat labelsMat(labels.size(), 1, CV_32SC1, &labels[0]);
或者使用类似的方式。但原程序中的代码是Mat labelsMat(labels),这可能在原二分类情况下有效,但在多分类情况下可能需要确保类型正确。
此外,SVM的训练数据需要确保标签是整数值,且正确对应。
总结修改步骤:
1. 定义六个类别名称,并为每个类别分配标签0到5。
2. 修改加载数据集的代码,遍历每个类别,加载对应的训练图像路径。
3. 对每个图像提取特征,并将对应的类别标签添加到labels数组中。
4. 确保训练数据矩阵和标签矩阵的格式正确。
5. 可能需要对SVM的参数进行调整,以适应多分类问题,但原代码中的SVM::C_SVC已经支持多类分类,因此可能不需要修改SVM的设置。
因此,代码的主要修改集中在数据加载部分,将原来的posPaths和negPaths替换为遍历六个类别的路径,并分配正确的标签。
可能的代码修改如下:
原来的:
// 加载数据集路径
vector<string> posPaths, negPaths;
glob("dataset/qualified/*.jpg", posPaths);
glob("dataset/unqualified/*.jpg", negPaths);
修改为:
// 定义类别和标签
vector<string> classNames = {"good", "atrophy", "spot", "broken", "peeling", "moldy"};
vector<string> trainPaths;
// 提取特征和标签
vector<vector<float>> features;
vector<int> labels;
for (int label = 0; label < classNames.size(); ++label) {
string pattern = "dataset/train/" + classNames[label] + "/*.jpg";
vector<string> paths;
glob(pattern, paths);
for (auto& path : paths) {
Mat img = imread(path);
if (!img.empty()) {
features.push_back(extractFeatures(img));
labels.push_back(label);
}
}
}
这样,每个类别的训练图像被加载,并赋予对应的标签0到5。
同时,原程序中的测试集部分可能也需要类似的处理,但当前代码仅处理训练,因此可能不需要在此处处理测试集。用户的问题可能仅需要修改训练数据的加载部分,因此上述修改应该足够。
另外,原程序中的SVM参数可能需要调整,因为原来的二分类问题改为六分类,可能需要调整SVM的超参数,如gamma或C值,但这属于模型调优的范畴,用户可能希望保持原有参数,或者需要进一步调整。但根据问题描述,用户主要关注数据集的加载,因此可能不需要修改模型参数。
测试部分可能需要在另一个程序中进行,例如加载测试集数据,提取特征,并使用训练好的模型进行预测,计算准确率等指标。但用户的问题不涉及这部分,所以可能不需要修改。
最后,确保所有路径正确,特别是数据集目录结构是否符合代码中的假设。例如,训练集是否位于dataset/train/各类别子目录下,图片格式是否为.jpg等。
综上,修改后的代码将加载六个类别的训练数据,并为每个类别分配正确的标签,使用SVM进行多分类训练。</think>以下是修改后的代码,主要改动集中在数据集加载部分,实现六分类数据读取:
```cpp
#include <opencv2/opencv.hpp>
#include <iostream>
#include <vector>
using namespace cv;
using namespace std;
using namespace cv::ml;
// 特征提取函数(保持不变)
vector<float> extractFeatures(Mat img) {
resize(img, img, Size(64, 64));
GaussianBlur(img, img, Size(5, 5), 0);
Mat hsv;
cvtColor(img, hsv, COLOR_BGR2HSV);
int histSize[] = { 16,16 };
float hRange[] = { 0,180 }, sRange[] = { 0,256 };
const float* ranges[] = { hRange, sRange };
int channels[] = { 0,1 };
Mat hist;
calcHist(&hsv, 1, channels, Mat(), hist, 2, histSize, ranges, true, false);
normalize(hist, hist, 0, 1, NORM_MINMAX);
vector<float> features;
features.assign((float*)hist.datastart, (float*)hist.dataend);
return features;
}
int main() {
// 定义类别标签映射
vector<string> categories = {"good", "atrophy", "spot", "broken", "peeling", "moldy"};
// 加载训练集
vector<vector<float>> features;
vector<int> labels;
// 遍历所有类别
for (int label = 0; label < categories.size(); ++label) {
// 构造训练集路径模式
string pattern = "dataset/train/" + categories[label] + "/*.jpg";
vector<string> paths;
glob(pattern, paths);
// 处理当前类别的样本
for (auto& path : paths) {
Mat img = imread(path);
if (!img.empty()) {
features.push_back(extractFeatures(img));
labels.push_back(label); // 使用数字标签0-5
}
}
}
// 创建训练数据矩阵(保持不变)
Mat trainData(features.size(), features[0].size(), CV_32FC1);
for (int i = 0; i < features.size(); ++i)
trainData.row(i) = Mat(features[i]).t();
// 注意标签矩阵类型改为CV_32SC1
Mat labelsMat(labels.size(), 1, CV_32SC1, labels.data());
// 创建SVM模型(可保持参数不变,但建议调整参数)
Ptr<SVM> svm = SVM::create();
svm->setType(SVM::C_SVC); // C-Support向量分类,支持多类
svm->setKernel(SVM::RBF);
svm->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER, 1000, 1e-6));
// 训练并保存模型
svm->train(trainData, ROW_SAMPLE, labelsMat);
svm->save("peanut_model.xml");
cout << "六分类模型训练完成!" << endl;
return 0;
}
```
主要修改点说明:
1. **目录结构适配**:
- 假设数据集按如下结构组织:
```
dataset/
train/
good/
*.jpg
atrophy/
*.jpg
spot/
*.jpg
broken/
*.jpg
peeling/
*.jpg
moldy/
*.jpg
test/
...(测试集结构同理)
```
2. **多标签支持**:
- 使用数字标签0-5分别对应六个类别
- 通过循环遍历类别目录实现自动标签分配
3. **数据类型调整**:
- 标签矩阵类型改为`CV_32SC1`(32位有符号整型)
- 保持特征提取部分不变,仍使用H-S直方图特征
建议后续改进方向:
1. 添加测试集验证代码
2. 调整SVM参数(建议增加迭代次数到10000)
3. 添加分类结果评估指标(混淆矩阵、准确率等)
4. 考虑使用更复杂的特征(如HOG+颜色直方图)
如果需要添加测试集验证功能,可以按照相同模式加载测试集数据,使用`svm->predict()`进行预测并计算准确率。
阅读全文
相关推荐



















