├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md └── source └── opencv-kalman.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | # C++ objects and libs 2 | 3 | *.slo 4 | *.lo 5 | *.o 6 | *.a 7 | *.la 8 | *.lai 9 | *.so 10 | *.dll 11 | *.dylib 12 | 13 | # Qt-es 14 | 15 | /.qmake.cache 16 | /.qmake.stash 17 | *.pro.user 18 | *.pro.user.* 19 | *.qbs.user 20 | *.qbs.user.* 21 | *.moc 22 | moc_*.cpp 23 | qrc_*.cpp 24 | ui_*.h 25 | Makefile* 26 | *-build-* 27 | 28 | # QtCreator 29 | 30 | *.autosave 31 | 32 | #QtCtreator Qml 33 | *.qmlproject.user 34 | *.qmlproject.user.* 35 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8) 2 | 3 | project(simple-opencv-kalman-tracker) 4 | 5 | set(CMAKE_BUILD_TYPE Debug) 6 | 7 | find_package( OpenCV REQUIRED ) 8 | 9 | 10 | 11 | set(SRC_PATH source ) 12 | 13 | set(${PROJECT_NAME}_SRC 14 | ${SRC_PATH}/opencv-kalman.cpp 15 | ) 16 | 17 | 18 | ######################################################### 19 | # Executable 20 | add_executable( ${PROJECT_NAME} ${${PROJECT_NAME}_SRC} ) 21 | target_link_libraries( ${PROJECT_NAME} ${OpenCV_LIBS} ) 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | 167 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # simple-opencv-kalman-tracker 2 | A simple Ball Tracker made using OpenCV to demonstrate the use of the Kalman Filter in Computer Vision 3 | 4 | You can find the full tutorial on [Robot-home website](http://www.robot-home.it/blog/en/software/ball-tracker-con-filtro-di-kalman/) 5 | 6 | Watch the demo working on YouTube: https://www.youtube.com/watch?v=sG-h5ONsj9s 7 | -------------------------------------------------------------------------------- /source/opencv-kalman.cpp: -------------------------------------------------------------------------------- 1 | /****************************************** 2 | * OpenCV Tutorial: Ball Tracking using * 3 | * Kalman Filter * 4 | ******************************************/ 5 | 6 | // Module "core" 7 | #include 8 | 9 | // Module "highgui" 10 | #include 11 | 12 | // Module "imgproc" 13 | #include 14 | 15 | // Module "video" 16 | #include 17 | 18 | // Output 19 | #include 20 | 21 | // Vector 22 | #include 23 | 24 | using namespace std; 25 | 26 | // >>>>> Color to be tracked 27 | #define MIN_H_BLUE 200 28 | #define MAX_H_BLUE 300 29 | // <<<<< Color to be tracked 30 | 31 | 32 | int main() 33 | { 34 | // Camera frame 35 | cv::Mat frame; 36 | 37 | // >>>> Kalman Filter 38 | int stateSize = 6; 39 | int measSize = 4; 40 | int contrSize = 0; 41 | 42 | unsigned int type = CV_32F; 43 | cv::KalmanFilter kf(stateSize, measSize, contrSize, type); 44 | 45 | cv::Mat state(stateSize, 1, type); // [x,y,v_x,v_y,w,h] 46 | cv::Mat meas(measSize, 1, type); // [z_x,z_y,z_w,z_h] 47 | //cv::Mat procNoise(stateSize, 1, type) 48 | // [E_x,E_y,E_v_x,E_v_y,E_w,E_h] 49 | 50 | // Transition State Matrix A 51 | // Note: set dT at each processing step! 52 | // [ 1 0 dT 0 0 0 ] 53 | // [ 0 1 0 dT 0 0 ] 54 | // [ 0 0 1 0 0 0 ] 55 | // [ 0 0 0 1 0 0 ] 56 | // [ 0 0 0 0 1 0 ] 57 | // [ 0 0 0 0 0 1 ] 58 | cv::setIdentity(kf.transitionMatrix); 59 | 60 | // Measure Matrix H 61 | // [ 1 0 0 0 0 0 ] 62 | // [ 0 1 0 0 0 0 ] 63 | // [ 0 0 0 0 1 0 ] 64 | // [ 0 0 0 0 0 1 ] 65 | kf.measurementMatrix = cv::Mat::zeros(measSize, stateSize, type); 66 | kf.measurementMatrix.at(0) = 1.0f; 67 | kf.measurementMatrix.at(7) = 1.0f; 68 | kf.measurementMatrix.at(16) = 1.0f; 69 | kf.measurementMatrix.at(23) = 1.0f; 70 | 71 | // Process Noise Covariance Matrix Q 72 | // [ Ex 0 0 0 0 0 ] 73 | // [ 0 Ey 0 0 0 0 ] 74 | // [ 0 0 Ev_x 0 0 0 ] 75 | // [ 0 0 0 Ev_y 0 0 ] 76 | // [ 0 0 0 0 Ew 0 ] 77 | // [ 0 0 0 0 0 Eh ] 78 | //cv::setIdentity(kf.processNoiseCov, cv::Scalar(1e-2)); 79 | kf.processNoiseCov.at(0) = 1e-2; 80 | kf.processNoiseCov.at(7) = 1e-2; 81 | kf.processNoiseCov.at(14) = 5.0f; 82 | kf.processNoiseCov.at(21) = 5.0f; 83 | kf.processNoiseCov.at(28) = 1e-2; 84 | kf.processNoiseCov.at(35) = 1e-2; 85 | 86 | // Measures Noise Covariance Matrix R 87 | cv::setIdentity(kf.measurementNoiseCov, cv::Scalar(1e-1)); 88 | // <<<< Kalman Filter 89 | 90 | // Camera Index 91 | int idx = 0; 92 | 93 | // Camera Capture 94 | cv::VideoCapture cap; 95 | 96 | // >>>>> Camera Settings 97 | if (!cap.open(idx)) 98 | { 99 | cout << "Webcam not connected.\n" << "Please verify\n"; 100 | return EXIT_FAILURE; 101 | } 102 | 103 | cap.set(CV_CAP_PROP_FRAME_WIDTH, 1024); 104 | cap.set(CV_CAP_PROP_FRAME_HEIGHT, 768); 105 | // <<<<< Camera Settings 106 | 107 | cout << "\nHit 'q' to exit...\n"; 108 | 109 | char ch = 0; 110 | 111 | double ticks = 0; 112 | bool found = false; 113 | 114 | int notFoundCount = 0; 115 | 116 | // >>>>> Main loop 117 | while (ch != 'q' && ch != 'Q') 118 | { 119 | double precTick = ticks; 120 | ticks = (double) cv::getTickCount(); 121 | 122 | double dT = (ticks - precTick) / cv::getTickFrequency(); //seconds 123 | 124 | // Frame acquisition 125 | cap >> frame; 126 | 127 | cv::Mat res; 128 | frame.copyTo( res ); 129 | 130 | if (found) 131 | { 132 | // >>>> Matrix A 133 | kf.transitionMatrix.at(2) = dT; 134 | kf.transitionMatrix.at(9) = dT; 135 | // <<<< Matrix A 136 | 137 | cout << "dT:" << endl << dT << endl; 138 | 139 | state = kf.predict(); 140 | cout << "State post:" << endl << state << endl; 141 | 142 | cv::Rect predRect; 143 | predRect.width = state.at(4); 144 | predRect.height = state.at(5); 145 | predRect.x = state.at(0) - predRect.width / 2; 146 | predRect.y = state.at(1) - predRect.height / 2; 147 | 148 | cv::Point center; 149 | center.x = state.at(0); 150 | center.y = state.at(1); 151 | cv::circle(res, center, 2, CV_RGB(255,0,0), -1); 152 | 153 | cv::rectangle(res, predRect, CV_RGB(255,0,0), 2); 154 | } 155 | 156 | // >>>>> Noise smoothing 157 | cv::Mat blur; 158 | cv::GaussianBlur(frame, blur, cv::Size(5, 5), 3.0, 3.0); 159 | // <<<<< Noise smoothing 160 | 161 | // >>>>> HSV conversion 162 | cv::Mat frmHsv; 163 | cv::cvtColor(blur, frmHsv, CV_BGR2HSV); 164 | // <<<<< HSV conversion 165 | 166 | // >>>>> Color Thresholding 167 | // Note: change parameters for different colors 168 | cv::Mat rangeRes = cv::Mat::zeros(frame.size(), CV_8UC1); 169 | cv::inRange(frmHsv, cv::Scalar(MIN_H_BLUE / 2, 100, 80), 170 | cv::Scalar(MAX_H_BLUE / 2, 255, 255), rangeRes); 171 | // <<<<< Color Thresholding 172 | 173 | // >>>>> Improving the result 174 | cv::erode(rangeRes, rangeRes, cv::Mat(), cv::Point(-1, -1), 2); 175 | cv::dilate(rangeRes, rangeRes, cv::Mat(), cv::Point(-1, -1), 2); 176 | // <<<<< Improving the result 177 | 178 | // Thresholding viewing 179 | cv::imshow("Threshold", rangeRes); 180 | 181 | // >>>>> Contours detection 182 | vector > contours; 183 | cv::findContours(rangeRes, contours, CV_RETR_EXTERNAL, 184 | CV_CHAIN_APPROX_NONE); 185 | // <<<<< Contours detection 186 | 187 | // >>>>> Filtering 188 | vector > balls; 189 | vector ballsBox; 190 | for (size_t i = 0; i < contours.size(); i++) 191 | { 192 | cv::Rect bBox; 193 | bBox = cv::boundingRect(contours[i]); 194 | 195 | float ratio = (float) bBox.width / (float) bBox.height; 196 | if (ratio > 1.0f) 197 | ratio = 1.0f / ratio; 198 | 199 | // Searching for a bBox almost square 200 | if (ratio > 0.75 && bBox.area() >= 400) 201 | { 202 | balls.push_back(contours[i]); 203 | ballsBox.push_back(bBox); 204 | } 205 | } 206 | // <<<<< Filtering 207 | 208 | cout << "Balls found:" << ballsBox.size() << endl; 209 | 210 | // >>>>> Detection result 211 | for (size_t i = 0; i < balls.size(); i++) 212 | { 213 | cv::drawContours(res, balls, i, CV_RGB(20,150,20), 1); 214 | cv::rectangle(res, ballsBox[i], CV_RGB(0,255,0), 2); 215 | 216 | cv::Point center; 217 | center.x = ballsBox[i].x + ballsBox[i].width / 2; 218 | center.y = ballsBox[i].y + ballsBox[i].height / 2; 219 | cv::circle(res, center, 2, CV_RGB(20,150,20), -1); 220 | 221 | stringstream sstr; 222 | sstr << "(" << center.x << "," << center.y << ")"; 223 | cv::putText(res, sstr.str(), 224 | cv::Point(center.x + 3, center.y - 3), 225 | cv::FONT_HERSHEY_SIMPLEX, 0.5, CV_RGB(20,150,20), 2); 226 | } 227 | // <<<<< Detection result 228 | 229 | // >>>>> Kalman Update 230 | if (balls.size() == 0) 231 | { 232 | notFoundCount++; 233 | cout << "notFoundCount:" << notFoundCount << endl; 234 | if( notFoundCount >= 100 ) 235 | { 236 | found = false; 237 | } 238 | /*else 239 | kf.statePost = state;*/ 240 | } 241 | else 242 | { 243 | notFoundCount = 0; 244 | 245 | meas.at(0) = ballsBox[0].x + ballsBox[0].width / 2; 246 | meas.at(1) = ballsBox[0].y + ballsBox[0].height / 2; 247 | meas.at(2) = (float)ballsBox[0].width; 248 | meas.at(3) = (float)ballsBox[0].height; 249 | 250 | if (!found) // First detection! 251 | { 252 | // >>>> Initialization 253 | kf.errorCovPre.at(0) = 1; // px 254 | kf.errorCovPre.at(7) = 1; // px 255 | kf.errorCovPre.at(14) = 1; 256 | kf.errorCovPre.at(21) = 1; 257 | kf.errorCovPre.at(28) = 1; // px 258 | kf.errorCovPre.at(35) = 1; // px 259 | 260 | state.at(0) = meas.at(0); 261 | state.at(1) = meas.at(1); 262 | state.at(2) = 0; 263 | state.at(3) = 0; 264 | state.at(4) = meas.at(2); 265 | state.at(5) = meas.at(3); 266 | // <<<< Initialization 267 | 268 | kf.statePost = state; 269 | 270 | found = true; 271 | } 272 | else 273 | kf.correct(meas); // Kalman Correction 274 | 275 | cout << "Measure matrix:" << endl << meas << endl; 276 | } 277 | // <<<<< Kalman Update 278 | 279 | // Final result 280 | cv::imshow("Tracking", res); 281 | 282 | // User key 283 | ch = cv::waitKey(1); 284 | } 285 | // <<<<< Main loop 286 | 287 | return EXIT_SUCCESS; 288 | } 289 | --------------------------------------------------------------------------------