Video-Summary/Application/ContourExctractor.py

150 lines
5.5 KiB
Python
Raw Normal View History

2020-12-26 13:58:58 +00:00
from Application.VideoReader import VideoReader
from Application.Config import Config
2021-02-05 19:04:10 +00:00
from threading import Thread, activeCount
2020-10-05 07:43:27 +00:00
from multiprocessing import Queue, Process, Pool
from multiprocessing.pool import ThreadPool
2020-10-11 12:13:27 +00:00
from queue import Queue
2020-12-26 13:58:58 +00:00
import imutils
import time
import cv2
import numpy as np
2021-02-05 19:04:10 +00:00
import os
2020-09-22 18:25:06 +00:00
2022-01-09 19:25:44 +00:00
2020-09-20 20:01:54 +00:00
class ContourExtractor:
2020-12-26 13:58:58 +00:00
# extracedContours = {frame_number: [(contour, (x,y,w,h)), ...], }
# dict with frame numbers as keys and the contour bounds of every contour for that frame
2020-09-22 18:25:06 +00:00
2020-11-27 00:06:25 +00:00
def getExtractedContours(self):
2020-09-24 20:48:04 +00:00
return self.extractedContours
2020-11-27 00:06:25 +00:00
def getExtractedMasks(self):
return self.extractedMasks
2020-10-11 15:09:49 +00:00
def __init__(self, config):
2020-10-11 12:13:27 +00:00
self.frameBuffer = Queue(16)
self.extractedContours = dict()
2020-11-27 00:06:25 +00:00
self.extractedMasks = dict()
2020-10-11 15:09:49 +00:00
self.min_area = config["min_area"]
self.max_area = config["max_area"]
self.threashold = config["threashold"]
self.resizeWidth = config["resizeWidth"]
self.videoPath = config["inputPath"]
2020-10-11 12:13:27 +00:00
self.xDim = 0
2020-12-26 13:58:58 +00:00
self.yDim = 0
2020-10-11 15:09:49 +00:00
self.config = config
2020-10-21 19:56:00 +00:00
self.lastFrames = None
self.averages = dict()
2020-10-11 12:13:27 +00:00
print("ContourExtractor initiated")
2020-12-26 13:58:58 +00:00
def extractContours(self):
videoReader = VideoReader(self.config)
2020-12-22 12:58:47 +00:00
self.fps = videoReader.getFPS()
self.length = videoReader.getLength()
2020-10-22 16:40:13 +00:00
videoReader.fillBuffer()
2020-10-08 20:26:29 +00:00
2020-10-17 22:02:05 +00:00
threads = self.config["videoBufferLength"]
self.start = time.time()
2020-10-31 19:36:43 +00:00
# start a bunch of frames and let them read from the video reader buffer until the video reader reaches EOF
2020-12-22 12:58:47 +00:00
with ThreadPool(2) as pool:
2022-08-15 10:20:28 +00:00
while True:
while not videoReader.videoEnded() and videoReader.buffer.qsize() == 0:
2022-01-09 19:25:44 +00:00
time.sleep(0.5)
2020-10-05 07:43:27 +00:00
2022-01-09 19:25:44 +00:00
tmpData = [videoReader.pop() for i in range(0, videoReader.buffer.qsize())]
2022-08-15 10:20:28 +00:00
if videoReader.videoEnded():
break
2020-12-22 12:58:47 +00:00
pool.map(self.computeMovingAverage, (tmpData,))
pool.map(self.async2, (tmpData,))
2020-12-26 13:58:58 +00:00
# for data in tmpData:
2020-12-05 19:57:24 +00:00
# self.getContours(data)
2020-10-17 22:02:05 +00:00
frameCount = tmpData[-1][0]
2020-10-22 16:40:13 +00:00
2020-10-08 20:26:29 +00:00
videoReader.thread.join()
2020-11-27 00:06:25 +00:00
return self.extractedContours, self.extractedMasks
2020-12-22 12:58:47 +00:00
def async2(self, tmpData):
2021-02-05 19:04:10 +00:00
with ThreadPool(os.cpu_count()) as pool2:
2020-12-22 12:58:47 +00:00
pool2.map(self.getContours, tmpData)
2020-10-11 12:13:27 +00:00
def getContours(self, data):
frameCount, frame = data
2020-10-31 19:36:43 +00:00
# wait for the reference frame, which is calculated by averaging some revious frames
2020-10-21 19:56:00 +00:00
while frameCount not in self.averages:
time.sleep(0.1)
firstFrame = self.averages.pop(frameCount, None)
2020-12-26 13:58:58 +00:00
2022-01-09 19:25:44 +00:00
if frameCount % (10 * self.fps) == 1:
2020-12-26 13:58:58 +00:00
print(
2022-01-09 19:25:44 +00:00
f" \r \033[K {round((frameCount/self.fps)*100/self.length, 2)} % processed in {round(time.time() - self.start, 2)}s",
end="\r",
)
2020-10-31 19:36:43 +00:00
2020-10-21 19:56:00 +00:00
gray = self.prepareFrame(frame)
2020-10-05 07:43:27 +00:00
frameDelta = cv2.absdiff(gray, firstFrame)
2022-01-09 19:25:44 +00:00
thresh = cv2.threshold(frameDelta, self.threashold, 255, cv2.THRESH_BINARY)[1]
2020-10-05 07:43:27 +00:00
# dilate the thresholded image to fill in holes, then find contours
2020-10-17 22:02:05 +00:00
thresh = cv2.dilate(thresh, None, iterations=10)
2022-01-09 19:25:44 +00:00
# cv2.imshow("changes x", thresh)
# cv2.waitKey(10) & 0XFF
cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
2020-10-05 07:43:27 +00:00
cnts = imutils.grab_contours(cnts)
contours = []
2020-11-27 00:06:25 +00:00
masks = []
2020-10-05 07:43:27 +00:00
for c in cnts:
ca = cv2.contourArea(c)
2020-11-27 00:06:25 +00:00
(x, y, w, h) = cv2.boundingRect(c)
2020-10-05 07:43:27 +00:00
if ca < self.min_area or ca > self.max_area:
continue
2020-10-11 12:13:27 +00:00
contours.append((x, y, w, h))
2020-12-26 13:58:58 +00:00
# the mask has to be packed like this, since np doesn't have a bit array,
# meaning every bit in the mask would take up 8bits, which migth be too much
2022-01-09 19:25:44 +00:00
masks.append(np.packbits(np.copy(thresh[y : y + h, x : x + w]), axis=0))
2020-12-26 13:58:58 +00:00
if len(contours) != 0 and contours is not None:
2020-10-11 12:13:27 +00:00
# this should be thread safe
self.extractedContours[frameCount] = contours
2020-12-26 13:58:58 +00:00
self.extractedMasks[frameCount] = masks
2020-10-11 12:13:27 +00:00
2020-10-21 19:56:00 +00:00
def prepareFrame(self, frame):
frame = imutils.resize(frame, width=self.resizeWidth)
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray, (3, 3), 0)
2020-10-21 19:56:00 +00:00
return gray
2020-10-22 16:40:13 +00:00
def computeMovingAverage(self, frames):
avg = []
2020-11-08 15:28:47 +00:00
averageFrames = self.config["avgNum"]
2020-10-22 16:40:13 +00:00
if frames[0][0] < averageFrames:
frame = frames[0][1]
frame = self.prepareFrame(frame)
for j in range(0, len(frames)):
2020-12-26 13:58:58 +00:00
frameNumber, _ = frames[j]
2020-10-22 16:40:13 +00:00
self.averages[frameNumber] = frame
# put last x frames into a buffer
2020-12-26 13:58:58 +00:00
self.lastFrames = frames[-averageFrames:]
2020-10-22 16:40:13 +00:00
return
if self.lastFrames is not None:
2020-12-26 13:58:58 +00:00
frames = self.lastFrames + frames
2020-10-22 16:40:13 +00:00
2022-01-09 19:25:44 +00:00
tmp = [[j, frames, averageFrames] for j in range(averageFrames, len(frames))]
2022-08-15 10:20:28 +00:00
with ThreadPool(int(os.cpu_count())) as pool:
2020-10-22 16:40:13 +00:00
pool.map(self.averageDaFrames, tmp)
2020-12-26 13:58:58 +00:00
self.lastFrames = frames[-averageFrames:]
2020-09-22 18:25:06 +00:00
2020-10-22 16:40:13 +00:00
def averageDaFrames(self, dat):
j, frames, averageFrames = dat
frameNumber, frame = frames[j]
frame = self.prepareFrame(frame)
2020-12-26 13:58:58 +00:00
2022-01-09 19:25:44 +00:00
avg = frame / averageFrames
for jj in range(0, averageFrames - 1):
avg += self.prepareFrame(frames[j - jj][1]) / averageFrames
2020-10-22 16:40:13 +00:00
self.averages[frameNumber] = np.array(np.round(avg), dtype=np.uint8)