Skip to content

Commit 9803a9e

Browse files
committed
Add 'ekf.hpp' and split examples
1 parent bd16bfc commit 9803a9e

8 files changed

+354
-98
lines changed

Diff for: CMakeLists.txt

+8-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
cmake_minimum_required(VERSION 2.6)
2-
project("cx_example")
2+
project("opencx")
33
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
44
find_package(OpenCV REQUIRED)
5-
add_executable(${PROJECT_NAME} "cx_example.cpp")
6-
target_link_libraries(${PROJECT_NAME} ${OpenCV_LIBS})
5+
6+
add_executable("example_cx" "example_cx.cpp")
7+
add_executable("example_sx" "example_sx.cpp")
8+
add_executable("example_ekf" "example_ekf.cpp")
9+
target_link_libraries("example_cx" ${OpenCV_LIBS})
10+
target_link_libraries("example_sx" ${OpenCV_LIBS})
11+
target_link_libraries("example_ekf" ${OpenCV_LIBS})

Diff for: README.md

+18-5
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,26 @@
11
## OpenCX: Sunglok's OpenCV Extension
2-
_OpenCX_ aims to provide extended functionality and tools to OpenCV for more convenience. It consists of a single header file, `opencx.hpp`, which only depends on OpenCV in C++. Just include the file to your project. It will work without complex configuration and dependency. OpenCX is [Beerware](http://en.wikipedia.org/wiki/Beerware) so that it is free to use and distribute.
2+
_OpenCX_ aims to provide extended functionality and tools to [OpenCV](https://opencv.org/) for more convenience. It consists of several header files, `opencx.hpp` and others, which only depend on OpenCV in C++. Just include the file to your project. It will work without complex configuration and dependency. OpenCX is [Beerware](http://en.wikipedia.org/wiki/Beerware) so that it is free to use and distribute.
33

44
* Homepage: <https://github.com/sunglok/opencx>
55

66
### File Description
7-
* `opencx.hpp` includes my extension to OpenCV
8-
* `opensx.hpp` includes my extension which does not depend on OpenCV
9-
* `cx_example.cpp` contains examples of using OpenCX.
10-
* `README.md` is this file which describes basic introduction of OpenCX.
7+
* `opencx.hpp`: My OpenCV extension
8+
* `opensx.hpp`: My C++ extension not depending on OpenCV
9+
* `ekf.hpp`: My implementation of the [extended Kalman filter](http://en.wikipedia.org/wiki/Extended_Kalman_filter)
10+
* `example_cx.cpp`: Examples using `opencx.hpp`
11+
* `example_sx.cpp`: Examples using `opensx.hpp`
12+
* `example_ekf.cpp`: Pose estimation examples using `cv::KalmanFilter` and `cx::EKF`
13+
* `README.md`: A brief introduction to OpenCX
14+
* `CMakeList.txt`: A [CMake](https://cmake.org/) script to build examples
15+
16+
### Running Examples
17+
1. `git clone https://github.com/sunglok/opencx.git`: Clone OpenCX repository
18+
2. `mkdir opecx/build && cd opencx/build`: Make a build directory
19+
3. `cmake ..`: Prepare to build (generating `Makefile` file or [MSVS](https://visualstudio.microsoft.com/) solution/project files)
20+
* In Windows, you need to specify the location of OpenCV (where `OpenCVConfig.cmake` is exist) as follows: `cmake .. -D OpenCV_DIR:PATH="C:\Your\OpenCV\Dir"`.
21+
* You can use [cmake-gui](https://cmake.org/runningcmake/) for easier CMake configuration.
22+
4. `make`: Build examples
23+
* In Windows, please open `opencx.sln` and build projects.
1124

1225
### License
1326
* [Beerware](http://en.wikipedia.org/wiki/Beerware)

Diff for: ekf.hpp

+229
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
/**
2+
* This file is a part of OpenCX, especially about extended Kalman filter (EKF).
3+
* - Homepage: https://github.com/sunglok/opencx
4+
*/
5+
6+
/**
7+
* ----------------------------------------------------------------------------
8+
* "THE BEER-WARE LICENSE" (Revision 42):
9+
* <[email protected]> wrote this file. As long as you retain this notice you
10+
* can do whatever you want with this stuff. If we meet some day, and you think
11+
* this stuff is worth it, you can buy me a beer in return. Sunglok Choi
12+
* ----------------------------------------------------------------------------
13+
*/
14+
15+
#ifndef __EKF__
16+
#define __EKF__
17+
18+
#include "opencv2/opencv.hpp"
19+
20+
namespace cx
21+
{
22+
/**
23+
* @brief Extended Kalman Filter (EKF)
24+
*
25+
* The extended Kalman filter is a nonlinear extension of the Kalman filter which linearizes nonlinear state transition and observation models at the point of the current state.
26+
* You can make your EKF application by inheriting this class and overriding six functions: transitModel, transitJacobian, transitNoise, observeModel, observeJacobian, and observeNoise.
27+
*
28+
* @see Extended Kalman Filter (Wikipedia), http://en.wikipedia.org/wiki/Extended_Kalman_filter
29+
* @see Welch, The Kalman Filter, http://www.cs.unc.edu/~welch/kalman/
30+
* @see Welch and Bishop, An Introduction to the Kalman Filter, UNC-Chapel Hill, TR95-041, 2006, <a href="http://www.cs.unc.edu/~welch/kalman/kalmanIntro.html">PDF</a>
31+
*/
32+
class EKF
33+
{
34+
public:
35+
/**
36+
* Initialize the state variable and covariance
37+
* @param state_dim The dimension of state variable
38+
* @return True if successful (false if failed)
39+
*/
40+
virtual bool initialize(int state_dim, int state_type = CV_64F)
41+
{
42+
return initialize(cv::Mat::zeros(state_dim, 1, state_type), cv::Mat::eye(state_dim, state_dim, state_type));
43+
}
44+
45+
/**
46+
* Initialize the state variable and covariance with the given values
47+
* @param state_vec The given state variable
48+
* @param state_cov The given state covariance
49+
* @return True if successful (false if failed)
50+
*/
51+
virtual bool initialize(cv::InputArray state_vec, cv::InputArray state_cov = cv::noArray())
52+
{
53+
CV_DbgAssert(!state_vec.empty());
54+
state_vec.copyTo(m_state_vec);
55+
if (m_state_vec.rows < m_state_vec.cols) m_state_vec = m_state_vec.t();
56+
CV_DbgAssert(m_state_vec.cols == 1);
57+
58+
int dim = m_state_vec.rows;
59+
if (!state_cov.empty())
60+
{
61+
state_cov.copyTo(m_state_cov);
62+
CV_DbgAssert(m_state_cov.rows == dim && m_state_cov.cols == dim);
63+
}
64+
else m_state_cov = cv::Mat::eye(dim, dim, m_state_vec.type());
65+
return true;
66+
}
67+
68+
/**
69+
* Predict the state variable and covariance from the given control input
70+
* @param control The given control input
71+
* @return True if successful (false if failed)
72+
*/
73+
virtual bool predict(cv::InputArray control)
74+
{
75+
// Calculate 'F' and 'Q'
76+
cv::Mat u = control.getMat();
77+
if (u.rows < u.cols) u = u.t();
78+
cv::Mat F = transitJacobian(m_state_vec, u);
79+
cv::Mat Q = transitNoiseCov(m_state_vec, u);
80+
81+
// Predict the state
82+
m_state_vec = transitModel(m_state_vec, u);
83+
m_state_cov = F * m_state_cov * F.t() + Q;
84+
85+
// Enforce the state covariance symmetric
86+
m_state_cov = 0.5 * m_state_cov + 0.5 * m_state_cov.t();
87+
return true;
88+
}
89+
90+
/**
91+
* Correct the state variable and covariance with the given measurement
92+
* @param measurement The given measurement
93+
* @return True if successful (false if failed)
94+
*/
95+
virtual bool correct(cv::InputArray measurement)
96+
{
97+
// Calculate Kalman gain
98+
cv::Mat z = measurement.getMat();
99+
if (z.rows < z.cols) z = z.t();
100+
cv::Mat H = observeJacobian(m_state_vec, z);
101+
cv::Mat R = observeNoiseCov(m_state_vec, z);
102+
cv::Mat S = H * m_state_cov * H.t() + R;
103+
cv::Mat K = m_state_cov * H.t() * S.inv(cv::DecompTypes::DECOMP_SVD);
104+
cv::Mat innovation = z - observeModel(m_state_vec, z);
105+
106+
// Correct the state
107+
m_state_vec = m_state_vec + K * innovation;
108+
cv::Mat I_KH = cv::Mat::eye(m_state_cov.size(), m_state_cov.type()) - K * H;
109+
//m_state_cov = I_KH * m_state_cov; // The standard form
110+
m_state_cov = I_KH * m_state_cov * I_KH.t() + K * R * K.t(); // Joseph form
111+
112+
// Enforce the state covariance symmetric
113+
m_state_cov = 0.5 * m_state_cov + 0.5 * cv::Mat(m_state_cov.t());
114+
return true;
115+
}
116+
117+
/**
118+
* Calculate squared <a href="https://en.wikipedia.org/wiki/Mahalanobis_distance">Mahalanobis distance</a> of the given measurement
119+
* @param measurement The given measurement
120+
* @return The squared Mahalanobis distance
121+
*/
122+
virtual double checkMeasurement(cv::InputArray measurement)
123+
{
124+
cv::Mat z = measurement.getMat();
125+
if (z.rows < z.cols) z = z.t();
126+
cv::Mat delta = z - observeModel(m_state_vec, z);
127+
cv::Mat H = observeJacobian(m_state_vec, z);
128+
cv::Mat S = H * m_state_cov * H.t();
129+
cv::Mat mah_dist2 = delta.t() * S.inv(cv::DecompTypes::DECOMP_SVD) * delta;
130+
return cv::sum(mah_dist2)(0);
131+
}
132+
133+
/**
134+
* Assign the state variable with the given value
135+
* @param state The given state variable
136+
* @return True if successful (false if failed)
137+
*/
138+
bool setState(cv::InputArray state)
139+
{
140+
CV_DbgAssert(state.size() == m_state_vec.size());
141+
CV_DbgAssert(state.type() == m_state_vec.type());
142+
m_state_vec = state.getMat();
143+
return true;
144+
}
145+
146+
/**
147+
* Get the current state variable
148+
* @return The state variable
149+
*/
150+
const cv::Mat getState() { return m_state_vec; }
151+
152+
/**
153+
* Assign the state covariance with the given value
154+
* @param covariance The given state covariance
155+
* @return True if successful (false if failed)
156+
*/
157+
bool setStateCov(const cv::InputArray covariance)
158+
{
159+
CV_DbgAssert(covariance.size() == m_state_cov.size());
160+
CV_DbgAssert(covariance.type() == m_state_cov.type());
161+
m_state_cov = covariance.getMat();
162+
return true;
163+
}
164+
165+
/**
166+
* Get the current state covariance
167+
* @return The state covariance
168+
*/
169+
const cv::Mat getStateCov() { return m_state_cov; }
170+
171+
protected:
172+
/**
173+
* The state transition function
174+
* @param state The state variable
175+
* @param control The given control input
176+
* @return The predicted state variable
177+
*/
178+
virtual cv::Mat transitModel(const cv::Mat& state, const cv::Mat& control) = 0;
179+
180+
/**
181+
* Return the Jacobian of the state transition function
182+
* @param state The state variable
183+
* @param control The given control input
184+
* @return The Jacobian of the state transition function
185+
*/
186+
virtual cv::Mat transitJacobian(const cv::Mat& state, const cv::Mat& control) = 0;
187+
188+
/**
189+
* Return the state transition noise
190+
* @param state The state variable
191+
* @param control The given control input
192+
* @return The state transition noise
193+
*/
194+
virtual cv::Mat transitNoiseCov(const cv::Mat& state, const cv::Mat& control) = 0;
195+
196+
/**
197+
* The state observation function
198+
* @param state The state variable
199+
* @param measurement The given measurement
200+
* @return The expected measurement
201+
*/
202+
virtual cv::Mat observeModel(const cv::Mat& state, const cv::Mat& measurement) = 0;
203+
204+
/**
205+
* Return the Jacobian of the state observation function
206+
* @param state The state variable
207+
* @param measurement The given measurement
208+
* @return The Jacobian of the state observation function
209+
*/
210+
virtual cv::Mat observeJacobian(const cv::Mat& state, const cv::Mat& measurement) = 0;
211+
212+
/**
213+
* Return the state observation noise
214+
* @param state The state variable
215+
* @param measurement The given measurement
216+
* @return The state observation noise
217+
*/
218+
virtual cv::Mat observeNoiseCov(const cv::Mat& state, const cv::Mat& measurement) = 0;
219+
220+
/** The state variable */
221+
cv::Mat m_state_vec;
222+
223+
/** The state covariance */
224+
cv::Mat m_state_cov;
225+
}; // End of 'EKF'
226+
227+
} // End of 'cx'
228+
229+
#endif // End of '__EKF__'

Diff for: cx_example.cpp renamed to example_cx.cpp

+1-72
Original file line numberDiff line numberDiff line change
@@ -1,78 +1,12 @@
11
/**
2-
* An example of OpenCX
3-
*
4-
* @author Sunglok Choi (http://sites.google.com/site/sunglok)
5-
* @version 0.3 (12/10/2019)
2+
* An example of OpenCX, especially for 'opencx.hpp'
63
*/
74

85
#include "opencx.hpp"
96
#include <iostream>
107

118
using namespace std;
129

13-
int testStringOperations(const string& text = "\t OpenCX makes OpenCV v4 easier!\n ")
14-
{
15-
cout << "### String Trimming" << endl;
16-
cout << "* Original text: " << text << endl;
17-
cout << "* Left-trimmed text: " << cx::trimLeft(text) << endl;
18-
cout << "* Right-trimmed text: " << cx::trimRight(text) << endl;
19-
cout << "* Both-trimmed text: " << cx::trimBoth(text) << endl;
20-
cout << endl;
21-
22-
cout << "### String toLowerCase" << endl;
23-
cout << "* Original text: " << cx::trimBoth(text) << endl;
24-
cout << "* Transformed text: " << cx::toLowerCase(cx::trimBoth(text)) << endl;
25-
cout << endl;
26-
return 0;
27-
}
28-
29-
int testCSVReader(const string& filename = "cx_example.csv")
30-
{
31-
// Generate a CSV file
32-
ofstream file(filename);
33-
if (!file.is_open()) return -1;
34-
file << "Name, ID, Salary, Bonus" << endl;
35-
file << "SC, 2, 29.3, 2.8" << endl;
36-
file << "KL, 4, 18.10, 4.8" << endl;
37-
file << "NJ, 14, 27.10, 4.1" << endl;
38-
file << "WY, 16, 12.5, 6.1" << endl;
39-
file.close();
40-
41-
// Read the CSV file
42-
cx::CSVReader reader;
43-
if (!reader.open(filename)) return -1;
44-
if (reader.size() != 5) return -1;
45-
if (reader.front().size() != 4) return -1;
46-
47-
// Extract the data (with default arguments)
48-
cx::CSVReader::String2D all_with_header = reader.extString2D();
49-
if (all_with_header.size() != 5) return -1;
50-
if (all_with_header.front().size() != 4) return -1;
51-
52-
cout << "### Test cx::CSVReader" << endl;
53-
for (size_t i = 0; i < all_with_header.size(); i++)
54-
{
55-
cout << "| ";
56-
for (size_t j = 0; j < all_with_header[i].size(); j++)
57-
cout << all_with_header[i][j] << " | ";
58-
cout << endl;
59-
}
60-
cout << endl;
61-
62-
// Extract the data as specific types
63-
cx::CSVReader::String2D name = reader.extString2D(1, { 0 });
64-
cx::CSVReader::Int2D ids = reader.extInt2D(1, { 1 });
65-
cx::CSVReader::Double2D data = reader.extDouble2D(1, { 2, 3 });
66-
if (name.size() != 4 || ids.size() != 4 || data.size() != 4) return -1;
67-
if (name.front().size() != 1 || ids.front().size() != 1 || data.front().size() != 2) return -1;
68-
69-
cout << "### Test cx::CSVReader" << endl;
70-
for (size_t i = 0; i < data.size(); i++)
71-
cout << "A person (name: " << name[i][0] << ", ID: " << ids[i][0] << ") will receive USD " << data[i][0] + data[i][1] << "." << endl;
72-
cout << endl;
73-
return 0;
74-
}
75-
7610
// An example to use 'cx::Algorithm'
7711
class NoiseGenerator : public cx::Algorithm
7812
{
@@ -263,15 +197,10 @@ int testKeyCodes()
263197

264198
int main()
265199
{
266-
// Test functionalities in 'opensx.hpp'
267-
if (testStringOperations() < 0) return -1;
268-
if (testCSVReader() < 0) return -1;
269-
270200
// Test functionalities in 'opencx.hpp'
271201
if (testAlgorithm() < 0) return -1;
272202
if (compareVideoWriter() < 0) return -1;
273203
if (testAngularOperations() < 0) return -1;
274204
if (testKeyCodes() < 0) return -1;
275-
276205
return 0;
277206
}

Diff for: example_ekf.cpp

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/**
2+
* An example of OpenCX, especially for 'ekf.hpp'
3+
*/
4+
5+
#include "opencx.hpp"
6+
#include <iostream>
7+
8+
using namespace std;
9+
10+
int main()
11+
{
12+
return 0;
13+
}

0 commit comments

Comments
 (0)