Add hungarian file
authorPeng Li <seudut@gmail.com>
Tue, 17 Jul 2018 13:15:13 +0000 (21:15 +0800)
committerPeng Li <seudut@gmail.com>
Tue, 17 Jul 2018 13:21:46 +0000 (21:21 +0800)
.gitignore
README.md
SConstruct
include/hungarian.h [new file with mode: 0644]
include/test.h [deleted file]
src/hungarian.cpp [new file with mode: 0644]
src/test.cpp [deleted file]
test/TestHungarian.cpp [new file with mode: 0644]
test/test-unittest.cpp [deleted file]

index 4fc836e..645f639 100644 (file)
@@ -1,2 +1,3 @@
 src/*.o
 *.o
+.sconsign.dblite
index 36d7542..13c713b 100644 (file)
--- a/README.md
+++ b/README.md
@@ -1,7 +1,12 @@
 Tracker++ cpp version on Linux (arm)
 
 ## install dependencies
-`apt-get install liblog4cpp5-dev libopencv-dev`
+
+- log4cpp : logger utils
+- opencv
+- eigen : matrix library of C++
+
+`apt-get install liblog4cpp5-dev libopencv-dev libeigen3-dev`
 
 ## Build 
 
index 4b4f63b..fa54472 100644 (file)
@@ -8,7 +8,7 @@ AddOption('--all', dest='all', action='store_true', help='Build all include test
 
 env = Environment(CXX="g++", 
                 CPPPATH=['#include'],
-                CCFLAGS=['-Wall', '-std=c++11'])
+                CCFLAGS=['-Wall', '-std=c++11', '-O2'])
 
 env.Append(LIBS = ['tracker'])
 env.ParseConfig("pkg-config --libs opencv log4cpp")
diff --git a/include/hungarian.h b/include/hungarian.h
new file mode 100644 (file)
index 0000000..64b8596
--- /dev/null
@@ -0,0 +1,15 @@
+#ifndef _HUNGARIAN_H_
+#define _HUNGARIAN_H_
+#include <eigen3/Eigen/Dense>
+
+// C++ version of linear_sum_assignment() in python scipy
+// https://docs.scipy.org/doc/scipy-0.18.1/reference/generated/scipy.optimize.linear_sum_assignment.html
+// http://csclab.murraystate.edu/~bob.pilgrim/445/munkres.html
+//
+
+// @param cost_matrix The cost matrix
+// @param row_ind, col_ind an array of row indices and one of corresponding column indices giving the optimal assignment.
+// @return the cost of the assignment
+int linear_sum_assignment(const Eigen::MatrixXi& cost_matrix, Eigen::VectorXi& row_ind, Eigen::VectorXi& col_ind);
+
+#endif // _HUNGARIAN_H_
diff --git a/include/test.h b/include/test.h
deleted file mode 100644 (file)
index cf43d54..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-#ifndef __TEST_H__
-#define __TEST_H__
-
-int sum(int a, int b);
-
-#endif // __TEST_H__
diff --git a/src/hungarian.cpp b/src/hungarian.cpp
new file mode 100644 (file)
index 0000000..9b46613
--- /dev/null
@@ -0,0 +1,336 @@
+#include <iostream>
+#include <algorithm>
+#include "hungarian.h"
+
+using namespace std;
+using namespace Eigen;
+
+class Hungary;
+
+int step_one(Hungary& state);
+int step_two(Hungary& state);
+int step_three(Hungary& state);
+int step_four(Hungary& state);
+int step_five(Hungary& state);
+int step_six(Hungary& state);
+
+class Hungary
+{
+public:
+    Hungary(const MatrixXi& cost){
+        this->cost = cost;
+        this->row_uncovered = VectorXi::Ones(cost.rows());
+        this->col_uncovered = VectorXi::Ones(cost.cols());
+        Z0_r = 0;
+        Z0_c = 0;
+        this->mark = MatrixXi::Zero(cost.rows(), cost.cols());
+    };
+    ~Hungary(){};
+
+    void print(){
+        cout <<"Cost:\n" << cost << "\nMark:\n" << mark << endl;
+        cout <<"Row uncovered :[" << row_uncovered.transpose() << "], col uncovered: [" << col_uncovered.transpose() << "]" << endl;
+    }
+
+public:
+    void clearCovers(){
+        this->row_uncovered.setOnes();
+        this->col_uncovered.setOnes();
+    }
+    
+public :
+    // The cost matrix, cols() should > rows(). If not, transpose it first.
+    MatrixXi cost;
+    // The mark matrix. the value of the element is 0 (None), 1 (means the starred), 2 (primed)
+    MatrixXi mark;
+    // the covered state of the rows (0: covered; 1: nocovered)
+    VectorXi row_uncovered;
+    // the covered state of the column (0: covered; 1: nocovered)
+    VectorXi col_uncovered;
+    // the position of Z0, used in step 5
+    int Z0_r;
+    int Z0_c;
+    int min_value;
+};
+
+int linear_sum_assignment(const MatrixXi& cost_matrix, VectorXi& row_ind, VectorXi& col_ind)
+{
+    // The algorithm expects more columns than rows in the cost matrix.
+    MatrixXi correct_matrix = cost_matrix;
+    bool is_transposed = false;
+    if (cost_matrix.cols() < cost_matrix.rows()){
+        cout << "cols < rows, transpose." << endl;
+        correct_matrix = cost_matrix.transpose();
+        is_transposed = true;
+    }
+    Hungary state(correct_matrix);
+    cout << "Cost Matrix: \n" << correct_matrix << endl;;
+
+    bool done = false;
+    int next = 1;
+    int pre_step = 1;
+    while (!done){
+        pre_step = next;
+        switch(next)
+        {
+            case 1:
+                next = step_one(state);
+                break;
+            case 2:
+                next = step_two(state);
+                break;
+            case 3:
+                next = step_three(state);
+                break;
+            case 4:
+                next = step_four(state);
+                break;
+            case 5:
+                next = step_five(state);
+                break;
+            case 6:
+                next = step_six(state);
+                break;
+            case 7:
+                done = true;
+                break;
+        }
+        cout << "After step: " << pre_step << endl;
+        state.print();
+    }
+    MatrixXi mark = state.mark;
+    if (is_transposed){
+        mark = state.mark.transpose();
+    }
+    cout << "Done" << endl << mark << endl;
+    int sum = (mark.array() * cost_matrix.array()).sum();
+    cout << "Sum :" << sum << endl;
+    
+
+    // return the array of the positions
+    row_ind = VectorXi::Zero(mark.cols());
+    col_ind = VectorXi::Zero(mark.cols());
+    VectorXi::Index max_index;
+    int point = 0;
+    for (int i = 0; i < mark.rows(); i++){
+        if (mark.row(i).maxCoeff(&max_index)){
+            row_ind(point) = i;
+            col_ind(point) = max_index;
+            point++;
+        }
+    }
+    return sum;
+}
+
+
+// Step 1:
+// For each row of the matrix, find the smallest element and subtract
+// it from every element in its row. Go to Step 2.
+int step_one(Hungary& state)
+{
+    state.cost.colwise() -= state.cost.rowwise().minCoeff();
+    return 2;
+}
+
+// Step 2:
+// Find a zero (Z) in the resulting matrix. If there is no starred zero in its row or column,
+// star Z. Repeat for each elements in the matrix. Go to Step 3.
+int step_two(Hungary& state)
+{
+    for (int i = 0; i < state.cost.rows(); i++){
+        for (int j = 0; j < state.cost.cols(); j++)
+            if (state.row_uncovered(i) == 1 && state.col_uncovered(j) == 1 && state.cost(i, j) == 0){
+                state.mark(i, j) = 1;
+                state.col_uncovered(j) = 0;
+                state.row_uncovered(i) = 0;
+            }
+    }
+    state.clearCovers();
+    return 3;
+}
+
+// Step 3:
+// Cover each column containing a starred zero. If K columns are covered,
+// the starred zeros describe a complete set of unique assignments. In this case,
+// Go to DONE, otherwise, Go to Step 4.
+int step_three(Hungary& state)
+{
+    MatrixXi m1 = (state.mark.array() == 1).select(state.mark, 0);
+    state.col_uncovered = m1.colwise().any();
+    //for (int i = 0; i < state.col_uncovered.size(); i++)
+    //    if (m1.col(i).any())
+    //        state.col_uncovered(i) = 0;
+        //state.col_uncovered(i) = (state.col_uncovered(i) == 1) ? 0 : 1;
+    state.col_uncovered = (state.col_uncovered.array() == 1).select(0, VectorXi::Ones(state.col_uncovered.size()));
+    int next = 4;
+    if (m1.sum() == m1.rows())
+        next = 7;
+    return next;
+}
+
+// Step 4 :
+// Find a noncovered zero and prime it. If there is no starred zero in the row
+// constaining this primed zero, Go to Step 5. Otherwise, cover this row and uncover the column
+// containing the starred zero. Continue in this manner until there are no uncovered zeros left.
+// Save the smallest uncovered value and Go to Step 6.
+//
+//
+// find_uncovered_zero 
+//   - find zero or minimal value in the uncovered elements. 
+//   Return 0 if zero found, or the minimal element.
+int find_uncovered_zero(Hungary& state, int& row, int& col)
+{
+    MatrixXi cover = state.row_uncovered * state.col_uncovered.transpose();
+    int min_value = 0;
+    for (int i = 0; i < cover.rows(); i++)
+        for (int j = 0; j < cover.cols(); j++){
+            if (cover(i, j) != 0){
+                if (state.cost(i, j) == 0){
+                    row = i;
+                    col = j;
+                    return 0;
+                } else {
+                    min_value = (min_value > 0) ? min(min_value, state.cost(i, j)) : state.cost(i, j);
+                }
+            }
+        }
+    return min_value;
+}
+
+int step_four(Hungary& state)
+{
+    while(true)
+    {
+        int rr = 0;
+        int cc = 0;
+        // find a nocovered zero
+        int min = find_uncovered_zero(state, rr, cc);
+        if(min == 0){
+            // prime it
+            state.mark(rr, cc) = 2;
+            // If no starred zero in its row
+            if ((state.mark.row(rr).array() == 1).any() == 0){
+                state.Z0_r = rr;
+                state.Z0_c = cc;
+                return 5;
+            } else {
+                // Otherwise, cover this row
+                state.row_uncovered(rr) = 0;
+                // uncover the column
+                for (int j = 0; j < state.mark.cols(); j++){
+                    if (state.mark(rr, j) == 1){
+                        state.col_uncovered(j) = 1;
+                    }
+                }
+            }
+        } else {
+            state.min_value = min;
+            return 6;
+        }
+    }
+}
+
+// Step 5:
+//   Construct a seriee of alternating primed and starred zeros as follows,
+//   Let Z0 represent the uncovered primed zero found in Step 4.
+//   Let Z1 denote the starred zero in the column of Z0 (if any).
+//   Let Z2 denote the primed zero in the row of Z1 (there will always be one).
+//   Continue until the series terminates at a primed zero that has no starred
+//   zero in its column. Unstar each starred zero of the series, star each primed 
+//   zero of the series, erase all primes and uncover every line in the matrix. Return to Step 3
+int step_five(Hungary& state)
+{
+    MatrixXi path = MatrixXi::Zero(state.cost.rows() + state.cost.cols(), 2);
+    int count = 0;
+    path(count, 0) = state.Z0_r;
+    path(count, 1) = state.Z0_c;
+
+    MatrixXi m1 = (state.mark.array() == 1).select(state.mark, 0);
+    MatrixXi m2 = (state.mark.array() == 2).select(state.mark, 0);
+    MatrixXi::Index index;
+    while(true){
+        // Z1, find the starred zero in the column of Z0
+        int max = m1.col(path(count, 1)).maxCoeff(&index);
+        if (max != 1)
+            break;
+        else {
+            count++;
+            path(count, 0) = index;
+            path(count, 1) = path(count - 1, 1);
+        }
+
+        // Z2, find the primed zero in the row of Z1;
+        max = m2.row(path(count, 0)).maxCoeff(&index);
+        if (max != 2){
+            cout << "Error, should never reach here" << endl;
+        } else {
+            count++;
+            path(count, 0) = path(count - 1, 0);
+            path(count, 1) = index;
+        }
+    }
+
+    // terminates
+    for (int i = 0; i < count + 1; i++){
+        if (state.mark(path(i, 0), path(i, 1)) == 1)
+            state.mark(path(i, 0), path(i, 1)) = 0;
+        else
+            state.mark(path(i, 0), path(i, 1)) = 1;
+    }
+
+    state.clearCovers();
+
+    // erase all prime markings
+    state.mark = (state.mark.array() == 2).select(0, state.mark);
+    return 3;
+}
+
+// Step 6:
+//    Add the value found in Step 4 to every element of each covered row, and substract it 
+//    from every element of each uncovered column.
+//    Return to Step 4 without altering any stars, primes, or covered lines.
+//
+int step_six(Hungary& state)
+{
+    for (int i = 0; i < state.mark.rows(); i++)
+        for (int j = 0; j < state.mark.cols(); j++){
+            if (state.row_uncovered(i) == 0)
+                state.cost(i, j) += state.min_value;
+            if (state.col_uncovered(j) == 1)
+                state.cost(i, j) -= state.min_value;
+        }
+    return 4;
+}
+
+/*
+int main()
+{
+    Matrix3i C;
+//    MatrixXi C2(4, 3);
+//
+    C << 1, 2, 3,
+         2, 4, 2,
+         3, 6, 9;
+
+    Matrix3i M;
+    M << 0, 1, 2,
+         0, 0, 0,
+         1, 0, 0;
+
+//    C2 << 4, 1, 3,
+//         2, 4, 2,
+//         3, 6, 9,
+//         2, 6, 3;
+
+    Vector3i vv;
+    //Matrix3i m1 = (M.array() == 1).select(0, MatrixXi::Ones(M.cols(), M.rows()));
+    //cout << m1.colwise().sum().transpose() << endl;
+    //vv = vv.rowwise() 
+
+    VectorXi row_ind, col_ind;
+
+    //MatrixXi RR = MatrixXi::Random(10, 10);
+    linear_sum_assignment(C, row_ind, col_ind);
+    cout << "row: [" << row_ind.transpose() << "], col: [" << col_ind.transpose() << "]" << endl;
+}
+*/
diff --git a/src/test.cpp b/src/test.cpp
deleted file mode 100644 (file)
index fef6dc4..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-#include "test.h"
-
-#include <iostream>
-
-int sum(int a, int b)
-{
-    return a + b;
-}
diff --git a/test/TestHungarian.cpp b/test/TestHungarian.cpp
new file mode 100644 (file)
index 0000000..3cb29b2
--- /dev/null
@@ -0,0 +1,24 @@
+#include "hungarian.h"
+#include "gtest/gtest.h"
+
+using namespace std;
+using namespace Eigen;
+
+TEST(Hungarian, Verify)
+{
+    Matrix3i C;
+    C << 1, 2, 3,
+         2, 4, 2,
+         3, 6, 9;
+
+    VectorXi row_ind, col_ind;
+    int ret = linear_sum_assignment(C, row_ind, col_ind);
+    Vector3i expect_row_ind, expect_col_ind;
+
+    expect_row_ind << 0, 1, 2;
+    expect_col_ind << 1, 2, 0;
+
+    EXPECT_EQ(ret, 7);
+    EXPECT_TRUE(expect_row_ind == row_ind);
+    EXPECT_TRUE(expect_col_ind == col_ind);
+}
diff --git a/test/test-unittest.cpp b/test/test-unittest.cpp
deleted file mode 100644 (file)
index a7459d6..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-#include "test.h"
-#include "gtest/gtest.h"
-
-TEST(FactorialTest, Negative) {
-  // This test is named "Negative", and belongs to the "FactorialTest"
-  // test case.
-  EXPECT_EQ(2, sum(1, 2));
-  //EXPECT_GT(Factorial(-10), 0);
-
-}
-
-