├── README.md ├── _config.yml └── traffic.py /README.md: -------------------------------------------------------------------------------- 1 | This is a demo made for tracking pedestrian as I am making an attempt to track other vehicles. Starting out the problem with pedestrian is somewhat easier since their motions are more predictable. Click [here](http://www.youtube.com/watch?v=ZYgb9e5i_JM) for the demo video. 2 | 3 | This Project uses OpenCV 3.1.0 and Python. The video source video can be found [here](https://www.dropbox.com/s/7mbo35zrxd1aefz/peopleCounter.avi?dl=0) 4 | 5 | Thanks Federico E. Mejía Barajas for his invaluable advices. I recommend looking at his [tutorial](http://www.femb.com.mx/blog/) and going through documentations on the OpenCV Python tutorial side to understand the code. 6 | 7 | [![IMAGE ALT TEXT](http://img.youtube.com/vi/ZYgb9e5i_JM/0.jpg)](http://www.youtube.com/watch?v=ZYgb9e5i_JM "Pedestrian Tracking and Counting") 8 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-slate -------------------------------------------------------------------------------- /traffic.py: -------------------------------------------------------------------------------- 1 | # ==================================================== 2 | # ==================== PACKAGES ====================== 3 | # ==================================================== 4 | 5 | ## WORKING SPECS: 6 | # OpenCV_3.1.0 7 | # Python_2.7.12 8 | 9 | import numpy as np 10 | import cv2 11 | import colorsys 12 | import collections 13 | 14 | 15 | # ==================================================== 16 | # ================== DEFINE CLASS ==================== 17 | # ==================================================== 18 | 19 | class Position(object): 20 | def __init__(self, _x, _y, _w, _h): 21 | self.x = _x 22 | self.y = _y 23 | self.w = _w 24 | self.h = _h 25 | 26 | def x(self): 27 | return self.x 28 | 29 | def y(self): 30 | return self.y 31 | 32 | def w(self): 33 | return self.w 34 | 35 | def h(self): 36 | return self.h 37 | 38 | class People(object): 39 | def __init__(self, _x, _y, _w, _h, _roi, _hue): 40 | # Position 41 | self.x = _x 42 | self.y = _y 43 | self.w = _w 44 | self.h = _h 45 | self.roi = _roi 46 | 47 | # Display of the contour while tracking 48 | self.hue = _hue 49 | self.color = hsv2rgb(self.hue%1, 1, 1) 50 | 51 | # Motion Descriptors 52 | self.center = [_x + _w/2, _y + _h/2] 53 | self.isIn = checkPosition(boundaryPt1, boundaryPt2, self.center, inCriterion) 54 | self.isInChangeFrameCount = toleranceCountIOStatus 55 | self.speed = [0,0] 56 | self.missingCount = 0 57 | 58 | # ROI - Region of Interest 59 | self.maxRoi = _roi 60 | self.roi = _roi 61 | 62 | def x(self): 63 | return self.x 64 | 65 | def y(self): 66 | return self.y 67 | 68 | def w(self): 69 | return self.w 70 | 71 | def h(self): 72 | return self.h 73 | 74 | def roi(self): 75 | return self.roi 76 | 77 | def color(self): 78 | return self.color 79 | 80 | def center(self): 81 | return self.center 82 | 83 | def maxRoi(self): 84 | return self.maxRoi 85 | 86 | def isIn(self): 87 | return self.isIn 88 | 89 | def speed(self): 90 | return self.speed 91 | 92 | def missingCount(self): 93 | return self.missingCount 94 | 95 | def isInChangeFrameCount(self): 96 | return self.isInChangeFrameCount 97 | 98 | def set(self, name, value): 99 | if name == "x": 100 | self.x = value 101 | elif name == "y": 102 | self.y = value 103 | elif name == "w": 104 | self.w = value 105 | elif name == "h": 106 | self.h = value 107 | elif name == "center": 108 | self.center = value 109 | elif name == "roi": 110 | self.roi = value 111 | # Automatically update maxRoi as roi is updated 112 | if self.roi.shape[0]*self.roi.shape[1] > self.maxRoi.shape[0]*self.maxRoi.shape[1]: 113 | self.maxRoi = self.roi 114 | elif name == "speed": 115 | self.speed = value 116 | elif name == "missingCount": 117 | self.missingCount = value 118 | elif name == "isIn": 119 | self.isIn = value 120 | elif name == "isInChangeFrameCount": 121 | self.isInChangeFrameCount = value 122 | else: 123 | return 124 | 125 | 126 | # ==================================================== 127 | # ===================== FUNCTION ===================== 128 | # ==================================================== 129 | 130 | def averageSize(): 131 | sum = 0 132 | for i in humanSizeSample: 133 | sum +=i 134 | return sum/sampleSize 135 | 136 | 137 | # Only care about top and bottom 138 | def checkTouchVSide(x, y, w, h, maxW, maxH, tolerance): 139 | if x <= 0: 140 | return True 141 | elif y - tolerance <= 0: 142 | return True 143 | elif x + w >= maxW: 144 | return True 145 | elif y + h + tolerance >= maxH: 146 | return True 147 | else: 148 | return False 149 | 150 | 151 | def getExteriorRect(pts): 152 | xArray = [] 153 | yArray = [] 154 | for pt in pts: 155 | xArray.append(pt[0]) 156 | yArray.append(pt[1]) 157 | xArray = sorted(xArray) 158 | yArray = sorted(yArray) 159 | return (xArray[0], yArray[0], xArray[3] - xArray[0], yArray[3] - yArray[0]) 160 | 161 | 162 | def hsv2rgb(h, s, v): 163 | return tuple(i * 255 for i in colorsys.hsv_to_rgb(h, s, v)) 164 | 165 | 166 | def checkPosition(boundaryPt1, boundaryPt2, currPos, inCriterion): 167 | m = (boundaryPt2[1] - boundaryPt1[1])/(boundaryPt2[0] - boundaryPt1[0]) 168 | c = boundaryPt2[1] - m*boundaryPt2[0] 169 | if inCriterion == "<": 170 | if currPos[0] * m + c < currPos[1]: 171 | return True 172 | else: 173 | return False 174 | elif inCriterion == ">": 175 | if currPos[0] * m + c > currPos[1]: 176 | return True 177 | else: 178 | return False 179 | else: 180 | return False 181 | 182 | def nothing(x): 183 | pass 184 | 185 | # ==================================================== 186 | # ================== VIDEO SOURCE ==================== 187 | # ==================================================== 188 | 189 | srcTest = 'peopleCounter.avi' 190 | srcWebcam = 0 191 | srcMain = '' # live source here 192 | cap = cv2.VideoCapture(srcTest) # Open video file 193 | 194 | 195 | # ==================================================== 196 | # ================== PRE-CONFIG ====================== 197 | # ==================================================== 198 | 199 | minArea = 500 # default min area to be considered person 200 | maxArea = 4000 # default max area to be considered person 201 | noFrameToCollectSample = 100 202 | toleranceRange = 50 # use for error calculation 203 | toleranceCount = 10 # maximum number of frame an object need to present in order to be accepted 204 | toleranceCountIOStatus = 3 # minimum number of frame between In/Out Status change -> prevent playing with the system 205 | startHue = 0 # In HSV this is RED 206 | hueIncrementValue = 0.1 # increment color every time to differentiate between different people 207 | 208 | 209 | # ==================================================== 210 | # ====================== SETUP ======================= 211 | # ==================================================== 212 | 213 | # fgbg = cv2.createBackgroundSubtractorMOG2(history=500, varThreshold = 16, detectShadows=True) 214 | fgbg = cv2.createBackgroundSubtractorMOG2(detectShadows=True) 215 | sampleSize = 100 216 | humanSizeSample = collections.deque(maxlen=sampleSize) 217 | 218 | midHeight = int(cap.get(4) / 2) 219 | maxWidth = cap.get(3) 220 | maxHeight = cap.get(4) 221 | 222 | inCriterion = "<" 223 | boundaryPt1 = [0, midHeight-100] 224 | boundaryPt2 = [maxWidth, midHeight] 225 | 226 | 227 | # ==================================================== 228 | # ====================== MAIN ======================== 229 | # ==================================================== 230 | 231 | # Passage Control 232 | allowPassage = True 233 | peopleViolationIn = 0 234 | peopleViolationOut = 0 235 | switch = '0 : PASS \n1 : STOP' 236 | 237 | # Controller 238 | cv2.namedWindow('config') 239 | cv2.createTrackbar(switch, 'config', 0, 1, nothing) 240 | 241 | # Initializa Other Variable 242 | averageArea = 0.000 # for calculation of min/max size for contour detected 243 | peopleIn = 0 # number of people going up 244 | peopleOut = 0 # number of people going up 245 | frameCounter = 0 246 | maskT = None 247 | passImage = None 248 | detectedPeople = [] 249 | detectedContours = [] 250 | 251 | # take first frame of the video 252 | _ , pFrame = cap.read() 253 | 254 | while (cap.isOpened()): 255 | 256 | # Check Passage Status 257 | status = cv2.getTrackbarPos(switch, 'config') 258 | if status == 0: 259 | allowPassage = True 260 | else: 261 | allowPassage = False 262 | 263 | # RE-Initialize 264 | frameInfo = np.zeros((400, 500, 3), np.uint8) 265 | averageArea = averageSize() 266 | ret, frame = cap.read() # read a frame 267 | frameForView = frame.copy() 268 | 269 | # Clean Frame 270 | gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) 271 | fgmask = fgbg.apply(gray) 272 | blur = cv2.medianBlur(fgmask, 5) 273 | thresh = cv2.threshold(blur, 127, 255, cv2.THRESH_BINARY)[1] # shadow of MOG@ is grey = 127 274 | kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) 275 | closing = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel) # fill any small holes 276 | opening = cv2.morphologyEx(closing, cv2.MORPH_OPEN, kernel) # remove noise 277 | contours = cv2.findContours(opening.copy(), cv2.RETR_EXTERNAL, 278 | cv2.CHAIN_APPROX_SIMPLE)[1] 279 | 280 | mask_opening = cv2.inRange(opening, np.array([0]), np.array([128])) 281 | noBg = cv2.bitwise_and(frame, frame, mask=mask_opening) 282 | 283 | # Process Contours 284 | for c in contours: 285 | # Filter Contour By Size 286 | if len(humanSizeSample) < 100: 287 | if cv2.contourArea(c) < minArea or cv2.contourArea(c) > maxArea: 288 | continue 289 | else: 290 | humanSizeSample.append(cv2.contourArea(c)) 291 | else: 292 | if cv2.contourArea(c) < averageArea/2 or cv2.contourArea(c) > averageArea*3: 293 | continue 294 | (x, y, w, h) = cv2.boundingRect(c) 295 | detectedContours.append(Position(x, y, w, h)) 296 | 297 | # Process Detected People 298 | if len(detectedPeople) != 0: 299 | for people in detectedPeople: 300 | 301 | # Setup Meanshift/Camshift for Tracking 302 | track_window = (people.x, people.y, people.w, people.h) 303 | hsv_roi = pOpening[people.y:people.y + people.h, people.x:people.x + people.w] 304 | mask = cv2.inRange(hsv_roi, np.array(128), np.array(256)) 305 | roi_hist = cv2.calcHist([hsv_roi], [0], mask, [100], [0, 256]) 306 | cv2.normalize(roi_hist, roi_hist, 0, 255, cv2.NORM_MINMAX) 307 | term_criteria = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 1, 1) # Setup the termination criteria, either 10 iteration or move by atleast 1 pt 308 | dst = cv2.calcBackProject([opening], [0], roi_hist, [0, 256], 1) 309 | ret, track_window = cv2.CamShift(dst, track_window, term_criteria) 310 | 311 | # Process POST Tracking 312 | pts = cv2.boxPoints(ret) 313 | pts = np.int0(pts) 314 | img2 = cv2.polylines(frameForView, [pts], True, people.color, 2) 315 | pos = sum(pts)/len(pts) 316 | isFound = False 317 | for dC in detectedContours: 318 | if dC.x - toleranceRange < pos[0] < dC.x + dC.w + toleranceRange \ 319 | and dC.y - toleranceRange < pos[1] < dC.y + dC.h + toleranceRange: 320 | people.set("x", dC.x) 321 | people.set("y", dC.y) 322 | people.set("w", dC.w) 323 | people.set("h", dC.h) 324 | people.set("speed", pos - people.center) 325 | people.set("center", pos) 326 | people.set("missingCount", 0) 327 | detectedContours.remove(dC) 328 | isFound = True 329 | 330 | tR = getExteriorRect(pts) 331 | people.set("roi", frame[tR[1]:tR[1]+tR[3], tR[0]:tR[0]+tR[2]]) 332 | 333 | # Process Continuous Tracking 334 | prevInStatus = people.isIn 335 | currInStatus = checkPosition(boundaryPt1, boundaryPt2, people.center, inCriterion) 336 | people.isIn = currInStatus 337 | 338 | # Check In/Out Status Change 339 | if prevInStatus != currInStatus and people.isInChangeFrameCount >= toleranceCountIOStatus: 340 | if not allowPassage: 341 | passImage = people.roi 342 | people.set("isInChangeFrameCount", 0) 343 | if currInStatus: 344 | peopleIn += 1 345 | if not allowPassage: 346 | peopleViolationIn += 1 347 | else: 348 | peopleOut += 1 349 | if not allowPassage: 350 | peopleViolationOut += 1 351 | else: 352 | people.set("isInChangeFrameCount", people.isInChangeFrameCount + 1) 353 | 354 | # Process DIS-continuous Tracking 355 | if not isFound: 356 | if people.missingCount > toleranceCount: 357 | detectedPeople.remove(people) 358 | else: 359 | if checkTouchVSide(people.x + people.speed[0], people.y + people.speed[1], people.w, 360 | people.h, maxWidth, maxHeight, toleranceRange): 361 | detectedPeople.remove(people) 362 | else: 363 | people.set("missingCount", people.missingCount+1) 364 | people.set("x", people.x + people.speed[0]) 365 | people.set("y", people.y + people.speed[1]) 366 | people.set("center", people.center + people.speed) 367 | 368 | # Check New People 369 | for dC in detectedContours: 370 | if checkTouchVSide(dC.x, dC.y, dC.w, dC.h, maxWidth, maxHeight, toleranceRange): 371 | startHue += hueIncrementValue 372 | detectedPeople.append(People(dC.x, dC.y, dC.w, dC.h, frame[dC.y:dC.y+dC.h, dC.x:dC.x+dC.w], startHue)) 373 | 374 | # RE-set 375 | detectedContours = [] 376 | pFrame = frame 377 | pNoBg = noBg 378 | pOpening = opening 379 | frameCounter += 1 380 | 381 | # Output 382 | try: 383 | # Setup Stats 384 | textNoOfPeople = "People: " + str(len(detectedPeople)) 385 | textNoIn = "In: " + str(peopleIn) 386 | textNoOut = "Out: " + str(peopleOut) 387 | textNoViolationIn = "In: " + str(peopleViolationIn) 388 | textNoViolationOut = "Out: " + str(peopleViolationOut) 389 | 390 | if allowPassage: 391 | cv2.line(frameForView, (long(boundaryPt1[0]), long(boundaryPt1[1])), 392 | (long(boundaryPt2[0]), long(boundaryPt2[1])), (0, 255, 0), 2) 393 | else: 394 | cv2.line(frameForView, (long(boundaryPt1[0]), long(boundaryPt1[1])), 395 | (long(boundaryPt2[0]), long(boundaryPt2[1])), (0, 0, 255), 2) 396 | 397 | # Draw Infos 398 | cv2.putText(frameInfo, textNoOfPeople, (30, 40), cv2.FONT_HERSHEY_SIMPLEX 399 | , 1, (255, 255, 255), 1, cv2.LINE_AA) 400 | cv2.putText(frameInfo, textNoIn, (30, 80), cv2.FONT_HERSHEY_SIMPLEX 401 | , 1, (255, 255, 255), 1, cv2.LINE_AA) 402 | cv2.putText(frameInfo, textNoOut, (30, 120), cv2.FONT_HERSHEY_SIMPLEX 403 | , 1, (255, 255, 255), 1, cv2.LINE_AA) 404 | cv2.line(frameInfo, (0, 160), (640, 160), (255, 255, 255), 1) 405 | cv2.putText(frameInfo, "VIOLATION", (30, 200), cv2.FONT_HERSHEY_SIMPLEX 406 | , 1, (255, 255, 255), 1, cv2.LINE_AA) 407 | cv2.putText(frameInfo, textNoViolationIn, (30, 240), cv2.FONT_HERSHEY_SIMPLEX 408 | , 1, (255, 255, 255), 1, cv2.LINE_AA) 409 | cv2.putText(frameInfo, textNoViolationOut, (30, 280), cv2.FONT_HERSHEY_SIMPLEX 410 | , 1, (255, 255, 255), 1, cv2.LINE_AA) 411 | 412 | # Display 413 | cv2.imshow('FrameForView', frameForView) 414 | # cv2.imshow('Frame', frame) 415 | if passImage != None: 416 | cv2.imshow('Violators', passImage) 417 | cv2.imshow('config', frameInfo) 418 | 419 | except: 420 | print('EOF') 421 | break 422 | 423 | # Abort and exit with 'Q' or ESC 424 | k = cv2.waitKey(30) & 0xff 425 | if k == 27: 426 | break 427 | # else: 428 | # cv2.imwrite(chr(k) + ".jpg", frame) 429 | 430 | cap.release() 431 | cv2.destroyAllWindows() 432 | 433 | --------------------------------------------------------------------------------