本文的背景在于通过yolov5模型和deepsort模型,对监控中抽取视频帧,从而得出人物识别预测模型,再通过配置文件将待检测的多边形热区传入后,检测各热区中的人数。
*而本文将讲述如何判断目标中心点是否在任意多边形区域内。*
from AIDetector_pytorch import Detector
import imutils
import numpy as np
import cv2
import configparser
def onSegment(p: tuple, q: tuple, r: tuple) -> bool:
'''
检查待判定点是否落在线段上
:param p:'tuple':线段起始点
:param q:'tuple':待判定点
:param r:'tuple':线段终止点
:return:'bool':若待判定点落在线段上返回True,否则返回False
'''
if ((q[0] <= max(p[0], r[0])) &
(q[0] >= min(p[0], r[0])) &
(q[1] <= max(p[1], r[1])) &
(q[1] >= min(p[1], r[1]))):
return True
return False
def orientation(p: tuple, q: tuple, r: tuple) -> int:
'''
对输入点坐标进行方向判定
:params:'tuple':三个点坐标p,q,r
:return:'int':
返回值为0:p、q、r三点共线
返回值为1:顺时针方向排列
返回值为2:逆时针方向排列
'''
val = (((q[1] - p[1]) *
(r[0] - q[0])) -
((q[0] - p[0]) *
(r[1] - q[1])))
if val == 0:
return 0
if val > 0:
return 1 # Collinear
else:
return 2 # Clock or counterclock
def doIntersect(p1, q1, p2, q2):
'''
检测p1为起始点q1为终止点组成的线段与p2为起始点q2为终止点组成的线段是否相交
:param p1:'tuple':第一个多边形端点
:param q1:'tuple':第二个多边形端点
:param p2:'tuple':目标检测对象中心点
:param q2:'tuple':与p2在同一条水平线上,往右侧延申的无穷点(横坐标设置为10000)
:return:'bool':若两线段相交返回True,否则返回False
'''
# Find the four orientations needed for
# general and special cases
o1 = orientation(p1, q1, p2)
o2 = orientation(p1, q1, q2)
o3 = orientation(p2, q2, p1)
o4 = orientation(p2, q2, q1)
# General case
if (o1 != o2) and (o3 != o4):
return True
# Special Cases
# p1, q1 and p2 are colinear and
# p2 lies on segment p1q1
if (o1 == 0) and (onSegment(p1, p2, q1)):
return True
# p1, q1 and p2 are colinear and
# q2 lies on segment p1q1
if (o2 == 0) and (onSegment(p1, q2, q1)):
return True
# p2, q2 and p1 are colinear and
# p1 lies on segment p2q2
if (o3 == 0) and (onSegment(p2, p1, q2)):
return True
# p2, q2 and q1 are colinear and
# q1 lies on segment p2q2
if (o4 == 0) and (onSegment(p2, q1, q2)):
return True
return False
def is_inside_polygon(points, p: tuple) -> bool:
'''
在给定多边形热区内,判断中心点是否在不规则多边形区域内部。若输入点数小于3,
则返回False。从目标中心点开始,往右侧画一条水平线,并将其延长到无穷大。计
算该线与多边形边缘相交的交点的个数。若交点的个数为奇数或者点位于多边形的边
上,则点在多边形内。如果没有一个条件为真,那么点在多边形外。
:param points:'list':多边形热区各个坐标点(按连接顺序依次输入的坐标)
:param p:'tuple':目标检测对象中心点坐标
:return:'bool':若p点落在多边形热区内则返回True
'''
INT_MAX = 10000
points = np.ravel(points)
points = [(points[i - 1], points[i]) for i in range(len(points)) if i % 2 != 0]
n = len(points)
# There must be at least 3 vertices
# in polygon
if n < 3:
return False
# Create a point for line segment
# from p to infinite
extreme = (INT_MAX, p[1])
count = i = 0
while True:
next = (i + 1) % n
# Check if the line segment from 'p' to
# 'extreme' intersects with the line
# segment from 'polygon[i]' to 'polygon[next]'
if (doIntersect(points[i],
points[next],
p, extreme)):
# If the point 'p' is colinear with line
# segment 'i-next', then check if it lies
# on segment. If it lies, return true, otherwise false
if orientation(points[i], p,
points[next]) == 0:
return onSegment(points[i], p,
points[next])
count += 1
i = next
if (i == 0):
break
# Return true if count is odd, false otherwise
return (count % 2 == 1)
这段代码是关于多边形判定,其核心思想在于==“穿针引线”==,从目标点往右侧引一条无限长的线,若右侧有多边形,无关多边形凹凸性,线总是会有进入-出去的流程,即若与多边形相交次数为奇数,则该点在多边形内。若为偶数,则不在多边形内。同时,还要考虑共线等情况。详细描述请看代码接口。
def plot_bboxes(image, bboxes, line_thickness=None):
# Plots one bounding box on image img
tl = line_thickness or round(
0.002 * (image.shape[0] + image.shape[1]) / 2) + 1 # line/font thickness
for (x1, y1, x2, y2, cls_id, pos_id) in bboxes:
if cls_id in ['person']:
color = (0, 0, 255)
else:
color = (0, 255, 0)
c1, c2 = (x1, y1), (x2, y2)
cv2.rectangle(image, c1, c2, color, thickness=tl, lineType=cv2.LINE_AA)
tf = max(tl - 1, 1) # font thickness
t_size = cv2.getTextSize(cls_id, 0, fontScale=tl / 3, thickness=tf)[0]
c2 = c1[0] + t_size[0], c1[1] - t_size[1] - 3
cv2.rectangle(image, c1, c2, color, -1, cv2.LINE_AA) # filled
cv2.putText(image, '{} ID-{}'.format(cls_id, pos_id), (c1[0], c1[1] - 2), 0, tl / 3,
[225, 255, 255], thickness=tf, lineType=cv2.LINE_AA)
return image
def get_num_single(area,result):
'''
计算检测对象中心点,判断目标对象中心点是否在热区内,并对预测结果标签进行判断,
若在热区内且标签为'person',则num加一。
:param area:'numpy.ndarray':单个热区坐标信息
:param result:'dict': 预测结果坐标
:return:'int':检测人数num
'''
num=0
#result = det.feedCap(frame)
for i in result['bboxes']:
box = i[:4]#读取前四位坐标信息
center = (
int((box[0] + box[2]) / 2), int((box[1] + box[3]) / 2),
)
#cv2.circle(frame, center, point_size=1, point_color=(0, 0, 255), thickness=4)
if i[4] == 'person':
if is_inside_polygon(area, center):
num = num+1
return num
def get_eachnum(frame,config):
'''
检测各个热区内人数
:param frame:'numpy.ndarray': 视频帧
:param config:'list': 热区集合
:return:list: 热区编号以及各热区检测人数
'''
eachnum=[]
result = det.feedCap(frame)
for area in config:
eachnum.append(area[0])
pts = np.array(eval(area[1])).reshape((-1, 1, 2))
eachnum.append(get_num_single(pts,result))
return eachnum
if __name__ == '__main__':
det = Detector()
video_capture = cv2.VideoCapture(0)
config = configparser.ConfigParser()
config.read('config.config')
area = config.items('area')
while video_capture.isOpened():
ret, frame = video_capture.read()
if not ret:
break
flag = cv2.waitKey(1)
print(get_eachnum(frame, area))
# 结果呈现
# # result 从feedcap中获取
# # frame = plot_bboxes(frame, result['bboxes'])
# # 绘制未填充的多边形
# # for area in area:
# # pts = np.array(eval(area[1])).reshape((-1, 1, 2))
# # cv2.polylines(frame, [pts], isClosed=True, color=(255, 255, 0), thickness=1)
# # cv2.imshow('demo', frame)
# # if flag == 27:
# # break
# # video_capture.release()
# # cv2.destroyAllWindows()
这段是实现过程,当然,大家是跑不了的哈,因为你们没有yolov5和deepsort嘿嘿嘿