【python】OpenCV—Tracking(10.6)—People Counting
文章目录
- 1、功能描述
- 2、代码实现
- 3、效果展示
- 4、完整代码
- 5、涉及到的库函数
- 6、参考来自
更多有趣的代码示例,可参考【Programming】
1、功能描述
借助 opencv-python,用 SSD 人形检测模型和质心跟踪方法实现对人群的计数
基于质心的跟踪可以参考 【python】OpenCV—Tracking(10.4)—Centroid,本文不过多介绍
2、代码实现
工程目录
安装依赖库
requirements.txt
schedule==1.1.0
numpy==1.24.3
argparse==1.4.0
imutils==0.5.4
dlib==19.24.1
opencv-python==4.5.5.64
scipy==1.10.1
cmake==3.22.5
模型文件
网络输入大小 1x3x300x300
trackableobject.py
class TrackableObject:def __init__(self, objectID, centroid):# store the object ID, then initialize a list of centroids# using the current centroidself.objectID = objectIDself.centroids = [centroid]# initialize a boolean used to indicate if the object has# already been counted or notself.counted = False
TrackableObject 构造函数接受 objectID + centroid 并存储它们。
centroids 变量是一个列表,因为它将包含对象的质心位置历史记录。
构造函数还将 counted 初始化为 False ,表示该对象还没有被计数。
people_counter.py
导入必要的库函数
from tracker.centroidtracker import CentroidTracker
from tracker.trackableobject import TrackableObject
from imutils.video import VideoStream
from itertools import zip_longest
from utils.mailer import Mailer
from imutils.video import FPS
from utils import thread
import numpy as np
import threading
import argparse
import datetime
import schedule
import logging
import imutils
import time
import dlib
import json
import csv
import cv2
dlib 库将用于其相关跟踪器实现
记录程序运行的时间,配置 log,初始化相关参数
# execution start time
start_time = time.time()
# setup logger
logging.basicConfig(level = logging.INFO, format = "[INFO] %(message)s")
logger = logging.getLogger(__name__)
# initiate features config.
with open("utils/config.json", "r") as file:config = json.load(file)
config.json 内容如下
{"Email_Send": "","Email_Receive": "","Email_Password": "","url": "","ALERT": false,"Threshold": 10,"Thread": false,"Log": false,"Scheduler": false,"Timer": false
}
参数传递
def parse_arguments():# function to parse the argumentsap = argparse.ArgumentParser()ap.add_argument("-p", "--prototxt", required=False, default="detector/MobileNetSSD_deploy.prototxt",help="path to Caffe 'deploy' prototxt file")ap.add_argument("-m", "--model", required=False, default="detector/MobileNetSSD_deploy.caffemodel",help="path to Caffe pre-trained model")ap.add_argument("-i", "--input", type=str, default="utils/data/tests/test_1.mp4",help="path to optional input video file")ap.add_argument("-o", "--output", type=str,help="path to optional output video file")# confidence default 0.4ap.add_argument("-c", "--confidence", type=float, default=0.4,help="minimum probability to filter weak detections")ap.add_argument("-s", "--skip-frames", type=int, default=30,help="# of skip frames between detections")args = vars(ap.parse_args())return args
- prototxt :Caffe 部署 prototxt 文件的路径。
- model :Caffe 预训练 CNN 模型的路径。
- input : 可选的输入视频文件路径。
- output :可选的输出视频路径。如果未指定路径,则不会录制视频。
- confidence :默认值为 0.4 ,这是有助于过滤掉弱检测的最小概率阈值。
- skip-frames :在跟踪对象上再次运行我们的 DNN 检测器之前要跳过的帧数。请记住,对象检测的计算成本很高,但它确实有助于我们的跟踪器重新评估帧中的对象。默认情况下,我们在使用 OpenCV DNN 模块和我们的 CNN 单次检测器模型检测对象之间跳过 30 帧。
保存 log,如果调用的话,会生成 counting_data.csv
def log_data(move_in, in_time, move_out, out_time):# function to log the counting datadata = [move_in, in_time, move_out, out_time]# transpose the data to align the columns properlyexport_data = zip_longest(*data, fillvalue = '')with open('utils/data/logs/counting_data.csv', 'w', newline = '') as myfile:wr = csv.writer(myfile, quoting = csv.QUOTE_ALL)if myfile.tell() == 0: # check if header rows are already existingwr.writerow(("Move In", "In Time", "Move Out", "Out Time"))wr.writerows(export_data)
eg:
核心函数 people_counter
(main function for people_counter.py),下面看看实现
首先定义好 MobileNet SSD 目标检测网络能预测的所有类别
args = parse_arguments()# initialize the list of class labels MobileNet SSD was trained to detectCLASSES = ["background", "aeroplane", "bicycle", "bird", "boat","bottle", "bus", "car", "cat", "chair", "cow", "diningtable","dog", "horse", "motorbike", "person", "pottedplant", "sheep","sofa", "train", "tvmonitor"]
载入 caffe 网络
# load our serialized model from disknet = cv2.dnn.readNetFromCaffe(args["prototxt"], args["model"])
如果没有配置输入视频,则开启网页,网址在 config.json 中配置
# if a video path was not supplied, grab a reference to the ip cameraif not args.get("input", False):logger.info("Starting the live stream..")vs = VideoStream(config["url"]).start()time.sleep(2.0)# otherwise, grab a reference to the video fileelse:logger.info("Starting the video..")vs = cv2.VideoCapture(args["input"])
初始化一些参数配置
# initialize the video writer (we'll instantiate later if need be)writer = None# initialize the frame dimensions (we'll set them as soon as we read# the first frame from the video)W = NoneH = None# instantiate our centroid tracker, then initialize a list to store# each of our dlib correlation trackers, followed by a dictionary to# map each unique object ID to a TrackableObjectct = CentroidTracker(maxDisappeared=40, maxDistance=50)trackers = []trackableObjects = {}# initialize the total number of frames processed thus far, along# with the total number of objects that have moved either up or downtotalFrames = 0totalDown = 0totalUp = 0# initialize empty lists to store the counting datatotal = []move_out = []move_in =[]out_time = []in_time = []# start the frames per second throughput estimatorfps = FPS().start()
- writer:我们的视频写入器。如果我们正在写入视频,我们稍后会实例化这个对象。
- W 和 H:我们的帧尺寸。我们需要将这些插入到 cv2.VideoWriter 中。
- ct:CentroidTracker。
- trackers :存储 dlib 相关跟踪器的列表。
- trackableObjects :将 objectID 映射到 TrackableObject 的字典。
- totalFrames :处理的帧总数。
- totalDown 和 totalUp :向下或向上移动的对象/人的总数。
- fps :我们用于基准测试的每秒帧数估计器。
遍历读取视频流中的每一帧图片
读取失败会退出
如果读取成功,存储好原始图片的长宽,把图片 resize 到宽固定长度为 500
如果要保存输出视频,则配置好 cv2.VideoWriter
中的视频相关参数
# loop over frames from the video streamwhile True:# grab the next frame and handle if we are reading from either# VideoCapture or VideoStreamframe = vs.read()frame = frame[1] if args.get("input", False) else frame# if we are viewing a video and we did not grab a frame then we# have reached the end of the videoif args["input"] is not None and frame is None:break# resize the frame to have a maximum width of 500 pixels (the# less data we have, the faster we can process it), then convert# the frame from BGR to RGB for dlibframe = imutils.resize(frame, width = 500)rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)# if the frame dimensions are empty, set themif W is None or H is None:(H, W) = frame.shape[:2]# if we are supposed to be writing a video to disk, initialize# the writerif args["output"] is not None and writer is None:fourcc = cv2.VideoWriter_fourcc(*"mp4v")writer = cv2.VideoWriter(args["output"], fourcc, 30,(W, H), True)
例子视频的尺寸为 (300, 402, 3)
第一帧
resize 后, 图片大小为 (373, 500, 3)
将状态初始化为 Waiting。可能的状态包括:
- Waiting:正在等待检测和跟踪人员。
- Detecting:正在使用 MobileNet SSD 检测人员。
- Tracking:人们在帧中被跟踪,正在计算 totalUp 和 totalDown 。
# initialize the current status along with our list of bounding# box rectangles returned by either (1) our object detector or# (2) the correlation trackersstatus = "Waiting"rects = []# check to see if we should run a more computationally expensive# object detection method to aid our trackerif totalFrames % args["skip_frames"] == 0:# set the status and initialize our new set of object trackersstatus = "Detecting"trackers = []# convert the frame to a blob and pass the blob through the# network and obtain the detectionsblob = cv2.dnn.blobFromImage(frame, 0.007843, (W, H), 127.5)net.setInput(blob)detections = net.forward()
检测时隔 skip_frames
帧才进行一次的,因为检测开销会比跟踪开销大,中间帧只用跟踪来进行检测框的更新
配置好输入数据,前向网络
遍历检测到的所有目标
获取其类别得分
得分高于设定的阈值 confidence
检测框才进行后续的处理
如果检测的类别不是 person
,也会跳过
# loop over the detectionsfor i in np.arange(0, detections.shape[2]):# extract the confidence (i.e., probability) associated# with the predictionconfidence = detections[0, 0, i, 2]# filter out weak detections by requiring a minimum# confidenceif confidence > args["confidence"]:# extract the index of the class label from the# detections listidx = int(detections[0, 0, i, 1])# if the class label is not a person, ignore itif CLASSES[idx] != "person":continue
第一帧网络预测结果 detections
的 shape 为 (1, 1, 100, 7)
7 的构成
array([ 0. , 15. , 0.99846387, 0.34079546, 0.1428327 ,0.53464395, 0.5692927 ], dtype=float32)
第二个维度是 class,第三维度是 score,后面四个维度是 bbox 坐标
第一次检测到人的 bbox 为 array([170.39772868, 53.27659577, 267.32197404, 212.3461861 ])
计算 box
实例化 dlib 相关跟踪器,将对象的边界框坐标传递给 dlib.rectangle,将结果存储为 rect
。
开始跟踪,并将跟踪器附加到跟踪器列表 trackers
中
这是每 N 个跳帧执行的所有操作的封装
# compute the (x, y)-coordinates of the bounding box# for the objectbox = detections[0, 0, i, 3:7] * np.array([W, H, W, H])(startX, startY, endX, endY) = box.astype("int")# construct a dlib rectangle object from the bounding# box coordinates and then start the dlib correlation# trackertracker = dlib.correlation_tracker()rect = dlib.rectangle(startX, startY, endX, endY)tracker.start_track(rgb, rect)# add the tracker to our list of trackers so we can# utilize it during skip framestrackers.append(tracker)
注意,每 skip_frames
帧才判断一次是否检测到人,所以第一帧没有人的话,要等到 skip_frames
帧后才再次启用人形检测器
中间帧用跟踪器而不是目标检测器来定位矩形框
遍历可用跟踪器。
将状态更新为Tracking并获取对象位置。
提取位置坐标,然后在 rects 列表中填充信息。
# otherwise, we should utilize our object *trackers* rather than# object *detectors* to obtain a higher frame processing throughputelse:# loop over the trackersfor tracker in trackers:# set the status of our system to be 'tracking' rather# than 'waiting' or 'detecting'status = "Tracking"# update the tracker and grab the updated positiontracker.update(rgb)pos = tracker.get_position()# unpack the position objectstartX = int(pos.left())startY = int(pos.top())endX = int(pos.right())endY = int(pos.bottom())# add the bounding box coordinates to the rectangles listrects.append((startX, startY, endX, endY))
第一次跟踪后的 bbox 更新为 [(167, 55, 264, 215)]
画一条水平可视化线(人们必须穿过它才能被跟踪)
# draw a horizontal line in the center of the frame -- once an# object crosses this line we will determine whether they were# moving 'up' or 'down'cv2.line(frame, (0, H // 2), (W, H // 2), (0, 0, 0), 3)cv2.putText(frame, "-Prediction border - Entrance-", (10, H - ((i * 20) + 200)),cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 0), 1)
并使用质心跟踪器来更新我们的对象质心
# use the centroid tracker to associate the (1) old object# centroids with (2) the newly computed object centroidsobjects = ct.update(rects)
此时 rects 为第一次跟踪后的 bbox,[(167, 55, 264, 215)], 质心 objects 的结果为OrderedDict({0: array([215, 135])})
,用红色点可视化如下
遍历 ID 和质心
我们尝试为当前的 objectID 获取 TrackableObject。如果 objectID 的 TrackableObject 不存在,我们就创建一个。否则,已经存在一个 TrackableObject ,所以我们需要弄清楚对象(人)是向上还是向下移动。
to.centroids
当前帧和历史帧,同一个 id 的质心,eg 第二帧的时候 [array([215, 135]), array([218, 139])]
,第三帧的时候 [array([215, 135]), array([218, 139]), array([217, 144])]
获取给定对象之前所有质心位置的 y 坐标值。然后,通过取当前质心位置与之前所有质心位置的平均值之间的差来计算方向。
我们取均值的原因是为了确保我们的方向跟踪更稳定。如果我们只存储这个人之前的质心位置,我们就有可能出现错误的方向计数
通过取均值,我们可以让我们的人计算得更准确。
如果 TrackableObject 还没有被计数,我们需要确定它是否已经准备好被计数,通过:
- 检查 direction 是否为负(表示对象向上移动)并且质心在中心线上方。在这种情况下,我们增加 totalUp。
- 或者检查 direction 是否为正(表示物体正在向下移动)且质心在中心线以下。如果这是真的,我们增加totalDown。
最后,我们将 TrackableObject 存储在 trackableObjects 字典中,这样我们就可以在捕获下一帧时获取并更新它。
# loop over the tracked objectsfor (objectID, centroid) in objects.items():# check to see if a trackable object exists for the current# object IDto = trackableObjects.get(objectID, None)# if there is no existing trackable object, create oneif to is None:to = TrackableObject(objectID, centroid)# otherwise, there is a trackable object so we can utilize it# to determine directionelse:# the difference between the y-coordinate of the *current*# centroid and the mean of *previous* centroids will tell# us in which direction the object is moving (negative for# 'up' and positive for 'down')y = [c[1] for c in to.centroids] # 历史同 id 质心的 y 坐标direction = centroid[1] - np.mean(y) # centroid 是当前帧的to.centroids.append(centroid)# check to see if the object has been counted or notif not to.counted:# if the direction is negative (indicating the object# is moving up) AND the centroid is above the center# line, count the objectif direction < 0 and centroid[1] < H // 2:totalUp += 1date_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")move_out.append(totalUp)out_time.append(date_time)to.counted = True# if the direction is positive (indicating the object# is moving down) AND the centroid is below the# center line, count the objectelif direction > 0 and centroid[1] > H // 2:totalDown += 1date_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")move_in.append(totalDown)in_time.append(date_time)# if the people limit exceeds over threshold, send an email alertif sum(total) >= config["Threshold"]:cv2.putText(frame, "-ALERT: People limit exceeded-", (10, frame.shape[0] - 80),cv2.FONT_HERSHEY_COMPLEX, 0.5, (0, 0, 255), 2)if config["ALERT"]:logger.info("Sending email alert..")email_thread = threading.Thread(target = send_mail)email_thread.daemon = Trueemail_thread.start()logger.info("Alert sent!")to.counted = True# compute the sum of total people insidetotal = []total.append(len(move_in) - len(move_out))# store the trackable object in our dictionarytrackableObjects[objectID] = to
画出质心,并向帧写入文本
# draw both the ID of the object and the centroid of the# object on the output frametext = "ID {}".format(objectID)cv2.putText(frame, text, (centroid[0] - 10, centroid[1] - 10),cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 2)cv2.circle(frame, (centroid[0], centroid[1]), 4, (255, 255, 255), -1)# construct a tuple of information we will be displaying on the frameinfo_status = [("Exit", totalUp),("Enter", totalDown),("Status", status),]info_total = [("Total people inside", ', '.join(map(str, total))),]# display the outputfor (i, (k, v)) in enumerate(info_status):text = "{}: {}".format(k, v)cv2.putText(frame, text, (10, H - ((i * 20) + 20)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 0), 2)for (i, (k, v)) in enumerate(info_total):text = "{}: {}".format(k, v)cv2.putText(frame, text, (265, H - ((i * 20) + 60)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 2)
保存 log,可视化跟踪过程,结果写入视频,按键监控,触发 q
就退出
# initiate a simple log to save the counting dataif config["Log"]:log_data(move_in, in_time, move_out, out_time)# check to see if we should write the frame to diskif writer is not None:writer.write(frame)# show the output framecv2.imshow("Real-Time Monitoring/Analysis Window", frame)key = cv2.waitKey(1) & 0xFF# if the `q` key was pressed, break from the loopif key == ord("q"):break# increment the total number of frames processed thus far and# then update the FPS countertotalFrames += 1fps.update()
时间统计,资源释放
# initiate the timerif config["Timer"]:# automatic timer to stop the live stream (set to 8 hours/28800s)end_time = time.time()num_seconds = (end_time - start_time)if num_seconds > 28800:break# stop the timer and display FPS informationfps.stop()logger.info("Elapsed time: {:.2f}".format(fps.elapsed()))logger.info("Approx. FPS: {:.2f}".format(fps.fps()))# release the camera device/resource (issue 15)if config["Thread"]:vs.release()# close any open windowscv2.destroyAllWindows()
3、效果展示
test_out
4、完整代码
-
链接: https://pan.baidu.com/s/14cBLhxVtsn6bNQQ5GqbPEg?pwd=x8md
-
提取码: x8md
核心代码 people_counter.py
from tracker.centroidtracker import CentroidTracker
from tracker.trackableobject import TrackableObject
from imutils.video import VideoStream
from itertools import zip_longest
from utils.mailer import Mailer
from imutils.video import FPS
from utils import thread
import numpy as np
import threading
import argparse
import datetime
import schedule
import logging
import imutils
import time
import dlib
import json
import csv
import cv2# execution start time
start_time = time.time()
# setup logger
logging.basicConfig(level = logging.INFO, format = "[INFO] %(message)s")
logger = logging.getLogger(__name__)
# initiate features config.
with open("utils/config.json", "r") as file:config = json.load(file)def parse_arguments():# function to parse the argumentsap = argparse.ArgumentParser()ap.add_argument("-p", "--prototxt", required=False, default="detector/MobileNetSSD_deploy.prototxt",help="path to Caffe 'deploy' prototxt file")ap.add_argument("-m", "--model", required=False, default="detector/MobileNetSSD_deploy.caffemodel",help="path to Caffe pre-trained model")ap.add_argument("-i", "--input", type=str, default="utils/data/tests/test_1.mp4",help="path to optional input video file")ap.add_argument("-o", "--output", type=str,help="path to optional output video file")# confidence default 0.4ap.add_argument("-c", "--confidence", type=float, default=0.4,help="minimum probability to filter weak detections")ap.add_argument("-s", "--skip-frames", type=int, default=30,help="# of skip frames between detections")args = vars(ap.parse_args())return args"""
python people_counter.py --prototxt detector/MobileNetSSD_deploy.prototxt
--model detector/MobileNetSSD_deploy.caffemodel
--input utils/data/tests/test_1.mp4
"""def send_mail():# function to send the email alertsMailer().send(config["Email_Receive"])def log_data(move_in, in_time, move_out, out_time):# function to log the counting datadata = [move_in, in_time, move_out, out_time]# transpose the data to align the columns properlyexport_data = zip_longest(*data, fillvalue = '')with open('utils/data/logs/counting_data.csv', 'w', newline = '') as myfile:wr = csv.writer(myfile, quoting = csv.QUOTE_ALL)if myfile.tell() == 0: # check if header rows are already existingwr.writerow(("Move In", "In Time", "Move Out", "Out Time"))wr.writerows(export_data)def people_counter():# main function for people_counter.pyargs = parse_arguments()# initialize the list of class labels MobileNet SSD was trained to detectCLASSES = ["background", "aeroplane", "bicycle", "bird", "boat","bottle", "bus", "car", "cat", "chair", "cow", "diningtable","dog", "horse", "motorbike", "person", "pottedplant", "sheep","sofa", "train", "tvmonitor"]# load our serialized model from disknet = cv2.dnn.readNetFromCaffe(args["prototxt"], args["model"])# if a video path was not supplied, grab a reference to the ip cameraif not args.get("input", False):logger.info("Starting the live stream..")vs = VideoStream(config["url"]).start()time.sleep(2.0)# otherwise, grab a reference to the video fileelse:logger.info("Starting the video..")vs = cv2.VideoCapture(args["input"])# initialize the video writer (we'll instantiate later if need be)writer = None# initialize the frame dimensions (we'll set them as soon as we read# the first frame from the video)W = NoneH = None# instantiate our centroid tracker, then initialize a list to store# each of our dlib correlation trackers, followed by a dictionary to# map each unique object ID to a TrackableObjectct = CentroidTracker(maxDisappeared=40, maxDistance=50)trackers = []trackableObjects = {}# initialize the total number of frames processed thus far, along# with the total number of objects that have moved either up or downtotalFrames = 0totalDown = 0totalUp = 0# initialize empty lists to store the counting datatotal = []move_out = []move_in =[]out_time = []in_time = []# start the frames per second throughput estimatorfps = FPS().start()if config["Thread"]:vs = thread.ThreadingClass(config["url"])# loop over frames from the video streamwhile True:# grab the next frame and handle if we are reading from either# VideoCapture or VideoStreamframe = vs.read()frame = frame[1] if args.get("input", False) else frame# if we are viewing a video and we did not grab a frame then we# have reached the end of the videoif args["input"] is not None and frame is None:break# resize the frame to have a maximum width of 500 pixels (the# less data we have, the faster we can process it), then convert# the frame from BGR to RGB for dlibframe = imutils.resize(frame, width=500)rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)# if the frame dimensions are empty, set themif W is None or H is None:(H, W) = frame.shape[:2]# if we are supposed to be writing a video to disk, initialize# the writerif args["output"] is not None and writer is None:fourcc = cv2.VideoWriter_fourcc(*"mp4v")writer = cv2.VideoWriter(args["output"], fourcc, 30,(W, H), True)# initialize the current status along with our list of bounding# box rectangles returned by either (1) our object detector or# (2) the correlation trackersstatus = "Waiting"rects = []# check to see if we should run a more computationally expensive# object detection method to aid our trackerif totalFrames % args["skip_frames"] == 0:# set the status and initialize our new set of object trackersstatus = "Detecting"trackers = []# convert the frame to a blob and pass the blob through the# network and obtain the detectionsblob = cv2.dnn.blobFromImage(frame, 0.007843, (W, H), 127.5)net.setInput(blob)detections = net.forward()# loop over the detectionsfor i in np.arange(0, detections.shape[2]):# extract the confidence (i.e., probability) associated# with the predictionconfidence = detections[0, 0, i, 2]# filter out weak detections by requiring a minimum# confidenceif confidence > args["confidence"]:# extract the index of the class label from the# detections listidx = int(detections[0, 0, i, 1])# if the class label is not a person, ignore itif CLASSES[idx] != "person":continue# compute the (x, y)-coordinates of the bounding box# for the objectbox = detections[0, 0, i, 3:7] * np.array([W, H, W, H])(startX, startY, endX, endY) = box.astype("int")# construct a dlib rectangle object from the bounding# box coordinates and then start the dlib correlation# trackertracker = dlib.correlation_tracker()rect = dlib.rectangle(startX, startY, endX, endY)tracker.start_track(rgb, rect)# add the tracker to our list of trackers so we can# utilize it during skip framestrackers.append(tracker)# otherwise, we should utilize our object *trackers* rather than# object *detectors* to obtain a higher frame processing throughputelse:# loop over the trackersfor tracker in trackers:# set the status of our system to be 'tracking' rather# than 'waiting' or 'detecting'status = "Tracking"# update the tracker and grab the updated positiontracker.update(rgb)pos = tracker.get_position()# unpack the position objectstartX = int(pos.left())startY = int(pos.top())endX = int(pos.right())endY = int(pos.bottom())# add the bounding box coordinates to the rectangles listrects.append((startX, startY, endX, endY))# draw a horizontal line in the center of the frame -- once an# object crosses this line we will determine whether they were# moving 'up' or 'down'cv2.line(frame, (0, H // 2), (W, H // 2), (0, 0, 0), 3)cv2.putText(frame, "-Prediction border - Entrance-", (10, H - ((i * 20) + 200)),cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 0), 1)# use the centroid tracker to associate the (1) old object# centroids with (2) the newly computed object centroidsobjects = ct.update(rects)# loop over the tracked objectsfor (objectID, centroid) in objects.items():# check to see if a trackable object exists for the current# object IDto = trackableObjects.get(objectID, None)# if there is no existing trackable object, create oneif to is None:to = TrackableObject(objectID, centroid)# otherwise, there is a trackable object so we can utilize it# to determine directionelse:# the difference between the y-coordinate of the *current*# centroid and the mean of *previous* centroids will tell# us in which direction the object is moving (negative for# 'up' and positive for 'down')y = [c[1] for c in to.centroids]direction = centroid[1] - np.mean(y)to.centroids.append(centroid)# check to see if the object has been counted or notif not to.counted:# if the direction is negative (indicating the object# is moving up) AND the centroid is above the center# line, count the objectif direction < 0 and centroid[1] < H // 2:totalUp += 1date_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")move_out.append(totalUp)out_time.append(date_time)to.counted = True# if the direction is positive (indicating the object# is moving down) AND the centroid is below the# center line, count the objectelif direction > 0 and centroid[1] > H // 2:totalDown += 1date_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")move_in.append(totalDown)in_time.append(date_time)# if the people limit exceeds over threshold, send an email alertif sum(total) >= config["Threshold"]:cv2.putText(frame, "-ALERT: People limit exceeded-", (10, frame.shape[0] - 80),cv2.FONT_HERSHEY_COMPLEX, 0.5, (0, 0, 255), 2)if config["ALERT"]:logger.info("Sending email alert..")email_thread = threading.Thread(target = send_mail)email_thread.daemon = Trueemail_thread.start()logger.info("Alert sent!")to.counted = True# compute the sum of total people insidetotal = []total.append(len(move_in) - len(move_out))# store the trackable object in our dictionarytrackableObjects[objectID] = to# draw both the ID of the object and the centroid of the# object on the output frametext = "ID {}".format(objectID)cv2.putText(frame, text, (centroid[0] - 10, centroid[1] - 10),cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 2)cv2.circle(frame, (centroid[0], centroid[1]), 4, (255, 255, 255), -1)# construct a tuple of information we will be displaying on the frameinfo_status = [("Exit", totalUp),("Enter", totalDown),("Status", status),]info_total = [("Total people inside", ', '.join(map(str, total))),]# display the outputfor (i, (k, v)) in enumerate(info_status):text = "{}: {}".format(k, v)cv2.putText(frame, text, (10, H - ((i * 20) + 20)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 0), 2)for (i, (k, v)) in enumerate(info_total):text = "{}: {}".format(k, v)cv2.putText(frame, text, (265, H - ((i * 20) + 60)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 2)# initiate a simple log to save the counting dataif config["Log"]:log_data(move_in, in_time, move_out, out_time)# check to see if we should write the frame to diskif writer is not None:writer.write(frame)# show the output framecv2.imshow("Real-Time Monitoring/Analysis Window", frame)key = cv2.waitKey(1) & 0xFF# if the `q` key was pressed, break from the loopif key == ord("q"):break# increment the total number of frames processed thus far and# then update the FPS countertotalFrames += 1fps.update()# initiate the timerif config["Timer"]:# automatic timer to stop the live stream (set to 8 hours/28800s)end_time = time.time()num_seconds = (end_time - start_time)if num_seconds > 28800:break# stop the timer and display FPS informationfps.stop()logger.info("Elapsed time: {:.2f}".format(fps.elapsed()))logger.info("Approx. FPS: {:.2f}".format(fps.fps()))# release the camera device/resource (issue 15)if config["Thread"]:vs.release()# close any open windowscv2.destroyAllWindows()# initiate the scheduler
if config["Scheduler"]:# runs at every day (09:00 am)schedule.every().day.at("09:00").do(people_counter)while True:schedule.run_pending()
else:people_counter()
运行命令
python people_counter.py --prototxt detector/MobileNetSSD_deploy.prototxt --model detector/MobileNetSSD_deploy.caffemodel --input utils/data/tests/test_1.mp4
我把很多参数传递都改为默认的了,直接运行也可以
5、涉及到的库函数
- schedule==1.1.0
- numpy==1.24.3
- argparse==1.4.0
- imutils==0.5.4
- dlib==19.24.1
- opencv-python==4.5.5.64
- scipy==1.10.1
- cmake==3.22.5
注意版本的兼容性,dlib 在线编译报错可以离线安装
6、参考来自
- https://github.com/saimj7/People-Counting-in-Real-Time
- 目标跟踪(6)OpenCV 人员计数器
- 【python】OpenCV—Tracking(10.4)—Centroid
- 【python】OpenCV—Tracking(10.5)—dlib
更多有趣的代码示例,可参考【Programming】
相关文章:
【python】OpenCV—Tracking(10.6)—People Counting
文章目录 1、功能描述2、代码实现3、效果展示4、完整代码5、涉及到的库函数6、参考来自 更多有趣的代码示例,可参考【Programming】 1、功能描述 借助 opencv-python,用 SSD 人形检测模型和质心跟踪方法实现对人群的计数 基于质心的跟踪可以参考 【pyt…...
JavaSE学习(前端初体验)
文章目录 前言一、准备环境二、创建站点(创建一个文件夹)三、将站点部署到编写器中四、VScode实用小设置五、案例展示 前言 首先了解前端三件套:HTML、CSS、JS HTML:超文本标记语言、框架层、描述数据的; CSS…...
智慧城市像一张无形大网,如何紧密连接你我他?
智慧城市作为复杂巨系统,其核心在于通过技术创新构建无缝连接的网络,使物理空间与数字空间深度融合。这张"无形大网"由物联网感知层、城市数据中台、人工智能中枢、数字服务入口和安全信任机制五大支柱编织而成,正在重塑城市运行规…...
Linux常用命令
一、history 用于显示历史命令。 history 10显示最近10条历史命令。!200使用第200行的指令。history -c清空历史记录。 二、pwd 用于显示当前绝对路径。 pwd显示当前绝对路径。 三、ls 用于以行的形式显示当前文件夹下所有内容。 ls -a显示所有内容,包括隐藏文…...
【AI】SpringAI 第二弹:接入 DeepSeek 官方服务
一、接入 DeepSeek 官方服务 通过一个简单的案例演示接入 DeepSeek 实现简单的问答功能 1.添加依赖 <dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-starter-model-openai</artifactId> </dependency> 2…...
QT的信号槽的直接触发,队列触发,自动触发
在Qt中,信号槽机制是一个非常强大的特性,它用于实现对象之间的通信。除了默认的直接触发方式之外,Qt还提供了队列触发等不同的触发方式。 1. 直接触发(Direct Connection) 直接触发是最常见的连接方式,信…...
typescript html input无法输入解决办法
input里加上这个: onkeydown:(e: KeyboardEvent) > {e.stopPropagation();...
工厂能耗系统智能化解决方案 —— 安科瑞企业能源管控平台
安科瑞顾强 政策背景与“双碳”战略驱动 2025年《政府工作报告》明确提出“单位国内生产总值能耗降低3%左右”的目标,要求通过产业结构升级(如高耗能行业技术革新或转型)、能源结构优化(提高非化石能源占比)及数字化…...
栅格数据处理
一、栅格数据的引入与基本操作 (一)加载栅格数据 在 ArcPy 中,栅格数据可以通过 arcpy.Raster 类来加载。例如,如果你有一个存储在本地路径下的栅格数据文件(如 GeoTIFF 格式),可以这样加载&a…...
C语言文件操作
本文重点: 什么是文件 文件名 文件类型 文件缓冲区 文件指针 文件的打开和关闭 文件的顺序读写 文件的随机读写 文件结束的判定 什么是文件 磁盘上的文件是文件。 但是在程序设计中,我们一般谈的文件有两种:程序文件、数据文件 程序文件 包括源程序文…...
毛笔书体检测-hog+svm python opencv源码
链接:https://pan.baidu.com/s/1l-bw8zR9psv1HycmMqQBqQ?pwd2ibp 提取码:2ibp --来自百度网盘超级会员V2的分享 1、毛笔字检测运行流程 如果解压文件发现乱码,可以下载Bandizip 解压文件 数据集在百度网盘里面 将文件名字改成images c…...
基于YOLOV11的道路坑洼分析系统
基于YOLOV11的道路坑洼分析系统 【包含内容】 【一】项目提供完整源代码及详细注释 【二】系统设计思路与实现说明 【三】图形化界面与实时检测统计可视化功能 【技术栈】 ①:系统环境:Windows/MacOS/Linux多平台支持,推荐NVIDIA GPU加速 ②…...
【系统搭建】DPDK安装配置与helloworld运行
一,安装相关依赖 1. 安装依赖 sudo apt update && sudo apt install -y \build-essential libnuma-dev meson ninja-build pciutils#安装Python3与PIP3 sudo apt install python3-pip2. 升级 pip 和 setuptools sudo apt install python3-pip python3-de…...
Distortion, Animation Raymarching
这节课的主要目的是对uv进行操作,实现一些动画的效果,实际就是采样的动画 struct texDistort {float2 texScale(float2 uv, float2 scale){float2 texScale (uv - 0.5) * scale 0.5;return texScale;}float2 texRotate(float2 uv, float angle){float…...
架构风格(高软59)
系列文章目录 架构风格 文章目录 系列文章目录前言一、架构风格定义?二、架构风格分类总结 前言 本节讲明架构风格知识点。 一、架构风格定义? 二、架构风格分类 总结 就是高软笔记,大佬请略过!...
免费使用RooCode + Boomerang AI + Gemini 2.5 Pro开发套件
若您正在寻找利用免费AI工具简化应用开发的方法,这份指南将为您揭开惊喜。 我们将详解如何免费整合RooCode、Boomerang AI智能代理与Google Gemini 2.5 Pro API,在Visual Studio Code中实现自动化编程加速。 这套方案能让您在几分钟内从创意跃迁至可运行原型。 套件构成与…...
《MAmmoTH2: Scaling Instructions from the Web》全文翻译
《MAmmoTH2: Scaling Instructions from the Web》 MAmmoTH2:从网络规模化采集指令数据 摘要 指令调优提升了大语言模型(LLM)的推理能力,其中数据质量和规模化是关键因素。大多数指令调优数据来源于人工众包或GPT-4蒸馏。我们提…...
解决Ubuntu终端命令不能补全的问题
使用命令: sudo vi /etc/bash.bashr 把框出的部分取消注释,取消后截图如下,保存退出: 使用命令env -i bash --noprofile --norc, 进行测试,查看tab自动补全是否可以使用。 tab键可正常使用, env -i bash …...
知识图谱与其它知识库的关系
知识图谱与其它知识库的关系 知识图谱与传统知识库:解构数据连接的哲学知识图谱的商业价值:连接带来的革命选择知识图谱还是传统数据库?一个实用指南 知识图谱的出现,正在改变了我们组织和理解信息的方式。 这种技术不仅仅是一种数…...
STM32基础教程——DMA+ADC多通道
目录 前言 编辑 技术实现 连线图 代码实现 技术要点 实验结果 问题记录 前言 DMA(Direct Memory Access)直接存储器存取,用来提供在外设和存储器 之间或者存储器和存储器之间的高速数据传输。无需CPU干预,数据可以通过DMA快速地移动࿰…...
波束形成(BF)从算法仿真到工程源码实现-第十一节-非线性波束形成算法工程化
一、概述 本节我们对非线性波束形成算法进行工程化,运行在respeaker core v2平台上,算法实时率在0.046左右。更多资料和代码可以进入https://t.zsxq.com/qgmoN ,同时欢迎大家提出宝贵的建议,以共同探讨学习。 二、算法实现 2.1 …...
Windows安装Rust版本GDAL
前言 笔者想安装GDAL,这是一个开源的地理数据库, 笔者到处搜索,最后看到这位大佬写的这篇文章,终于成功了。 aliothor/Windows-Install-Rust-Gdal-Tutorial: Windows Install Rust Version Gdal Stepshttps://github.com/aliot…...
OpenCv高阶(六)——图像的透视变换
目录 一、透视变换的定义与作用 二、透视变换的过程 三、OpenCV 中的透视变换函数 1. cv2.getPerspectiveTransform(src, dst) 2. cv2.warpPerspective(src, H, dsize, dstNone, flagscv2.INTER_LINEAR, borderModecv2.BORDER_CONSTANT, borderValue0) 四、文档扫描校正&a…...
常用正则化技术dropout
在深度学习中,Dropout 是一种常用的正则化技术,用于防止神经网络过拟合。它的核心思想是随机丢弃(临时关闭)网络中的部分神经元,迫使模型不依赖单一神经元,从而提升泛化能力。 1. Dropout…...
66.加1
目录 一、问题描述 二、解题思路 三、代码 四、复杂度分析 一、问题描述 给定一个由 整数 组成的 非空 数组所表示的非负整数,在该数的基础上加一。 最高位数字存放在数组的首位, 数组中每个元素只存储单个数字。 你可以假设除了整数 0 之外&#…...
Tecnomatix Plant Simulation 2302安装教程
Tecnomatix Plant Simulation 2302安装教程,这个比较简单,只有4步即可完成。 第1步:获取并下载安装包 Follow WX account and reply: 2302, get the installation package link. 下载安装包至电脑本地,打开安装包文件如下图所示…...
Flutter 与原生通信
Flutter 与原生之间的通信主要基于通道机制,包括 MethodChannel、EventChannel 和 BasicMessageChannel。 MethodChannel:用于 Flutter 与原生之间的方法调用,实现双向通信,适合一次性的方法调用并获取返回值,如 Flut…...
关于postman的使用(一)
postman创建被测系统结构 改为被测系统名称 添加一级功能 添加接口测试 请求发起前脚本和请求发起后脚本 请求前运行脚本(需要一个随机的岗位名称): 上述脚本功能是自动生成一个岗位名称并且配置它为postman的变量下面是调用 请求后运行脚本…...
【c语言】深入理解指针1
深入理解指针1 一、数组名的理解二、使用指针访问数组三、一维数组传参本质四、二级指针 一、数组名的理解 数组名就是数组首元素的地址,类型是指针类型,但是存在两个例外: sizeof(arr) : 整个数组在内存中的大小 &arr : 整个数组的地址…...
leetcode14.最长公共前缀
暴力逐个比对最长前缀 class Solution {public String longestCommonPrefix(String[] strs) {String prefix strs[0];for (int i 1; i < strs.length; i) {prefix longestCommonPrefix(prefix, strs[i]);}return prefix;}private String longestCommonPrefix(String st…...
云服务器X86计算和Arm计算架构有什么区别?
阿里云服务器架构X86计算和ARM计算有什么区别?x86架构是最常见的,CPU采用Intel或AMD处理器;ARM架构具有低功耗的特性,CPU采用Ampere Altra / AltraMax或阿里自研倚天710处理器。如何选择?阿里云服务器网aliyunfuwuqi.com建议根据实际使用场景选择,X86架构兼容性更广,适合…...
leetcode0079. 单词搜索-medium
1 题目: 单词搜索 官方标定难度:中 给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。 单词必须按照字母顺序,通过相邻的单元格内的字…...
ShellScript脚本编程
语法基础 脚本结构 我们先从这个小demo程序来窥探一下我们shell脚本的程序结构 #!/bin/bash# 注释信息echo_str"hello world"test(){echo $echo_str }test echo_str 首先我们可以通过文本编辑器(在这里我们使用linux自带文本编辑神器vim),新建一个文件…...
【leetcode100】整数拆分
1、题目描述 给定一个正整数 n ,将其拆分为 k 个 正整数 的和( k > 2 ),并使这些整数的乘积最大化。 返回 你可以获得的最大乘积 。 示例 1: 输入: n 2 输出: 1 解释: 2 1 1, 1 1 1。 示例 2: 输入: n 10 输出: 36…...
leetcode:2899. 上一个遍历的整数(python3解法)
难度:简单 给你一个整数数组 nums ,其中 nums[i] 要么是一个正整数,要么是 -1 。我们需要为每个 -1 找到相应的正整数,我们称之为最后访问的整数。 为了达到这个目标,定义两个空数组:seen 和 ans。 从数组 …...
Mysql读写分离(2)-中间件mycat和实践方案
系统环境要求 Mysql版本5.5版本以上jdk1.7Mycat1.6 mycat使用Java开发,因为用到了JDK 7的部分功能,所以在使用前请确保安装了JDK 7.0,并设置了正确的Java环境变量(可在命令行窗口输入:“java –version”获知是否安装…...
QT之在多线程中如何优雅的处理资源泄漏
概述 在多线程编程中,资源泄漏是一个常见且需要特别关注的问题。资源泄漏通常指的是程序未能正确释放分配给它的资源(如内存、文件句柄、数据库连接等),这可能导致系统性能下降甚至崩溃。尤其是在多线程环境中,由于多个线程可能同时访问相同的资源,增加了管理这些资源的…...
SPA 收入支出/技师提成自动统计系统——仙盟共创平台——未来之窗
支出 spa服务 使用开始:https://mp.weixin.qq.com/s/Ok3wuSYAPhd-6N8DrK7jwg 收入清晰呈现:自动整合 SPA 门店各类服务项目收入数据,包括面部护理、身体按摩、特色疗程等。通过对接收银系统,实时记录每笔消费金额,按不…...
Linux的应用领域,测试与Linux,Linux的介绍,VirtualBox和Ubuntu的安装,VMware的安装和打开虚拟机CentOS
目录 Linux的应用领域 测试人员在Linux的工作 测试人员需要掌握Linux的程度 Linux的介绍 Linux的介绍 Linux发行版 Unix和Linux的渊源 虚拟机和Linux的安装 VirtualBox和Ubuntu的安装 安装VirtualBox 安装Ubuntu 下载Ubuntu操作系统的镜像文件 创建虚拟机 虚拟机…...
《Not All Tokens Are What You Need for Pretraining》全文翻译
《Not All Tokens Are What You Need for Pretraining》 不是所有的词元都是预训练所需 摘要 先前的语言模型预训练方法通常对所有训练词元均匀地应用下一词预测损失。对此常规做法提出挑战,我们认为“语料库中的并非所有词元对于语言模型训练同等重要”。我们的…...
vscode终端运行windows服务器的conda出错
远程windows服务器可以运行,本地vscode不能。 打开vscode settings.json文件 添加conda所在路径...
使用基数树优化高并发内存池(替代加锁访问的哈希表和红黑树)
前言: 本篇旨在熟悉 基于tcmalloc的高性能并发内存池项目之后,对于最后的优化进行的笔记梳理,项目完整代码 可点击:项目代码 进行查看。 优化思想风暴: 为了方便根据页号查找到对应的span, 这里我们可以使用红黑树或…...
【bash】.bashrc
查看当前路径文件数量 alias file_num"ls -l | grep ^- | wc -l"查看文件大小 alias file_size"du -sh"alias ll alias ll"ls -ltrh"cd的同时执行ll alias cdcdls; function cdls() {builtin cd "$1" && ll }自定义prompt…...
自我生成,自我训练:大模型用合成数据实现“自我学习”机制实战解析
目录 自我生成,自我训练:大模型用合成数据实现“自我学习”机制实战解析 一、什么是自我学习机制? 二、实现机制:如何用合成数据实现自我训练? ✅ 方式一:Prompt强化生成 → 自我采样再训练 ✅ 方式二…...
Linux的命令格式,运行级别,找回root密码,Linux用户的分类,绝对路径和相对路径,硬链接和软链接,实用按键
目录 Linux的命令格式 运行级别 运行级别说明 切换运行级别 指定默认的运行级别 找回root密码 找回root密码 可能会出现的问题 Linux的用户分类 绝对路径和相对路径 硬链接和软链接 实用按键 Linux的命令格式 Linux的命令格式: command [-options] [par…...
C# JSON
在C#中,你可以使用System.Text.Json或Newtonsoft.Json库来解析JSON字符串。以下是使用这两种库分别解析你提供的JSON字符串的示例。 1. 使用 System.Text.Json System.Text.Json 是 .NET Core 3.0 及以上版本中包含的内置JSON库。以下是如何使用它来解析你的JSON字…...
入门-C编程基础部分:6、常量
飞书文档https://x509p6c8to.feishu.cn/wiki/MnkLwEozRidtw6kyeW9cwClbnAg C 常量 常量是固定值,在程序执行期间不会改变,可以让我们编程更加规范。 常量可以是任何的基本数据类型,比如整数常量、浮点常量、字符常量,或字符串字…...
数字时代的AI与大数据:用高级AI开发技术革新大数据管理
李升伟 编译 在当今数字时代,数据的爆炸式增长令人惊叹 从社交媒体互动到物联网设备的传感器数据,企业正被海量信息淹没。但如何将这种无序的数据洪流转化为有价值的洞察?答案在于人工智能(AI)开发技术的革新&#x…...
数据结构与算法入门 Day 0:程序世界的基石与密码
🌟数据结构与算法入门 Day 0:程序世界的基石与密码🔑 ps:接受到了不少的私信反馈,说应该先把前置的知识内容做一个梳理,所以把昨天的文章删除了,重新开启今天的博文写作 Hey 小伙伴们ÿ…...
20250416在荣品的PRO-RK3566开发板的Android13下编译native C的应用程序的步骤
mm编译的简略步骤以及详细LOG,仅供参考: rootrootrootroot-X99-Turbo:~/hailuo_temp/Android13.0$ source build/envsetup.sh rootrootrootroot-X99-Turbo:~/hailuo_temp/Android13.0$ lunch 57. rk3566_t-userdebug Pick from common choices abo…...