├── README.md ├── .gitignore ├── object_counting_log.py ├── object_counting.py ├── common_object.py ├── object_counting_pipeline.py ├── object_detection.py ├── object_track.py └── test.py /README.md: -------------------------------------------------------------------------------- 1 | # ObjectCounting 2 | Common moving object selection, tracking and counting. 3 | 4 | 通用运动物体选择,追踪,并记录物体数量。 5 | 6 | 你可以阅读这篇[文章](http://jupiterd.top/2018/06/23/python-opencv-object-counting.html)来理解这个项目 7 | 8 | ## Video visualization demo 9 | ![image](http://p88h3xolw.bkt.clouddn.com/18-6-24/53667606.jpg) 10 | 11 | ## Data 12 | You can get video [here](https://youtu.be/wqctLW0Hb_0). 13 | 14 | 你可以在这里获取[视频](https://youtu.be/wqctLW0Hb_0)。 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | 47 | # Translations 48 | *.mo 49 | *.pot 50 | 51 | # Django stuff: 52 | *.log 53 | 54 | # Sphinx documentation 55 | docs/_build/ 56 | 57 | # PyBuilder 58 | target/ 59 | 60 | \.idea/ 61 | 62 | video/ 63 | *.avi 64 | *.png 65 | -------------------------------------------------------------------------------- /object_counting_log.py: -------------------------------------------------------------------------------- 1 | import cv2 as cv 2 | 3 | 4 | class ObjectCountingLog: 5 | """ 物体计数日志 """ 6 | 7 | def __init__(self, split_line): 8 | self.split_line = split_line 9 | self._counting_pic_list = [] 10 | 11 | def update_counting_pic_list(self, frame, new_counting_list): 12 | """ 13 | 更新物体图像列表 14 | 15 | :param frame: 图像 16 | :param new_counting_list: 被新追踪到的物体列表 17 | """ 18 | # 维护此列表最多只有8个物体 19 | if len(new_counting_list) + len(self._counting_pic_list) > 8: 20 | del self._counting_pic_list[:len(new_counting_list)] 21 | 22 | for each_object in new_counting_list: 23 | x1, y1, x2, y2 = each_object.rect 24 | object_pic = frame[y1 - self.split_line:y2 - self.split_line, x1:x2] 25 | object_pic = cv.resize(object_pic, (160, 160)) 26 | 27 | self._counting_pic_list.append(object_pic) 28 | 29 | def get_counting_pic_list(self): 30 | """获取图像列表""" 31 | return self._counting_pic_list 32 | -------------------------------------------------------------------------------- /object_counting.py: -------------------------------------------------------------------------------- 1 | class ObjectCounting: 2 | """ 物体计数 """ 3 | 4 | def __init__(self, counting_line): 5 | """ 6 | :type counting_line: int 7 | :param counting_line: 计数线的y轴坐标 8 | """ 9 | self.counting_line = counting_line 10 | 11 | def get_object_count(self, object_list): 12 | """ 13 | 获取物体个数 14 | 15 | :param object_list: 物体列表 16 | :return: 物体进出个数和被计数的物体列表 17 | """ 18 | object_in = object_out = 0 19 | new_counting_list = [] 20 | 21 | for each_object in object_list: 22 | # 运动轨迹至少为两个, 否则无法判断 23 | # 并且该物体还未被计数才可计数 24 | if not each_object.is_counted() and len(each_object.points) > 1: 25 | # 需要计算运动轨迹中最后两个点的向量来判断运动方向 26 | new_cx, new_cy = each_object.points[-1] 27 | last_cx, last_cy = each_object.points[-2] 28 | 29 | # 判断两点是否在计数线两侧, 如果在两侧则为负数(等于零也算在两侧), 否则为正数 30 | is_valid = (self.counting_line - new_cy) * (self.counting_line - last_cy) <= 0 31 | if is_valid: 32 | # 如果出去则最新的点在计数线上方 33 | if new_cy - last_cy <= 0: 34 | object_out += 1 35 | else: 36 | object_in += 1 37 | 38 | new_counting_list.append(each_object) 39 | each_object.set_counted() 40 | 41 | return object_in, object_out, new_counting_list 42 | -------------------------------------------------------------------------------- /common_object.py: -------------------------------------------------------------------------------- 1 | class CommonObject: 2 | """ 3 | 记录每个物体的信息 4 | 如: 运动轨迹坐标, 有效性 5 | """ 6 | 7 | def __init__(self, rect, point, flag): 8 | """ 9 | :type rect: list(x1, y1, x2, y2) 10 | :param rect: 矩形坐标 11 | :type point: list(x1, y1) 12 | :param point: 运动轨迹坐标(目标中心) 13 | :type flag: int 14 | :param flag: 帧标记. 检测中会向实例发送一个标记, 如果标记与实例标记相同, 则表明在这一帧中存在该物体 15 | """ 16 | self.rect = list(rect) 17 | self.points = [point] 18 | 19 | self._frame_flag = flag 20 | self._has_been_counted = False # 记录是否已被计数 21 | 22 | def set_counted(self): 23 | """设置被计数标记, 表明该实例已被计数""" 24 | self._has_been_counted = True 25 | 26 | def is_counted(self): 27 | """ 28 | 返回该实例是否被计数 29 | 30 | :return: 如果已被计数, 返回True; 否则返回False 31 | """ 32 | return self._has_been_counted 33 | 34 | def update_point(self, rect, point, flag): 35 | """ 36 | 更新物体运动轨迹坐标 37 | 38 | :type rect: list(x1, y1, x2, y2) 39 | :param rect: 标记物体的矩形框坐标 40 | :type point: list(x1, y1) 41 | :param point: 物体坐标中心 (cx, cy) 42 | :type flag: int 43 | :param flag: 帧标记 44 | """ 45 | if len(self.points) >= 10: 46 | del self.points[0] 47 | 48 | self.points.append(point) 49 | self.rect = list(rect) 50 | 51 | self._frame_flag = flag 52 | 53 | def is_exist(self, flag): 54 | """ 55 | 判断帧标记是否与物体标记相等, 56 | 如果相等则表明这一帧中检测到了物体 57 | 58 | :type flag: int 59 | :param flag: 帧标记 60 | :return: 存在, 返回True; 不存在, 返回False 61 | """ 62 | return self._frame_flag == flag 63 | 64 | @staticmethod 65 | def get_last_point(common_object): 66 | """ 67 | 获取物体实例最后一个运动坐标 68 | 69 | :type common_object: CommonObject 70 | :param common_object: CommonObject实例 71 | :return: 坐标 72 | """ 73 | return common_object.points[-1] 74 | 75 | @staticmethod 76 | def has_new_flag(object_and_new_flag): 77 | """ 78 | 用于筛选物体, 将不存在的物体删去 79 | 在筛选时应传入一个含有物体实例和帧标记的tuple 80 | 81 | :type object_and_new_flag: list(CommonObject, flag) 82 | :return: 存在时: True, 不存在时: False 83 | """ 84 | object_, new_flag = object_and_new_flag 85 | return object_.is_exist(new_flag) 86 | -------------------------------------------------------------------------------- /object_counting_pipeline.py: -------------------------------------------------------------------------------- 1 | from common_object import CommonObject 2 | import cv2 as cv 3 | 4 | 5 | class ObjectCountingPipeline: 6 | """ 运动物体 检测 - 追踪 - 计数 pipeline """ 7 | 8 | def __init__(self, object_detection_model, object_track_model, object_counting_model): 9 | """ 10 | :param object_detection_model: 物体选择模型 11 | :param object_track_model: 物体追踪模型 12 | :param object_counting_model: 物体计数模型 13 | """ 14 | self._object_detection_model = object_detection_model 15 | self._object_track_model = object_track_model 16 | self._object_counting_model = object_counting_model 17 | 18 | def detection_object(self, frame, min_width, min_height, show_mask_frame=False): 19 | """ 20 | 从图像中选择运动物体 21 | 22 | :param frame: 二值化图像 23 | :param min_width: 物体的最小宽度 24 | :param min_height: 物体的最小高度 25 | :param show_mask_frame: 是否显示去噪后的图像 26 | :return: 每个物体的矩形框左上角x1, y1坐标, 右下角x2, y2坐标和物体中心坐标cx, cy 27 | [(x1, y1, x2, y2), (cx, cy)] 28 | """ 29 | frame_mask = self._object_detection_model.background_update(frame) # 标记图像中的运动物体 30 | filter_mask = self._object_detection_model.filter_mask(frame_mask) # 将图像去噪 31 | matches_list = self._object_detection_model.detect_object(filter_mask, min_width, min_height) # 计算运动物体的坐标 32 | 33 | if show_mask_frame: 34 | cv.imshow("img", filter_mask) 35 | 36 | return matches_list 37 | 38 | def object_track(self, object_list, new_matches_list): 39 | """ 40 | 追踪运动目标 41 | 42 | :param object_list: 物体列表 43 | :param new_matches_list: 检测到的新的物体坐标 44 | :return: 更新后的物体列表 45 | """ 46 | new_object_list = self._object_track_model.object_track(object_list, new_matches_list) 47 | 48 | return new_object_list 49 | 50 | def object_counting(self, object_list): 51 | """ 52 | 物体计数 53 | 54 | :param object_list: 物体列表 55 | :return: 新检测到并符合要求的物体个数 56 | """ 57 | object_in, object_out, new_counting_list = self._object_counting_model.get_object_count(object_list) 58 | 59 | return object_in, object_out, new_counting_list 60 | 61 | def run(self, frame, min_width, min_height, object_list, counting_log=None, show_mask_frame=False): 62 | """ 63 | 运行计数流程 64 | 65 | :param frame: 图像 66 | :param min_width: 物体最小宽度 67 | :param min_height: 物体最小高度 68 | :param object_list: 物体列表 69 | :param counting_log: 物体计数日志 70 | :param show_mask_frame: 是否显示去噪后的图像 71 | :return: 追踪到的物体列表object_list, 进入的物体个数object_in, 出去的物体个数object_out 72 | """ 73 | matches_list = self.detection_object(frame, min_width, min_height, show_mask_frame) 74 | 75 | # 如果已存在物体则开始追踪 76 | if object_list: 77 | object_list = self.object_track(object_list, matches_list) 78 | 79 | # 否则需添加物体 80 | else: 81 | for rect, point in matches_list: 82 | new_object = CommonObject(rect, point, self._object_track_model.get_frame_flag()) 83 | object_list.append(new_object) 84 | 85 | object_in, object_out, new_counting_list = self.object_counting(object_list) 86 | 87 | if counting_log: 88 | counting_log.update_counting_pic_list(frame, new_counting_list) 89 | 90 | return object_list, object_in, object_out 91 | -------------------------------------------------------------------------------- /object_detection.py: -------------------------------------------------------------------------------- 1 | import cv2 as cv 2 | import numpy as np 3 | from imutils.object_detection import non_max_suppression 4 | 5 | 6 | class ObjectDetection: 7 | """ 检测并选择运动物体 """ 8 | 9 | def __init__(self, model, model_history, learn_rate, kernel): 10 | """ 11 | :param model: 物体检测模型 12 | :param model_history: 物体检测模型训练次数 13 | :param learn_rate: 模型学习速率 14 | :param kernel: 去噪时所用的核 15 | """ 16 | self._model = model # 物体检测模型 17 | self._model_history = model_history # 物体检测模型训练次数 18 | self.learn_rate = learn_rate # 算法模型的学习速率 19 | 20 | self._kernel = kernel # 去噪时所用的矩阵 21 | 22 | def train_model(self, cap): 23 | """ 24 | 训练模型 25 | 26 | :param cap: 视频流 27 | """ 28 | print("开始训练模型\n模型: {}\n训练次数: {}".format(self._model, self._model_history)) 29 | for i in range(self._model_history): 30 | retval, frame = cap.read() 31 | 32 | if retval: 33 | self._model.apply(frame, None, self.learn_rate) 34 | else: 35 | raise IOError("图像获取失败") 36 | 37 | def background_update(self, frame): 38 | """ 39 | 利用模型更新背景 40 | 41 | :param frame: 新的帧 42 | :return: 更新后的图像 43 | """ 44 | return self._model.apply(frame, None, self.learn_rate) 45 | 46 | def filter_mask(self, frame): 47 | """ 48 | 将图像去噪 49 | 50 | :param frame: 视频帧 51 | :return: 去噪后的二值化图像 52 | """ 53 | closing = cv.morphologyEx(frame, cv.MORPH_CLOSE, self._kernel) 54 | opening = cv.morphologyEx(closing, cv.MORPH_OPEN, self._kernel) 55 | 56 | expend = cv.dilate(opening, self._kernel, iterations=2) 57 | erode = cv.erode(expend, self._kernel) 58 | 59 | # 清除低于阀值噪点, 因为可能还存在灰色像素 60 | threshold = cv.threshold(erode, 240, 255, cv.THRESH_BINARY)[1] 61 | 62 | return threshold 63 | 64 | def detect_object(self, frame, min_width=35, min_height=35): 65 | """ 66 | 将二值化图像中的物体挑选出来 67 | 68 | :param frame: 二值化图像 69 | :param min_width: 物体最小宽度 70 | :param min_height: 物体最小高度 71 | :return: 每个物体的矩形框左上角x1, y1坐标, 右下角x2, y2坐标和物体中心坐标cx, cy 72 | [(x1, y1, x2, y2), (cx, cy)] 73 | """ 74 | matches = [] 75 | 76 | # 找到物体边界矩形 77 | image, contours, hierarchy = cv.findContours(frame, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_TC89_L1) 78 | 79 | # 利用非极大值抑制法避免一个物体上多个矩形(误检测多次) 80 | rects = np.array([(x, y, x + w, y + h) for x, y, w, h in map(cv.boundingRect, contours)]) 81 | pick = non_max_suppression(rects, overlapThresh=0.65) 82 | 83 | # 从每个坐标中选出符合标准大小的坐标(物体) 84 | for x1, y1, x2, y2 in pick: 85 | # 判断物体大小是否大于设定的标准 86 | is_valid = (x2 - x1 > min_width) and (y2 - y1 > min_height) 87 | 88 | # 符合标准, 将矩形坐标和物体中心坐标添加到列表中 89 | if is_valid: 90 | centroid = self._get_centroid(x1, y1, x2, y2) 91 | 92 | matches.append([(x1, y1, x2, y2), centroid]) 93 | 94 | return matches 95 | 96 | @staticmethod 97 | def _get_centroid(x1, y1, x2, y2): 98 | """ 99 | 获取物体中心 100 | 101 | :param x1: x轴起点 102 | :param y1: y轴起点 103 | :param x2: x轴终点 104 | :param y2: y轴终点 105 | :return: 中心坐标 (cx, xy) 106 | """ 107 | return ((x2 + x1) // 2), ((y2 + y1) // 2) 108 | -------------------------------------------------------------------------------- /object_track.py: -------------------------------------------------------------------------------- 1 | from common_object import CommonObject 2 | 3 | import numpy as np 4 | 5 | 6 | class ObjectTrackKNN: 7 | """ 追踪多个运动物体 """ 8 | 9 | def __init__(self, split_line, centroid_threshold_square): 10 | """ 11 | :type split_line: int 12 | :param split_line: 将图像分割为两部分的线(取下半部分为识别区域) 13 | :type centroid_threshold_square: int 14 | :param centroid_threshold_square: 运动轨迹最大距离的平方 15 | """ 16 | self._split_line = split_line 17 | self.centroid_threshold_square = centroid_threshold_square 18 | self._frame_flag = 0 19 | 20 | def _update_frame_flag(self): 21 | """更新帧标记""" 22 | self._frame_flag = (self._frame_flag + 1) % 10000 23 | 24 | def _calculate_distance_square(self, object_last_point_list, x, y): 25 | """ 26 | 计算新点与每个旧点的距离 27 | 28 | :param object_last_point_list: 所有物体上一次运动到的坐标集合 29 | :param x: 新的点的x坐标 30 | :param y: 新的点的y坐标 31 | :return: 距离的平方 32 | """ 33 | cx_square = np.square(object_last_point_list[:, 0] - x) 34 | cy_square = np.square(object_last_point_list[:, 1] - y - self._split_line) 35 | distance_square = cx_square + cy_square 36 | 37 | return distance_square 38 | 39 | def _calculate_offset(self, y1, y2, new_cy): 40 | """ 41 | 计算y轴偏移坐标 42 | 43 | 将y轴上的坐标加上偏移量, 因为在检测时只有整个图像的split_line线下的部分 44 | 因此要加上split_line的偏移量, 才能与原图像的区域坐标对应 45 | 46 | :param y1: 矩形y1坐标 47 | :param y2: 矩形y2坐标 48 | :param new_cy: 矩形中心y坐标 49 | :return: 偏移坐标 50 | """ 51 | y1 += self._split_line 52 | y2 += self._split_line 53 | new_cy += self._split_line 54 | 55 | return y1, y2, new_cy 56 | 57 | def _update_object_list(self, object_list): 58 | """ 59 | 删除未被新的帧标记标记过的物体对象 60 | 61 | :param object_list: 物体列表 62 | :return: 更新后的物体列表 63 | """ 64 | # 清除未被标记的物体对象 65 | temp = filter(CommonObject.has_new_flag, zip(object_list, [self._frame_flag] * len(object_list))) 66 | object_list = [each[0] for each in temp] # temp中的每个元素的格式为(object, flag), 只需获取object 67 | 68 | return object_list 69 | 70 | def get_frame_flag(self): 71 | """获取当前帧标记""" 72 | return self._frame_flag 73 | 74 | def object_track(self, object_list, new_matches_list): 75 | """ 76 | 计算物体运动轨迹, 将新的轨迹坐标添加到对应的object中, 并更新object_list列表 77 | 78 | :param object_list: 物体列表 79 | :param new_matches_list: 检测到的新的物体坐标 80 | :return: 物体列表 81 | """ 82 | self._update_frame_flag() 83 | 84 | get_last_points = map(CommonObject.get_last_point, object_list) # 获取每个物体上一次所在位置的坐标 85 | object_last_point_list = np.array([last_point for last_point in get_last_points]) 86 | 87 | # 利用KNN算法的思想, 将新的坐标进行分类, 匹配最近的轨迹 88 | for (x1, y1, x2, y2), (new_cx, new_cy) in new_matches_list: 89 | # 逼近分割线, 避免识别到的矩形框变形严重(过小) 90 | if new_cy < 30: 91 | continue 92 | 93 | distance_square = self._calculate_distance_square(object_last_point_list, new_cx, new_cy) 94 | 95 | y1, y2, new_cy = self._calculate_offset(y1, y2, new_cy) 96 | 97 | # 判断该点是否属于已存在的物体 98 | is_exist = distance_square.min() < self.centroid_threshold_square 99 | if is_exist: 100 | min_distance_index = distance_square.argmin() # 取出与新点距离平方最小的(匹配的)旧点的序号 101 | selected_object = object_list[min_distance_index] # 根据匹配的旧点序号, 取出对应的物体 102 | 103 | selected_object.update_point((x1, y1, x2, y2), (new_cx, new_cy), self._frame_flag) # 更新物体运动轨迹 104 | 105 | # 该点不属于任何已存在的物体 106 | else: 107 | # 创建新的物体并添加到object_list中 108 | new_object = CommonObject((x1, y1, x2, y2), (new_cx, new_cy), self._frame_flag) 109 | object_list.append(new_object) 110 | 111 | object_list = self._update_object_list(object_list) # 去除未被标记的物体 112 | 113 | return object_list 114 | -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | from object_detection import ObjectDetection 2 | from object_track import ObjectTrackKNN 3 | from object_counting import ObjectCounting 4 | from object_counting_log import ObjectCountingLog 5 | from object_counting_pipeline import ObjectCountingPipeline 6 | 7 | import cv2 as cv 8 | import numpy as np 9 | from math import sqrt 10 | 11 | 12 | def draw_frame(frame, object_list, string_and_coordinate, font, counting_log=None, split_line=None, counting_line=None): 13 | """ 14 | 绘制图像 15 | 16 | :param frame: 图像 17 | :type object_list: list 18 | :param object_list: 物体列表 19 | :type string_and_coordinate: list 20 | :param string_and_coordinate: 显示的字符串和坐标的列表 21 | :param font: 字体 22 | :param counting_log: 物体计数日志 23 | :type split_line: int 24 | :param split_line: 检测分割线的y轴坐标 25 | :type counting_line: int 26 | :param counting_line: 计数线的y轴坐标 27 | :return: frame 28 | """ 29 | height, width = frame.shape[:2] 30 | show_frame = np.zeros((height + 160, width, 3), dtype=np.uint8) 31 | show_frame[:height, :] = frame 32 | 33 | # 绘制分割线 34 | if split_line: 35 | cv.line(show_frame, (0, split_line), (1280, split_line), (0, 255, 0), 2) 36 | 37 | # 绘制计数线 38 | if counting_line: 39 | cv.line(show_frame, (0, counting_line), (1280, counting_line), (255, 255, 0), 1) 40 | 41 | # 绘制物体追踪矩形和运动轨迹 42 | for each_object in object_list: 43 | # 绘制矩形 44 | x1, y1, x2, y2 = each_object.rect 45 | cv.rectangle(show_frame, (x1, y1), (x2, y2), (255, 0, 0), 2) 46 | 47 | centroid_count = len(each_object.points) 48 | color = (0, 0, 255) 49 | # 存在两个以上的点, 可以绘制轨迹 50 | if centroid_count >= 2: 51 | # 被计数过的物体和未被计数过的的物体轨迹颜色不同 52 | if each_object.is_counted(): 53 | color = (0, 255, 255) 54 | 55 | for i in range(centroid_count - 1): 56 | thickness = int(sqrt((i + 1) * 2.5)) # 运动轨迹线条粗细 57 | cv.line(show_frame, each_object.points[i], each_object.points[i + 1], color, thickness) 58 | 59 | # 只存在一个点, 标记中心 60 | else: 61 | cv.circle(show_frame, each_object.points[0], 1, color, 1) 62 | 63 | if counting_log: 64 | for i, object_pic in enumerate(counting_log.get_counting_pic_list()): 65 | show_frame[height:, (7 - i) * 160:(8 - i) * 160] = object_pic 66 | 67 | for string, coordinate in string_and_coordinate: 68 | cv.putText(show_frame, string, coordinate, font, 1, (0, 0, 0), 1) 69 | 70 | return show_frame 71 | 72 | 73 | if __name__ == '__main__': 74 | print("打开视频") 75 | video = "./video/3.mp4" 76 | cap = cv.VideoCapture(video) 77 | 78 | print("初始化物体选择模型") 79 | history = 500 80 | var_threshold = 64 81 | learn_rate = 0.005 82 | bg_subtractor = cv.createBackgroundSubtractorMOG2(history, var_threshold, detectShadows=False) 83 | kernel = cv.getStructuringElement(cv.MORPH_ELLIPSE, (3, 3)) 84 | detection_model = ObjectDetection(bg_subtractor, history, learn_rate, kernel) 85 | detection_model.train_model(cap) 86 | 87 | print("初始化物体追踪模型") 88 | split_line = 368 89 | centroid_threshold_square = 1300 90 | track_model = ObjectTrackKNN(split_line, centroid_threshold_square) 91 | 92 | print("初始化物体计数模型") 93 | counting_line = split_line + 50 94 | counting_model = ObjectCounting(counting_line) 95 | 96 | print("初始化物体计数日志") 97 | counting_log = ObjectCountingLog(split_line) 98 | 99 | print("初始化pipeline") 100 | pipeline = ObjectCountingPipeline(detection_model, track_model, counting_model) 101 | 102 | print("初始化视频输出器") 103 | fourcc = cv.VideoWriter_fourcc(*'XVID') 104 | out = cv.VideoWriter('output_{}_{}_{}.avi'.format(history, var_threshold, learn_rate), fourcc, 25.0, (1280, 880)) 105 | 106 | font = cv.FONT_HERSHEY_SIMPLEX 107 | 108 | object_list = [] # 检测到的物体列表 109 | counting_pic_list = [] # 记录已计数的物体 110 | object_in = object_out = 0 # 物体的进出个数 111 | start_time = end_time = total_time = fps = 0 # 计算fps所需 112 | tick_frequency = cv.getTickFrequency() 113 | 114 | fps_string = "fps: 0" 115 | 116 | retval, frame = cap.read() 117 | while retval: 118 | frame_temp = frame[split_line:, :] 119 | 120 | start_time = cv.getTickCount() 121 | object_list, new_object_in, new_object_out = pipeline.run(frame_temp, 35, 35, object_list, counting_log) 122 | object_in += new_object_in 123 | object_out += new_object_out 124 | 125 | counting_string = "in: {} out: {}".format(object_in, object_out) 126 | 127 | string_and_coordinate = [(counting_string, (40, 40)), (fps_string, (1100, 40))] 128 | 129 | frame = draw_frame(frame, object_list, string_and_coordinate, font, counting_log, split_line, counting_line) 130 | 131 | cv.imshow("video", frame) 132 | 133 | out.write(frame) 134 | 135 | key = cv.waitKey(10) & 0xff 136 | 137 | retval, frame = cap.read() 138 | 139 | fps += 1 140 | end_time = cv.getTickCount() 141 | 142 | # 一秒更新一次fps 143 | total_time += (end_time - start_time) / tick_frequency 144 | if total_time >= 1: 145 | fps_string = "fps: {}".format(fps) 146 | fps = 0 147 | total_time = 0 148 | 149 | if key == ord('q'): 150 | break 151 | elif key == ord(' '): 152 | cv.waitKey(0) 153 | 154 | cv.destroyAllWindows() 155 | cap.release() 156 | --------------------------------------------------------------------------------