如何正确地使用 BoW 训练 OpenCV SVM
How to train OpenCV SVM with BoW Properly
我无法训练 SVM 识别我的对象。我正在尝试使用 SURF + Bag Of Words + SVM 来做到这一点。我的问题是分类器没有检测到任何东西。结果全部为0.
这是我的代码:
Ptr<FeatureDetector> detector = FeatureDetector::create("SURF");
Ptr<DescriptorExtractor> descriptors = DescriptorExtractor::create("SURF");
string to_string(const int val) {
int i = val;
std::string s;
std::stringstream out;
out << i;
s = out.str();
return s;
}
Mat compute_features(Mat image) {
vector<KeyPoint> keypoints;
Mat features;
detector->detect(image, keypoints);
KeyPointsFilter::retainBest(keypoints, 1500);
descriptors->compute(image, keypoints, features);
return features;
}
BOWKMeansTrainer addFeaturesToBOWKMeansTrainer(String dir, BOWKMeansTrainer& bowTrainer) {
DIR *dp;
struct dirent *dirp;
struct stat filestat;
dp = opendir(dir.c_str());
Mat features;
Mat img;
string filepath;
#pragma loop(hint_parallel(4))
for (; (dirp = readdir(dp));) {
filepath = dir + dirp->d_name;
cout << "Reading... " << filepath << endl;
if (stat( filepath.c_str(), &filestat )) continue;
if (S_ISDIR( filestat.st_mode )) continue;
img = imread(filepath, 0);
features = compute_features(img);
bowTrainer.add(features);
}
return bowTrainer;
}
void computeFeaturesWithBow(string dir, Mat& trainingData, Mat& labels, BOWImgDescriptorExtractor& bowDE, int label) {
DIR *dp;
struct dirent *dirp;
struct stat filestat;
dp = opendir(dir.c_str());
vector<KeyPoint> keypoints;
Mat features;
Mat img;
string filepath;
#pragma loop(hint_parallel(4))
for (;(dirp = readdir(dp));) {
filepath = dir + dirp->d_name;
cout << "Reading: " << filepath << endl;
if (stat( filepath.c_str(), &filestat )) continue;
if (S_ISDIR( filestat.st_mode )) continue;
img = imread(filepath, 0);
detector->detect(img, keypoints);
bowDE.compute(img, keypoints, features);
trainingData.push_back(features);
labels.push_back((float) label);
}
cout << string( 100, '\n' );
}
int main() {
initModule_nonfree();
Ptr<DescriptorMatcher> matcher = DescriptorMatcher::create("FlannBased");
TermCriteria tc(CV_TERMCRIT_ITER + CV_TERMCRIT_EPS, 10, 0.001);
int dictionarySize = 1000;
int retries = 1;
int flags = KMEANS_PP_CENTERS;
BOWKMeansTrainer bowTrainer(dictionarySize, tc, retries, flags);
BOWImgDescriptorExtractor bowDE(descriptors, matcher);
string dir = "./positive_large", filepath;
DIR *dp;
struct dirent *dirp;
struct stat filestat;
cout << "Add Features to KMeans" << endl;
addFeaturesToBOWKMeansTrainer("./positive_large/", bowTrainer);
addFeaturesToBOWKMeansTrainer("./negative_large/", bowTrainer);
cout << endl << "Clustering..." << endl;
Mat dictionary = bowTrainer.cluster();
bowDE.setVocabulary(dictionary);
Mat labels(0, 1, CV_32FC1);
Mat trainingData(0, dictionarySize, CV_32FC1);
cout << endl << "Extract bow features" << endl;
computeFeaturesWithBow("./positive_large/", trainingData, labels, bowDE, 1);
computeFeaturesWithBow("./negative_large/", trainingData, labels, bowDE, 0);
CvSVMParams params;
params.kernel_type=CvSVM::RBF;
params.svm_type=CvSVM::C_SVC;
params.gamma=0.50625000000000009;
params.C=312.50000000000000;
params.term_crit=cvTermCriteria(CV_TERMCRIT_ITER,100,0.000001);
CvSVM svm;
cout << endl << "Begin training" << endl;
bool res=svm.train(trainingData,labels,cv::Mat(),cv::Mat(),params);
svm.save("classifier.xml");
//CvSVM svm;
svm.load("classifier.xml");
VideoCapture cap(0); // open the default camera
if(!cap.isOpened()) // check if we succeeded
return -1;
Mat featuresFromCam, grey;
vector<KeyPoint> cameraKeyPoints;
namedWindow("edges",1);
for(;;)
{
Mat frame;
cap >> frame; // get a new frame from camera
cvtColor(frame, grey, CV_BGR2GRAY);
detector->detect(grey, cameraKeyPoints);
bowDE.compute(grey, cameraKeyPoints, featuresFromCam);
cout << svm.predict(featuresFromCam) << endl;
imshow("edges", frame);
if(waitKey(30) >= 0) break;
}
return 0;
}
你应该知道我从一个现有项目中获得了参数并取得了很好的结果,所以我认为它们在我的代码中也会有用(但最终可能不会)。
我有310张正面图片和508张负面图片。我尝试使用相同数量的正面和负面图像,但结果是一样的。
我要检测的对象是汽车方向盘。这里是 my dataset.
你知道我做错了什么吗?谢谢!
首先,使用现有项目中的相同参数并不能证明您使用的参数是正确的。事实上,在我看来这完全是一种胡说八道的做法(无罪)。这是因为,SVM 参数直接受到数据集和描述符提取方法的影响。为了获得正确的参数,您必须进行 交叉验证 。因此,如果这些参数是从不同的识别任务中获得的,那将没有任何意义。例如,在我的面部验证项目中,gamma
和 C
的最佳参数分别为 0.0625 和 10。
您的方法的另一个重要问题是测试图像。据我从你的代码中看到的,你没有使用磁盘中的图像来测试你的分类器,所以从这里的其余部分我会做一些假设。如果您从相机获取的测试图像与您的正面图像不同,它将失败。我的意思是不同的;您必须确保您的测试图像仅由方向盘组成,因为您的训练图像仅包含方向盘。如果您的测试图像包含,例如带有它的汽车座椅,您的测试图像的 BoW 描述符将与您的火车图像 BoW 描述符完全不同。因此,简单地说,您的测试图像不应该包含方向盘和其他一些物体,它们应该只包含方向盘。
如果你满足这些,使用训练图像来测试你的系统是最基本的方法。即使在那种情况下你失败了,你也可能有一些实施问题。其他方法可以是这个;将你的训练数据分成两部分,这样你就有四个分区:
- 正面火车图像
- 负面火车图像
- 阳性测试图像
- 负面测试图片
仅使用训练图像来训练系统并使用测试图像对其进行测试。同样,您必须通过交叉验证指定参数。
除此之外,您可能还需要检查一些特定的步骤以便定位问题,然后再执行我之前写的内容:
- 每张图片检测到多少个关键点?相似的图像应该产生相似数量的关键点。
- 你知道 BoW 描述符是图像的 SURF 描述符的直方图。确保相似的图像产生相似的直方图(BoW 描述符)。您最好通过可视化直方图来检查这一点。
- 如果上一步都满足,那么问题很可能出在SVM训练这一步,这是非常重要的一步(也许是最重要的一步)。
我希望我能够强调交叉验证的重要性。进行交叉验证!
祝你好运!
我无法训练 SVM 识别我的对象。我正在尝试使用 SURF + Bag Of Words + SVM 来做到这一点。我的问题是分类器没有检测到任何东西。结果全部为0.
这是我的代码:
Ptr<FeatureDetector> detector = FeatureDetector::create("SURF");
Ptr<DescriptorExtractor> descriptors = DescriptorExtractor::create("SURF");
string to_string(const int val) {
int i = val;
std::string s;
std::stringstream out;
out << i;
s = out.str();
return s;
}
Mat compute_features(Mat image) {
vector<KeyPoint> keypoints;
Mat features;
detector->detect(image, keypoints);
KeyPointsFilter::retainBest(keypoints, 1500);
descriptors->compute(image, keypoints, features);
return features;
}
BOWKMeansTrainer addFeaturesToBOWKMeansTrainer(String dir, BOWKMeansTrainer& bowTrainer) {
DIR *dp;
struct dirent *dirp;
struct stat filestat;
dp = opendir(dir.c_str());
Mat features;
Mat img;
string filepath;
#pragma loop(hint_parallel(4))
for (; (dirp = readdir(dp));) {
filepath = dir + dirp->d_name;
cout << "Reading... " << filepath << endl;
if (stat( filepath.c_str(), &filestat )) continue;
if (S_ISDIR( filestat.st_mode )) continue;
img = imread(filepath, 0);
features = compute_features(img);
bowTrainer.add(features);
}
return bowTrainer;
}
void computeFeaturesWithBow(string dir, Mat& trainingData, Mat& labels, BOWImgDescriptorExtractor& bowDE, int label) {
DIR *dp;
struct dirent *dirp;
struct stat filestat;
dp = opendir(dir.c_str());
vector<KeyPoint> keypoints;
Mat features;
Mat img;
string filepath;
#pragma loop(hint_parallel(4))
for (;(dirp = readdir(dp));) {
filepath = dir + dirp->d_name;
cout << "Reading: " << filepath << endl;
if (stat( filepath.c_str(), &filestat )) continue;
if (S_ISDIR( filestat.st_mode )) continue;
img = imread(filepath, 0);
detector->detect(img, keypoints);
bowDE.compute(img, keypoints, features);
trainingData.push_back(features);
labels.push_back((float) label);
}
cout << string( 100, '\n' );
}
int main() {
initModule_nonfree();
Ptr<DescriptorMatcher> matcher = DescriptorMatcher::create("FlannBased");
TermCriteria tc(CV_TERMCRIT_ITER + CV_TERMCRIT_EPS, 10, 0.001);
int dictionarySize = 1000;
int retries = 1;
int flags = KMEANS_PP_CENTERS;
BOWKMeansTrainer bowTrainer(dictionarySize, tc, retries, flags);
BOWImgDescriptorExtractor bowDE(descriptors, matcher);
string dir = "./positive_large", filepath;
DIR *dp;
struct dirent *dirp;
struct stat filestat;
cout << "Add Features to KMeans" << endl;
addFeaturesToBOWKMeansTrainer("./positive_large/", bowTrainer);
addFeaturesToBOWKMeansTrainer("./negative_large/", bowTrainer);
cout << endl << "Clustering..." << endl;
Mat dictionary = bowTrainer.cluster();
bowDE.setVocabulary(dictionary);
Mat labels(0, 1, CV_32FC1);
Mat trainingData(0, dictionarySize, CV_32FC1);
cout << endl << "Extract bow features" << endl;
computeFeaturesWithBow("./positive_large/", trainingData, labels, bowDE, 1);
computeFeaturesWithBow("./negative_large/", trainingData, labels, bowDE, 0);
CvSVMParams params;
params.kernel_type=CvSVM::RBF;
params.svm_type=CvSVM::C_SVC;
params.gamma=0.50625000000000009;
params.C=312.50000000000000;
params.term_crit=cvTermCriteria(CV_TERMCRIT_ITER,100,0.000001);
CvSVM svm;
cout << endl << "Begin training" << endl;
bool res=svm.train(trainingData,labels,cv::Mat(),cv::Mat(),params);
svm.save("classifier.xml");
//CvSVM svm;
svm.load("classifier.xml");
VideoCapture cap(0); // open the default camera
if(!cap.isOpened()) // check if we succeeded
return -1;
Mat featuresFromCam, grey;
vector<KeyPoint> cameraKeyPoints;
namedWindow("edges",1);
for(;;)
{
Mat frame;
cap >> frame; // get a new frame from camera
cvtColor(frame, grey, CV_BGR2GRAY);
detector->detect(grey, cameraKeyPoints);
bowDE.compute(grey, cameraKeyPoints, featuresFromCam);
cout << svm.predict(featuresFromCam) << endl;
imshow("edges", frame);
if(waitKey(30) >= 0) break;
}
return 0;
}
你应该知道我从一个现有项目中获得了参数并取得了很好的结果,所以我认为它们在我的代码中也会有用(但最终可能不会)。
我有310张正面图片和508张负面图片。我尝试使用相同数量的正面和负面图像,但结果是一样的。 我要检测的对象是汽车方向盘。这里是 my dataset.
你知道我做错了什么吗?谢谢!
首先,使用现有项目中的相同参数并不能证明您使用的参数是正确的。事实上,在我看来这完全是一种胡说八道的做法(无罪)。这是因为,SVM 参数直接受到数据集和描述符提取方法的影响。为了获得正确的参数,您必须进行 交叉验证 。因此,如果这些参数是从不同的识别任务中获得的,那将没有任何意义。例如,在我的面部验证项目中,gamma
和 C
的最佳参数分别为 0.0625 和 10。
您的方法的另一个重要问题是测试图像。据我从你的代码中看到的,你没有使用磁盘中的图像来测试你的分类器,所以从这里的其余部分我会做一些假设。如果您从相机获取的测试图像与您的正面图像不同,它将失败。我的意思是不同的;您必须确保您的测试图像仅由方向盘组成,因为您的训练图像仅包含方向盘。如果您的测试图像包含,例如带有它的汽车座椅,您的测试图像的 BoW 描述符将与您的火车图像 BoW 描述符完全不同。因此,简单地说,您的测试图像不应该包含方向盘和其他一些物体,它们应该只包含方向盘。
如果你满足这些,使用训练图像来测试你的系统是最基本的方法。即使在那种情况下你失败了,你也可能有一些实施问题。其他方法可以是这个;将你的训练数据分成两部分,这样你就有四个分区:
- 正面火车图像
- 负面火车图像
- 阳性测试图像
- 负面测试图片
仅使用训练图像来训练系统并使用测试图像对其进行测试。同样,您必须通过交叉验证指定参数。
除此之外,您可能还需要检查一些特定的步骤以便定位问题,然后再执行我之前写的内容:
- 每张图片检测到多少个关键点?相似的图像应该产生相似数量的关键点。
- 你知道 BoW 描述符是图像的 SURF 描述符的直方图。确保相似的图像产生相似的直方图(BoW 描述符)。您最好通过可视化直方图来检查这一点。
- 如果上一步都满足,那么问题很可能出在SVM训练这一步,这是非常重要的一步(也许是最重要的一步)。
我希望我能够强调交叉验证的重要性。进行交叉验证!
祝你好运!