Ask Your Question
0

Simple SVM example -- not returning expected results

asked 2017-01-27 17:32:20 -0600

Hey all --

I took a class on ML a long time ago in college, and now I'm trying to use OpenCV's ML library to re-teach it to myself. I wanted to through a simple classification example to teach myself how it works, but so far it isn't going as expected.

I wanted to:

  • create two groups of points -- points below the line y = x (labelled -1), and points above it (labeled 1),
  • Feed the set of points into a SVM,
  • Predict the classification of an obvious test point as a test case, and
  • Draw the support vectors

My code is at the bottom of the post (using Cinder as a graphical wrapper).

The code seems to run fine, but the simple validations I'm running are both wrong:

  • When asked to classify a point at y = 0, the SVM incorrectly returns 1 as the classification.
  • It returns the support vectors at approximately (0, 0) -- my understanding is that it should return at least two support vectors (one on each side of the classification hyperplane), and the support vector should be an actual point...

There doesn't seem to be a lot of tutorials for how to use the OpenCV3 SVM class; does anyone have any experience / advice to share on how to get this to work?

#include "cinder/app/App.h"
#include "cinder/app/RendererGl.h"
#include "cinder/gl/gl.h"
#include "cinder/Rand.h"

#include <opencv2\core\core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2\ml\ml.hpp>

using namespace ci;
using namespace ci::app;
using namespace std;

class exampleClassificationApp : public App {
  public:
    void setup() override;
    void update() override;
    void draw() override;

    std::vector<cv::Point> mPoints;
    std::vector<int> mLabels;
    cv::Ptr<cv::ml::SVM> mSVM;
};

void exampleClassificationApp::setup() {
    for (int i = 0; i < 100; i++) {
        float x = Rand::randFloat(0, ci::app::getWindowWidth());
        float y = Rand::randFloat(0, x);
        mPoints.push_back(cv::Point(x, y));
        mLabels.push_back(-1.0);
    }
    for (int i = 0; i < 100; i++) {
        float x = Rand::randFloat(0, ci::app::getWindowWidth());
        float y = Rand::randFloat(x, ci::app::getWindowHeight());
        mPoints.push_back(cv::Point(x, y));
        mLabels.push_back(1.0);
    }
    cv::Mat trainingData(mPoints.size(), 2, CV_32FC1, mPoints.data());
    cv::Mat trainingLabels(mLabels.size(), 1, CV_32SC1, mLabels.data());

    std::printf("Created SVM\n");
    mSVM = cv::ml::SVM::create();

    std::printf("Setting parameters...\n");
    mSVM->setType(cv::ml::SVM::C_SVC);
    mSVM->setKernel(cv::ml::SVM::LINEAR);
    mSVM->setC(1.0);

    std::printf("Training...\n");
    mSVM->train(trainingData, cv::ml::ROW_SAMPLE, trainingLabels);

    float data[2] = { 0, ci::app::getWindowWidth() / 2.0 };
    cv::Mat query(1, 2, CV_32F, data);
    int response = mSVM->predict(query);

    std::printf("Asked SVM where to classify %f, %f -- result was %d\n", query.at<float>(0,0), query.at<float>(0, 1), response);
}

void exampleClassificationApp::update() {

}

void exampleClassificationApp::draw() {
    gl::clear( Color( 0, 0, 0 ) );
    for (int i = 0; i < mPoints.size(); i++) {
        if (mLabels[i] == -1.0) {
            gl::color(Color(1, 0, 0));
        }
        else {
            gl::color(Color(0 ...
(more)
edit retag flag offensive close merge delete

1 answer

Sort by ยป oldest newest most voted
1

answered 2017-01-28 01:38:23 -0600

berak gravatar image

updated 2017-01-28 21:39:31 -0600

  • please have a look at a related sample

  • "When asked to classify a point at y = 0, the SVM incorrectly returns 1"

    you're actually testing a point at (0,W/2), not (W/2,0) so the classification is correct.

  • cv::Mat trainingData(mPoints.size(), 2, CV_32FC1, mPoints.data()); <-- you cannot put a vector of integer points into a float Mat (which just casts the pointer instead of converting individual elements). that's also, why the support vector has wrong values. if you still want to use this contruct, please use Point2f , not Point

  • mSVM->getSupportVectors() retrieves the single compressed support vector for a linear SVM, if you check mSVM->getUncompressedSupportVectors() you'll get a nice diagonal:


[174.18019, 168.38301;
 228.93629, 216.20596;
 10.42392, 10.316812;
 342.7677, 342.85413;
 21.18305, 32.687538;
 93.60273, 96.210686;
 399.43951, 399.63388]

(using Cinder as a graphical wrapper). -- that's a bit sad, because it makes your code irreproducable.

here's modified code for reference:

#include <opencv2/opencv.hpp>

using namespace cv;
using namespace std;

class exampleClassificationApp {
  public:
    void setup(int w, int h);
    void draw(int w, int h);

    std::vector<cv::Point2f> mPoints;
    std::vector<int> mLabels;
    cv::Ptr<cv::ml::SVM> mSVM;
};

void exampleClassificationApp::setup(int w,int h) {
    for (int i = 0; i < 100; i++) {
        float x = cv::theRNG().uniform(0.0f, float(w));
        float y = cv::theRNG().uniform(0.0f, float(x));
        mPoints.push_back(cv::Point2f(x, y));
        mLabels.push_back(-1);
    }
    for (int i = 0; i < 100; i++) {
        float x = cv::theRNG().uniform(0.0f, float(w));
        float y = cv::theRNG().uniform(x, float(h));
        mPoints.push_back(cv::Point2f(x, y));
        mLabels.push_back(1);
    }
    cv::Mat trainingData(mPoints.size(), 2, CV_32FC1, mPoints.data());
    cv::Mat trainingLabels(mLabels.size(), 1, CV_32SC1, mLabels.data());

    mSVM = cv::ml::SVM::create();

    mSVM->setType(cv::ml::SVM::C_SVC);
    mSVM->setKernel(cv::ml::SVM::LINEAR);
    mSVM->setC(1.0);

    mSVM->train(trainingData, cv::ml::ROW_SAMPLE, trainingLabels);

    float data[2] = { 0, float(w) / 2.0f };
    cv::Mat query(1, 2, CV_32F, data);
    int response = mSVM->predict(query);

    std::printf("Asked SVM where to classify %f, %f -- result was %d\n", query.at<float>(0,0), query.at<float>(0, 1), response);
}

void exampleClassificationApp::draw(int w, int h) {
    Mat img(h,w,CV_8UC3,Scalar::all(60));
    for (int i = 0; i < mPoints.size(); i++) {
        Point p = mPoints[i];
        p.y = h - p.y; // y-axis points down in opencv
        if (mLabels[i] == -1) {
            circle(img, p, 5, Scalar(200, 0, 0), -1);
        }
        else {
            circle(img, p, 5, Scalar(0,0,200), -1);
        }
    }
    imshow("img", img);
    waitKey();
    cv::Mat supports;
    if (mSVM->isTrained()) {
        supports = mSVM->getSupportVectors();
        std::cout << "Support Vectors are " << supports << std::endl;
        supports = mSVM->getUncompressedSupportVectors();
        std::cout << "uncompressed Support Vectors are " << supports << std::endl;

    }   
}

int main() {
    exampleClassificationApp app;
    app.setup(400,400);
    app.draw(400,400);
    return 0;
}
edit flag offensive delete link more

Comments

thanks! A couple of dumb mistakes on my end, but thanks so much for the clarification!

nathan.lachenmyer gravatar imagenathan.lachenmyer ( 2017-01-28 09:29:33 -0600 )edit

So I tried converting all points to a cv::Point2f and then converting them to a matrix, and then calling mSVM->getUncompressedSupportVectors(), but my support vectors are all around (0, 0) within error (something like 1e-47). Any thoughts on why that might be?

nathan.lachenmyer gravatar imagenathan.lachenmyer ( 2017-01-28 14:44:20 -0600 )edit

my guess: you forgot to channge the vector<Point2f>mPoints . see edit above, please.

berak gravatar imageberak ( 2017-01-28 21:08:09 -0600 )edit

Question Tools

1 follower

Stats

Asked: 2017-01-27 17:32:20 -0600

Seen: 614 times

Last updated: Jan 28 '17