├── .gitattributes ├── .gitignore ├── CMakeLists.txt ├── Capture.cpp ├── Capture.h ├── Frame.cpp ├── Frame.h ├── Main.cpp ├── README.md └── TB.mp4 /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################# 2 | ## Eclipse 3 | ################# 4 | 5 | *.pydevproject 6 | .project 7 | .metadata 8 | bin/ 9 | tmp/ 10 | *.tmp 11 | *.bak 12 | *.swp 13 | *~.nib 14 | local.properties 15 | .classpath 16 | .settings/ 17 | .loadpath 18 | 19 | # External tool builders 20 | .externalToolBuilders/ 21 | 22 | # Locally stored "Eclipse launch configurations" 23 | *.launch 24 | 25 | # CDT-specific 26 | .cproject 27 | 28 | # PDT-specific 29 | .buildpath 30 | 31 | 32 | ################# 33 | ## Visual Studio 34 | ################# 35 | 36 | ## Ignore Visual Studio temporary files, build results, and 37 | ## files generated by popular Visual Studio add-ons. 38 | 39 | # User-specific files 40 | *.suo 41 | *.user 42 | *.sln.docstates 43 | 44 | # Build results 45 | 46 | [Dd]ebug/ 47 | [Rr]elease/ 48 | x64/ 49 | build/ 50 | [Bb]in/ 51 | [Oo]bj/ 52 | 53 | # MSTest test Results 54 | [Tt]est[Rr]esult*/ 55 | [Bb]uild[Ll]og.* 56 | 57 | *_i.c 58 | *_p.c 59 | *.ilk 60 | *.meta 61 | *.obj 62 | *.pch 63 | *.pdb 64 | *.pgc 65 | *.pgd 66 | *.rsp 67 | *.sbr 68 | *.tlb 69 | *.tli 70 | *.tlh 71 | *.tmp 72 | *.tmp_proj 73 | *.log 74 | *.vspscc 75 | *.vssscc 76 | .builds 77 | *.pidb 78 | *.log 79 | *.scc 80 | 81 | # Visual C++ cache files 82 | ipch/ 83 | *.aps 84 | *.ncb 85 | *.opensdf 86 | *.sdf 87 | *.cachefile 88 | 89 | # Visual Studio profiler 90 | *.psess 91 | *.vsp 92 | *.vspx 93 | 94 | # Guidance Automation Toolkit 95 | *.gpState 96 | 97 | # ReSharper is a .NET coding add-in 98 | _ReSharper*/ 99 | *.[Rr]e[Ss]harper 100 | 101 | # TeamCity is a build add-in 102 | _TeamCity* 103 | 104 | # DotCover is a Code Coverage Tool 105 | *.dotCover 106 | 107 | # NCrunch 108 | *.ncrunch* 109 | .*crunch*.local.xml 110 | 111 | # Installshield output folder 112 | [Ee]xpress/ 113 | 114 | # DocProject is a documentation generator add-in 115 | DocProject/buildhelp/ 116 | DocProject/Help/*.HxT 117 | DocProject/Help/*.HxC 118 | DocProject/Help/*.hhc 119 | DocProject/Help/*.hhk 120 | DocProject/Help/*.hhp 121 | DocProject/Help/Html2 122 | DocProject/Help/html 123 | 124 | # Click-Once directory 125 | publish/ 126 | 127 | # Publish Web Output 128 | *.Publish.xml 129 | *.pubxml 130 | 131 | # NuGet Packages Directory 132 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 133 | #packages/ 134 | 135 | # Windows Azure Build Output 136 | csx 137 | *.build.csdef 138 | 139 | # Windows Store app package directory 140 | AppPackages/ 141 | 142 | # Others 143 | sql/ 144 | *.Cache 145 | ClientBin/ 146 | [Ss]tyle[Cc]op.* 147 | ~$* 148 | *~ 149 | *.dbmdl 150 | *.[Pp]ublish.xml 151 | *.pfx 152 | *.publishsettings 153 | 154 | # RIA/Silverlight projects 155 | Generated_Code/ 156 | 157 | # Backup & report files from converting an old project file to a newer 158 | # Visual Studio version. Backup files are not needed, because we have git ;-) 159 | _UpgradeReport_Files/ 160 | Backup*/ 161 | UpgradeLog*.XML 162 | UpgradeLog*.htm 163 | 164 | # SQL Server files 165 | App_Data/*.mdf 166 | App_Data/*.ldf 167 | 168 | ############# 169 | ## Windows detritus 170 | ############# 171 | 172 | # Windows image file caches 173 | Thumbs.db 174 | ehthumbs.db 175 | 176 | # Folder config file 177 | Desktop.ini 178 | 179 | # Recycle Bin used on file shares 180 | $RECYCLE.BIN/ 181 | 182 | # Mac crap 183 | .DS_Store 184 | 185 | 186 | ############# 187 | ## Python 188 | ############# 189 | 190 | *.py[co] 191 | 192 | # Packages 193 | *.egg 194 | *.egg-info 195 | dist/ 196 | build/ 197 | eggs/ 198 | parts/ 199 | var/ 200 | sdist/ 201 | develop-eggs/ 202 | .installed.cfg 203 | 204 | # Installer logs 205 | pip-log.txt 206 | 207 | # Unit test / coverage reports 208 | .coverage 209 | .tox 210 | 211 | #Translations 212 | *.mo 213 | 214 | #Mr Developer 215 | .mr.developer.cfg 216 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8.4) 2 | project(detection) 3 | 4 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") 5 | 6 | set(SOURCE Main.cpp Frame.cpp Capture.cpp) 7 | set(HEADER Frame.h Capture.h) 8 | add_executable(detection ${SOURCE}) 9 | 10 | #add_executable(detection Main.cpp) 11 | 12 | 13 | find_package(OpenCV REQUIRED ) 14 | target_link_libraries(detection ${OpenCV_LIBS} ) -------------------------------------------------------------------------------- /Capture.cpp: -------------------------------------------------------------------------------- 1 | #include "Capture.h" 2 | 3 | using namespace std; 4 | using namespace chrono; 5 | 6 | 7 | Capture::Capture(int cameraNumber) 8 | { 9 | capture.open(cameraNumber); 10 | } 11 | 12 | Capture::Capture(string fileName) 13 | { 14 | capture.open(fileName); 15 | } 16 | 17 | Capture::~Capture() 18 | { 19 | capture.release(); 20 | 21 | } 22 | 23 | bool Capture::isOpened() 24 | { 25 | return capture.isOpened(); 26 | } 27 | 28 | 29 | Mat Capture::getFrame() 30 | { 31 | Mat m; 32 | return m; 33 | } 34 | 35 | 36 | 37 | vector Capture::uniteRect(vector> contours) 38 | { 39 | vector rects; 40 | for (auto i = contours.begin(); i != contours.end(); i++) 41 | { 42 | rects.push_back(boundingRect(*i)); 43 | } 44 | bool isCrossed = true; 45 | // Rect stub(1, 1, 1, 1); 46 | while (isCrossed) 47 | { 48 | isCrossed = false; 49 | for (auto i = rects.begin(); i != rects.end();) 50 | { 51 | if ((i->height + i->width) < MINRECTPERIMETR) 52 | { 53 | i = rects.erase(i); 54 | } 55 | else 56 | { 57 | for (auto j = i; j != rects.end(); j++) 58 | { 59 | if (i == j) continue; 60 | if ((*i & *j).width != 0) 61 | { 62 | *i = *i | *j; 63 | *j = Rect(1, 1, 1, 1); 64 | isCrossed = true; 65 | } 66 | } 67 | i++; 68 | } 69 | } 70 | } 71 | return rects; 72 | } 73 | 74 | vector> Capture::uniteContours(vector> cnts) 75 | { 76 | vector> contours = cnts; 77 | bool isCrossed = true; 78 | vector stub = { Point(1, 1) }; 79 | while (isCrossed) 80 | { 81 | isCrossed = false; 82 | for (auto i = contours.begin(); i != contours.end();) 83 | { 84 | if (i->size() < 30) 85 | { 86 | i = contours.erase(i); 87 | } 88 | else 89 | { 90 | for (auto j = i; j != contours.end(); j++) 91 | { 92 | if (i == j) continue; 93 | if ((boundingRect(*i) & boundingRect(*j)).width != 0) 94 | { 95 | i->insert(i->end(), j->begin(), j->end()); 96 | *j = stub; 97 | isCrossed = true; 98 | } 99 | } 100 | i++; 101 | } 102 | } 103 | } 104 | return contours; 105 | } 106 | 107 | 108 | vector Capture::getFeaturePoints(vector contours) 109 | { 110 | vector features; 111 | const int qty = 10; 112 | long step = (long)contours.size()/qty; 113 | for (auto i = contours.begin(); i < contours.end(); i+=step) 114 | { 115 | features.push_back(Point2f(i->x, i->y)); 116 | } 117 | return features; 118 | } 119 | 120 | 121 | void Capture::cut(map& framesFlow, mutex& mutex_frames, vector>>& allTracks, mutex& mutex_tracks) 122 | { 123 | bool endFlag = true; 124 | while (endFlag) 125 | { 126 | milliseconds thisTime = duration_cast(high_resolution_clock::now().time_since_epoch()); 127 | mutex_frames.lock(); 128 | mutex_tracks.lock(); 129 | for (auto frameIt = framesFlow.begin(); frameIt != framesFlow.end();) 130 | { 131 | milliseconds frameTime = frameIt->first; 132 | if ((thisTime.count() - frameTime.count()) > timeRange) 133 | { 134 | frameIt = framesFlow.erase(frameIt); 135 | for (auto trackIt = allTracks.begin(); trackIt != allTracks.end(); trackIt++) 136 | { 137 | auto mapIt = trackIt->find(frameTime); 138 | if (mapIt != trackIt->end()) 139 | { 140 | mapIt = trackIt->erase(mapIt); 141 | } 142 | } 143 | } 144 | else frameIt++; 145 | } 146 | mutex_frames.unlock(); 147 | mutex_tracks.unlock(); 148 | } 149 | } 150 | 151 | 152 | void Capture::find(map& framesFlow, mutex& mutex_frames, vector>>& allTracks, mutex& mutex_tracks) 153 | { 154 | BackgroundSubtractorMOG2 backgroundSubtractor(10, 25, false); 155 | vector hierarchy; 156 | TermCriteria termcrit(CV_TERMCRIT_ITER | CV_TERMCRIT_EPS, 20, 0.03); 157 | Size subPixWinSize(10, 10), winSize(31, 31); 158 | bool firstTime = true; 159 | Mat savemask, gray, prevGray; 160 | bool endFlag = true; 161 | while (endFlag) 162 | { 163 | Mat frame, mask, fgimg; 164 | currentTime = duration_cast(high_resolution_clock::now().time_since_epoch()); 165 | capture >> frame; 166 | backgroundSubtractor(frame, mask, -1); 167 | mask.copyTo(savemask); 168 | Frame frametoMap(frame, savemask); 169 | mutex_frames.lock(); 170 | mutex_tracks.lock(); 171 | for (auto frameIt = framesFlow.begin(); frameIt != framesFlow.end();) 172 | { 173 | milliseconds frameTime = frameIt->first; 174 | if ((currentTime.count() - frameTime.count()) > timeRange) 175 | { 176 | frameIt = framesFlow.erase(frameIt); 177 | for (auto trackIt = allTracks.begin(); trackIt != allTracks.end(); trackIt++) 178 | { 179 | auto mapIt = trackIt->find(frameTime); 180 | if (mapIt != trackIt->end()) 181 | { 182 | mapIt = trackIt->erase(mapIt); 183 | } 184 | } 185 | } 186 | else frameIt++; 187 | } 188 | framesFlow.emplace(currentTime, frametoMap); 189 | mutex_frames.unlock(); 190 | mutex_tracks.unlock(); 191 | fgimg = Scalar::all(0); 192 | frame.copyTo(fgimg, mask); 193 | vector> allContours; 194 | findContours(mask, allContours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_NONE); 195 | if (allContours.size() > 0 && allContours.size() < 1000) 196 | { 197 | frame.copyTo(frame); 198 | cvtColor(frame, gray, COLOR_BGR2GRAY); 199 | vector> conts = uniteContours(allContours); 200 | if (prevGray.empty()) 201 | { 202 | gray.copyTo(prevGray); 203 | lastTime = currentTime; 204 | } 205 | mutex_tracks.lock(); 206 | if (allTracks.empty()) 207 | { 208 | for (auto contIt = conts.begin(); contIt != conts.end(); contIt++) 209 | { 210 | map> oneTrack; 211 | oneTrack.emplace(currentTime, *contIt); 212 | allTracks.push_back(oneTrack); 213 | } 214 | } 215 | else 216 | { 217 | vector status; 218 | vector err; 219 | multimap pointsNum; 220 | vector pointsPrev, pointsNow; 221 | for (auto allTrackIt = allTracks.begin(); allTrackIt != allTracks.end(); allTrackIt++) 222 | { 223 | long trackNumber = allTrackIt - allTracks.begin(); 224 | if (allTrackIt->size() > 0) 225 | { 226 | vector tmpVec = getFeaturePoints(allTrackIt->rbegin()->second); 227 | for (auto i = pointsPrev.size(); i < pointsPrev.size() + tmpVec.size(); i++) 228 | { 229 | pointsNum.emplace(trackNumber, i); 230 | } 231 | pointsPrev.insert(pointsPrev.end(), tmpVec.begin(), tmpVec.end()); 232 | } 233 | } 234 | calcOpticalFlowPyrLK(prevGray, gray, pointsPrev, pointsNow, status, err, winSize, 3, termcrit, 0, 0.001); 235 | for (auto allTrackIt = allTracks.begin(); allTrackIt != allTracks.end(); allTrackIt++) 236 | { 237 | long trackNumber = allTrackIt - allTracks.begin(); 238 | if (allTrackIt->size() > 0) 239 | { 240 | auto pointsNumIt = pointsNum.equal_range(trackNumber); 241 | vector tmpVecPoints; 242 | for (auto it = pointsNumIt.first; it != pointsNumIt.second; it++) 243 | { 244 | tmpVecPoints.push_back(pointsNow[it->second]); 245 | } 246 | Rect tmpRect = boundingRect(tmpVecPoints); 247 | for (auto contIt = conts.begin(); contIt != conts.end();) 248 | { 249 | Rect rect = boundingRect(*contIt); 250 | if ((tmpRect&rect).width > 0) 251 | { 252 | allTrackIt->emplace(currentTime, *contIt); 253 | contIt = conts.erase(contIt); 254 | break; 255 | } 256 | else contIt++; 257 | } 258 | } 259 | } 260 | for (auto contIt = conts.begin(); contIt != conts.end(); contIt++) 261 | { 262 | map> oneTrack; 263 | oneTrack.emplace(currentTime, *contIt); 264 | allTracks.push_back(oneTrack); 265 | } 266 | } 267 | mutex_tracks.unlock(); 268 | } 269 | int delay = 1; 270 | waitKey(delay); 271 | milliseconds endtime = duration_cast(high_resolution_clock::now().time_since_epoch()); 272 | fps = (int) 1000 / (endtime - currentTime).count(); 273 | swap(prevGray, gray); 274 | lastTime = currentTime; 275 | } 276 | } 277 | 278 | 279 | void Capture::display(map& framesFlow, mutex& mutex_frames, vector>>& allTracks, mutex& mutex_tracks) 280 | { 281 | bool endFlag = true; 282 | while (endFlag) 283 | { 284 | mutex_frames.lock(); 285 | if (framesFlow.size() == 0) 286 | { 287 | mutex_frames.unlock(); 288 | continue; 289 | } 290 | auto frameIt = framesFlow.rbegin(); 291 | milliseconds time = frameIt->first; 292 | Mat outFrame; 293 | outFrame = frameIt->second.getImg(); 294 | mutex_tracks.lock(); 295 | for (auto trackIt = allTracks.begin(); trackIt != allTracks.end(); trackIt++) 296 | { 297 | if (trackIt->size() > 1) 298 | { 299 | auto mapIt = trackIt->find(time); 300 | if (mapIt==trackIt->end()) continue; 301 | long number = trackIt - allTracks.begin(); 302 | Rect r = boundingRect(mapIt->second); 303 | rectangle(outFrame, r, Scalar(255, 0, 0), 1, 8, 0); 304 | stringstream ss; 305 | ss << number; 306 | string stringNumber = ss.str(); 307 | putText(outFrame, stringNumber, Point(r.x + 5, r.y + 5), 308 | FONT_HERSHEY_COMPLEX_SMALL, 1, Scalar(0,0,255), 1, 8); 309 | } 310 | } 311 | mutex_frames.unlock(); 312 | mutex_tracks.unlock(); 313 | stringstream sst; 314 | sst << fps; 315 | string fpsString = "FPS = " + sst.str(); 316 | putText(outFrame, fpsString, Point(20, outFrame.rows - 20), 317 | FONT_HERSHEY_COMPLEX_SMALL, 0.8, Scalar(255,0,255), 1, 8); 318 | imshow("tracks", outFrame); 319 | waitKey(10); 320 | } 321 | } 322 | 323 | void Capture::displayTime(Mat img) 324 | { 325 | seconds sec = duration_cast(currentTime); 326 | time_t timefordisp = sec.count(); 327 | putText(img, ctime(&timefordisp), Point(20, img.rows - 40), FONT_HERSHEY_COMPLEX, 1, Scalar::all(255), 1, 8); 328 | } -------------------------------------------------------------------------------- /Capture.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "opencv2/highgui/highgui.hpp" 3 | #include "opencv2/core/core.hpp" 4 | #include 5 | #include 6 | #include "Frame.h" 7 | #include 8 | 9 | using namespace cv; 10 | using namespace std; 11 | using namespace chrono; 12 | 13 | class Capture 14 | { 15 | const int MINRECTPERIMETR = 50; 16 | 17 | VideoCapture capture; 18 | milliseconds currentTime, lastTime; 19 | const int timeRange = 3000; // in milliseconds 20 | int fps; 21 | 22 | 23 | vector getFeaturePoints(vector); 24 | void displayTime(Mat); 25 | vector uniteRect(vector>); 26 | vector> uniteContours(vector>); 27 | vector convertBack(vector); 28 | vector getPoints(Rect); 29 | 30 | public: 31 | Capture(int); 32 | Capture(string); 33 | ~Capture(); 34 | bool isOpened(); 35 | Mat getFrame(); 36 | void find(map&, mutex&, vector>>&, mutex&); 37 | void cut(map&, mutex&, vector>>&, mutex&); 38 | void display(map&, mutex&, vector>>&, mutex&); 39 | 40 | }; 41 | -------------------------------------------------------------------------------- /Frame.cpp: -------------------------------------------------------------------------------- 1 | #include "Frame.h" 2 | 3 | 4 | Frame::Frame(Mat pic, Mat picMask) 5 | { 6 | img = pic; 7 | mask = picMask; 8 | } 9 | 10 | 11 | Frame::~Frame() 12 | { 13 | } 14 | 15 | 16 | Mat Frame::getImg() 17 | { 18 | return img; 19 | } 20 | 21 | Mat Frame::getMask() 22 | { 23 | return mask; 24 | } -------------------------------------------------------------------------------- /Frame.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | using namespace std; 6 | using namespace cv; 7 | 8 | class Frame 9 | { 10 | // chrono::milliseconds timeStamp; 11 | Mat img, mask; 12 | 13 | public: 14 | // Frame(chrono::milliseconds, Mat, Mat); 15 | Frame(Mat, Mat); 16 | ~Frame(); 17 | long long getTime(); 18 | Mat getImg(); 19 | Mat getMask(); 20 | }; -------------------------------------------------------------------------------- /Main.cpp: -------------------------------------------------------------------------------- 1 | #include "Capture.h" 2 | #include 3 | 4 | using namespace std; 5 | using namespace cv; 6 | 7 | map frames; 8 | vector>> allTracks; 9 | mutex mutex_frames, mutex_tracks; 10 | 11 | const int cameraNumber = 0; 12 | const string fileName = R"(/home/drew/ClionProjects/detection/123.avi)"; 13 | //const string fileName = R"(/home/drew/ClionProjects/detection/TB.mp4)"; 14 | 15 | 16 | int main() 17 | { 18 | // Capture capture(cameraNumber); 19 | // if (!capture.isOpened()) 20 | // { 21 | // cout << "Error: Camera #" << cameraNumber << " is not available now." << endl; 22 | // return -1; 23 | // } 24 | 25 | const char* file = fileName.c_str(); 26 | FILE* f = fopen(file, "r"); 27 | if (f == NULL) { 28 | cout << "Error: File "<< file << " is not found." << endl; 29 | return -1; 30 | } 31 | fclose(f); 32 | Capture capture(fileName); 33 | 34 | 35 | thread capturing(&Capture::find, &capture, ref(frames), ref(mutex_frames), ref(allTracks), ref(mutex_tracks)); 36 | thread display(&Capture::display, &capture, ref(frames), ref(mutex_frames), ref(allTracks), ref(mutex_tracks)); 37 | 38 | if (capturing.joinable()) 39 | { 40 | capturing.join(); 41 | } 42 | 43 | 44 | if (display.joinable()) 45 | { 46 | display.join(); 47 | } 48 | 49 | 50 | return 0; 51 | } 52 | 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Motion analysis and object tracking. 2 | This project shows how to track moving objects. 3 | It finds motion in a video stream and calculates an optical flow for a sparse feature set using the Lukas-Kanade method. 4 | 5 | It's based on the [OpenCV](http://opencv.org/) library. 6 | 7 | ## Example 8 | [http://youtu.be/tluSr064jPE](http://youtu.be/tluSr064jPE) 9 | 10 | [![](http://img.youtube.com/vi/tluSr064jPE/0.jpg)](http://youtu.be/tluSr064jPE) 11 | 12 | [http://youtu.be/zS17b80RPvg](http://youtu.be/zS17b80RPvg) 13 | 14 | [![](http://img.youtube.com/vi/zS17b80RPvg/0.jpg)](http://youtu.be/zS17b80RPvg) 15 | 16 | ## License 17 | 18 | * [Apache Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.html) 19 | -------------------------------------------------------------------------------- /TB.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ndrwk/detection/5f6394fb2435cde8e071331ac20c29441ef18004/TB.mp4 --------------------------------------------------------------------------------