feat: 完善智慧停车综合管理平台源码
This commit is contained in:
@@ -0,0 +1 @@
|
||||
__pycache__
|
||||
@@ -0,0 +1,141 @@
|
||||
name: pytorch
|
||||
channels:
|
||||
- pytorch
|
||||
- nvidia
|
||||
- https://repo.anaconda.com/pkgs/main/
|
||||
- https://repo.anaconda.com/pkgs/free/
|
||||
- https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main/
|
||||
- https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/
|
||||
- defaults
|
||||
dependencies:
|
||||
- _libgcc_mutex=0.1=main
|
||||
- _openmp_mutex=5.1=1_gnu
|
||||
- autopep8=1.6.0=pyhd3eb1b0_1
|
||||
- blas=1.0=mkl
|
||||
- brotlipy=0.7.0=py310h7f8727e_1002
|
||||
- bzip2=1.0.8=h7b6447c_0
|
||||
- ca-certificates=2023.01.10=h06a4308_0
|
||||
- certifi=2022.12.7=py310h06a4308_0
|
||||
- cffi=1.15.1=py310h5eee18b_3
|
||||
- charset-normalizer=2.0.4=pyhd3eb1b0_0
|
||||
- cryptography=39.0.1=py310h9ce1e76_0
|
||||
- cuda-cudart=11.7.99=0
|
||||
- cuda-cupti=11.7.101=0
|
||||
- cuda-libraries=11.7.1=0
|
||||
- cuda-nvrtc=11.7.99=0
|
||||
- cuda-nvtx=11.7.91=0
|
||||
- cuda-runtime=11.7.1=0
|
||||
- ffmpeg=4.3=hf484d3e_0
|
||||
- filelock=3.9.0=py310h06a4308_0
|
||||
- flit-core=3.8.0=py310h06a4308_0
|
||||
- freetype=2.12.1=h4a9f257_0
|
||||
- giflib=5.2.1=h5eee18b_3
|
||||
- gmp=6.2.1=h295c915_3
|
||||
- gmpy2=2.1.2=py310heeb90bb_0
|
||||
- gnutls=3.6.15=he1e5248_0
|
||||
- idna=3.4=py310h06a4308_0
|
||||
- intel-openmp=2021.4.0=h06a4308_3561
|
||||
- jinja2=3.1.2=py310h06a4308_0
|
||||
- jpeg=9e=h5eee18b_1
|
||||
- lame=3.100=h7b6447c_0
|
||||
- lcms2=2.12=h3be6417_0
|
||||
- ld_impl_linux-64=2.38=h1181459_1
|
||||
- lerc=3.0=h295c915_0
|
||||
- libcublas=11.10.3.66=0
|
||||
- libcufft=10.7.2.124=h4fbf590_0
|
||||
- libcufile=1.6.0.25=0
|
||||
- libcurand=10.3.2.56=0
|
||||
- libcusolver=11.4.0.1=0
|
||||
- libcusparse=11.7.4.91=0
|
||||
- libdeflate=1.17=h5eee18b_0
|
||||
- libffi=3.4.2=h6a678d5_6
|
||||
- libgcc-ng=11.2.0=h1234567_1
|
||||
- libgomp=11.2.0=h1234567_1
|
||||
- libiconv=1.16=h7f8727e_2
|
||||
- libidn2=2.3.2=h7f8727e_0
|
||||
- libnpp=11.7.4.75=0
|
||||
- libnvjpeg=11.8.0.2=0
|
||||
- libpng=1.6.39=h5eee18b_0
|
||||
- libstdcxx-ng=11.2.0=h1234567_1
|
||||
- libtasn1=4.19.0=h5eee18b_0
|
||||
- libtiff=4.5.0=h6a678d5_2
|
||||
- libunistring=0.9.10=h27cfd23_0
|
||||
- libuuid=1.41.5=h5eee18b_0
|
||||
- libwebp=1.2.4=h11a3e52_1
|
||||
- libwebp-base=1.2.4=h5eee18b_1
|
||||
- lz4-c=1.9.4=h6a678d5_0
|
||||
- markupsafe=2.1.1=py310h7f8727e_0
|
||||
- mkl=2021.4.0=h06a4308_640
|
||||
- mkl-service=2.4.0=py310h7f8727e_0
|
||||
- mkl_fft=1.3.1=py310hd6ae3a3_0
|
||||
- mkl_random=1.2.2=py310h00e6091_0
|
||||
- mpc=1.1.0=h10f8cd9_1
|
||||
- mpfr=4.0.2=hb69a4c5_1
|
||||
- ncurses=6.4=h6a678d5_0
|
||||
- nettle=3.7.3=hbbd107a_1
|
||||
- networkx=2.8.4=py310h06a4308_1
|
||||
- numpy=1.23.5=py310hd5efca6_0
|
||||
- numpy-base=1.23.5=py310h8e6c178_0
|
||||
- openh264=2.1.1=h4ff587b_0
|
||||
- openssl=1.1.1t=h7f8727e_0
|
||||
- pillow=9.4.0=py310h6a678d5_0
|
||||
- pip=23.0.1=py310h06a4308_0
|
||||
- pycodestyle=2.10.0=py310h06a4308_0
|
||||
- pycparser=2.21=pyhd3eb1b0_0
|
||||
- pyopenssl=23.0.0=py310h06a4308_0
|
||||
- pysocks=1.7.1=py310h06a4308_0
|
||||
- python=3.10.10=h7a1cb2a_2
|
||||
- pytorch=2.0.0=py3.10_cuda11.7_cudnn8.5.0_0
|
||||
- pytorch-cuda=11.7=h778d358_3
|
||||
- pytorch-mutex=1.0=cuda
|
||||
- readline=8.2=h5eee18b_0
|
||||
- requests=2.28.1=py310h06a4308_1
|
||||
- setuptools=65.6.3=py310h06a4308_0
|
||||
- six=1.16.0=pyhd3eb1b0_1
|
||||
- sqlite=3.41.1=h5eee18b_0
|
||||
- sympy=1.11.1=py310h06a4308_0
|
||||
- tk=8.6.12=h1ccaba5_0
|
||||
- toml=0.10.2=pyhd3eb1b0_0
|
||||
- torchaudio=2.0.0=py310_cu117
|
||||
- torchtriton=2.0.0=py310
|
||||
- torchvision=0.15.0=py310_cu117
|
||||
- typing_extensions=4.4.0=py310h06a4308_0
|
||||
- urllib3=1.26.15=py310h06a4308_0
|
||||
- wheel=0.38.4=py310h06a4308_0
|
||||
- xz=5.2.10=h5eee18b_1
|
||||
- zlib=1.2.13=h5eee18b_0
|
||||
- zstd=1.5.4=hc292b87_0
|
||||
- pip:
|
||||
- aiohttp==3.8.4
|
||||
- aiosignal==1.3.1
|
||||
- async-timeout==4.0.2
|
||||
- attrs==22.2.0
|
||||
- bidict==0.22.1
|
||||
- contourpy==1.0.7
|
||||
- cycler==0.11.0
|
||||
- fonttools==4.39.3
|
||||
- frozenlist==1.3.3
|
||||
- kiwisolver==1.4.4
|
||||
- matplotlib==3.7.1
|
||||
- mpmath==1.2.1
|
||||
- multidict==6.0.4
|
||||
- opencv-python==4.7.0.72
|
||||
- packaging==23.0
|
||||
- pandas==2.0.0
|
||||
- psutil==5.9.4
|
||||
- pyparsing==3.0.9
|
||||
- python-dateutil==2.8.2
|
||||
- python-engineio==4.4.0
|
||||
- python-socketio==5.8.0
|
||||
- pytz==2023.3
|
||||
- pyyaml==6.0
|
||||
- scipy==1.10.1
|
||||
- seaborn==0.12.2
|
||||
- sentry-sdk==1.19.1
|
||||
- thop==0.1.1-2209072238
|
||||
- tqdm==4.65.0
|
||||
- tzdata==2023.3
|
||||
- ultralytics==8.0.72
|
||||
- websocket-client==1.5.1
|
||||
- yarl==1.8.2
|
||||
prefix: /root/anaconda3/envs/pytorch
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
from model import YOLOModel
|
||||
from wsClient import WSClient
|
||||
import asyncio
|
||||
import multiprocessing
|
||||
import os
|
||||
import requests
|
||||
import base64
|
||||
import hashlib
|
||||
import hmac
|
||||
|
||||
|
||||
def getOfflineCameraIDs(httpServerUrl, adminUsername, password):
|
||||
__SHA256KEY = b"smart-parking-platform"
|
||||
r = requests.get(
|
||||
httpServerUrl + "/api/ai/getOfflineCameraList",
|
||||
params={
|
||||
"adminUsername": adminUsername,
|
||||
"password": base64.b64encode(
|
||||
hmac.new(__SHA256KEY, password.encode("utf-8"), hashlib.sha256).digest()
|
||||
).decode("utf-8"),
|
||||
},
|
||||
)
|
||||
res = r.json()
|
||||
if not res["success"]:
|
||||
raise Exception(res["message"])
|
||||
|
||||
return list(map(lambda x: x["cameraID"], res["data"]))
|
||||
|
||||
|
||||
# Windows推流本机摄像头到rtmp://localhost:1515/hls/3
|
||||
# ffmpeg -list_devices true -f dshow -i dummy
|
||||
# ffmpeg -f dshow -i video="USB2.0 HD UVC WebCam" -vcodec libx264 -preset:v ultrafast -tune:v zerolatency -g 30 -f:v flv -c:a copy rtmp://192.169.3.3:1515/live/3
|
||||
|
||||
|
||||
def ffmpegStreamToRtmpServer(streamUrl: str, rtmpUrl: str):
|
||||
print("exec ffmpeg...")
|
||||
if streamUrl.startswith("rtsp"):
|
||||
os.execvp(
|
||||
"/usr/bin/ffmpeg",
|
||||
f"/usr/bin/ffmpeg -hide_banner -loglevel error -rtsp_transport tcp -i {streamUrl} -c:v libx264 -preset:v ultrafast -tune:v zerolatency -g 30 -f:v flv -c:a copy {rtmpUrl}".split(
|
||||
" "
|
||||
),
|
||||
)
|
||||
else:
|
||||
os.execvp(
|
||||
"/usr/bin/ffmpeg",
|
||||
f"/usr/bin/ffmpeg -hide_banner -loglevel error -i {streamUrl} -c:v libx264 -preset:v ultrafast -tune:v zerolatency -g 30 -f:v flv -c:a copy {rtmpUrl}".split(
|
||||
" "
|
||||
),
|
||||
)
|
||||
pass
|
||||
|
||||
|
||||
async def beginWork(ws: WSClient):
|
||||
rtmpUrl = ws.rtmpServerUrl + "/" + ws.cameraID
|
||||
ffmpegProcess = multiprocessing.Process(
|
||||
target=ffmpegStreamToRtmpServer, args=(ws.rtspUrl, rtmpUrl), daemon=True
|
||||
)
|
||||
try:
|
||||
model = YOLOModel(device=ws.context.get("modelDevice", "cpu"))
|
||||
|
||||
ffmpegProcess.start()
|
||||
|
||||
print(
|
||||
f"Begin to detect video for camera {ws.cameraID}, streamUrl: {ws.rtspUrl} \nIf wait too long, please check if the stream url is correct."
|
||||
)
|
||||
results = model.detectVideo(ws.rtspUrl, classList=[0, 2]) # 0:person, 2:car
|
||||
|
||||
for frameResult in results:
|
||||
clsCount = model.getResultClsCount(frameResult)
|
||||
|
||||
await ws.trySendAlarm(
|
||||
{
|
||||
"algorithmType": "body",
|
||||
"count": clsCount.get("person", 0),
|
||||
"predictResult": frameResult,
|
||||
}
|
||||
)
|
||||
await ws.trySendAlarm(
|
||||
{
|
||||
"algorithmType": "vehicle",
|
||||
"count": clsCount.get("car", 0),
|
||||
"predictResult": frameResult,
|
||||
}
|
||||
)
|
||||
|
||||
if not ffmpegProcess.is_alive():
|
||||
print(f"ffmpeg process for camera {ws.cameraID} is dead")
|
||||
break
|
||||
await asyncio.sleep(float(ws.context.get("interval", 1)))
|
||||
|
||||
pass
|
||||
finally:
|
||||
ffmpegProcess.kill()
|
||||
await ws.disconnect()
|
||||
print("error, kill ffmpeg process")
|
||||
|
||||
pass
|
||||
|
||||
|
||||
def main(
|
||||
wsServerUrl="",
|
||||
rtmpServerUrl="",
|
||||
adminUsername="",
|
||||
password="",
|
||||
cameraID="",
|
||||
detectionInterval=0.5,
|
||||
modelDevice="cpu",
|
||||
):
|
||||
asyncio.run(
|
||||
WSClient(
|
||||
wsServerUrl,
|
||||
rtmpServerUrl,
|
||||
adminUsername,
|
||||
password,
|
||||
str(cameraID),
|
||||
beginWork,
|
||||
{"interval": float(detectionInterval), "modelDevice": modelDevice},
|
||||
).stayConnected()
|
||||
)
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
multiprocessing.freeze_support()
|
||||
httpServerUrl = os.getenv("HTTP_SERVER_URL", "http://localhost")
|
||||
wsServerUrl = httpServerUrl + "/ws"
|
||||
rtmpServerUrl = os.getenv("RTMP_SERVER_URL", "rtmp://localhost:1515/live")
|
||||
adminUsername = os.getenv("ADMIN_USERNAME", "admin123")
|
||||
password = os.getenv("ADMIN_PASSWORD", "admin123")
|
||||
cameraIDs = []
|
||||
if os.getenv("CAMERA_IDS") is not None:
|
||||
cameraIDs = os.getenv("CAMERA_IDS").split(",")
|
||||
else:
|
||||
cameraIDs = getOfflineCameraIDs(httpServerUrl, adminUsername, password)
|
||||
|
||||
detectionInterval = float(os.getenv("DETECTION_INTERVAL", 0.5))
|
||||
modelDevice = os.getenv("MODEL_DEVICE", "cpu")
|
||||
|
||||
processes = []
|
||||
# start a process for each camera
|
||||
for cameraID in cameraIDs:
|
||||
p = multiprocessing.Process(
|
||||
target=main,
|
||||
args=(
|
||||
wsServerUrl,
|
||||
rtmpServerUrl,
|
||||
adminUsername,
|
||||
password,
|
||||
cameraID,
|
||||
detectionInterval,
|
||||
modelDevice,
|
||||
),
|
||||
)
|
||||
p.start()
|
||||
processes.append(p)
|
||||
pass
|
||||
|
||||
# wait for all processes to finish
|
||||
for p in processes:
|
||||
p.join()
|
||||
|
||||
pass
|
||||
@@ -0,0 +1,51 @@
|
||||
from ultralytics import YOLO
|
||||
|
||||
|
||||
class YOLOModel:
|
||||
__model = None
|
||||
conf = 0.5
|
||||
device = "cpu"
|
||||
|
||||
def __init__(self, conf=0.5, device="cpu"):
|
||||
self.conf = conf
|
||||
self.device = device
|
||||
self.__model = YOLO(model="./yolov8n.pt", task="detect")
|
||||
|
||||
def detectImage(self, image, classList=[0, 2]):
|
||||
result = self.__model.predict(
|
||||
source=image,
|
||||
verbose=False,
|
||||
conf=self.conf,
|
||||
line_thickness=1,
|
||||
classes=classList,
|
||||
device=self.device,
|
||||
)[0]
|
||||
result.clsCount = self.getResultClsCount(result)
|
||||
return result
|
||||
|
||||
def detectVideo(self, video, classList=[0, 2]):
|
||||
result = self.__model.predict(
|
||||
source=video,
|
||||
verbose=False,
|
||||
stream=True,
|
||||
line_thickness=1,
|
||||
conf=self.conf,
|
||||
classes=classList,
|
||||
device=self.device,
|
||||
)
|
||||
return result
|
||||
|
||||
def getResultClsCount(self, result):
|
||||
clsCount = dict()
|
||||
detectedCls = result.boxes.cls
|
||||
for clsValue in detectedCls.unique():
|
||||
clsName = str(result.names[int(clsValue)])
|
||||
count = int((detectedCls == clsValue).sum())
|
||||
clsCount[clsName] = count
|
||||
return clsCount
|
||||
|
||||
def getSupportedClsList(self):
|
||||
return self.__model.names
|
||||
|
||||
|
||||
pass
|
||||
@@ -0,0 +1,213 @@
|
||||
from typing import Any
|
||||
import socketio
|
||||
import hashlib
|
||||
import hmac
|
||||
import base64
|
||||
import json
|
||||
import cv2
|
||||
import asyncio
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class WSClient:
|
||||
__SHA256KEY = b"smart-parking-platform"
|
||||
sio = socketio.AsyncClient(reconnection=False)
|
||||
wsServerUrl = None
|
||||
rtmpServerUrl = None
|
||||
|
||||
connected = False
|
||||
ready = False
|
||||
|
||||
async def onReady(self):
|
||||
pass
|
||||
|
||||
username = None
|
||||
password = None
|
||||
cameraID = None
|
||||
|
||||
rtspUrl = None
|
||||
alarmRules = None
|
||||
"""
|
||||
@type alarmRule: [{
|
||||
'id': 1,
|
||||
'name': '测试规则',
|
||||
'enabled': True,
|
||||
'algorithmType':'body',
|
||||
'triggerDayOfWeek': [1, 2, 3, 4, 5],
|
||||
'triggerTimeStart': '09:00:00',
|
||||
'triggerTimeEnd': '18:00:00',
|
||||
'triggerCountMin': 0,
|
||||
'triggerCountMax': -1
|
||||
}]
|
||||
"""
|
||||
|
||||
alarmThrottle = {}
|
||||
"""
|
||||
@type alarmThrottle: {
|
||||
[ alarmRuleID:str ]: datetime
|
||||
}
|
||||
"""
|
||||
|
||||
throttleTime = 60 # seconds
|
||||
|
||||
context = {}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
wsServerUrl: str,
|
||||
rtmpServerUrl: str,
|
||||
username: str,
|
||||
password: str,
|
||||
cameraID: str,
|
||||
onReady: Any,
|
||||
context: dict = {},
|
||||
) -> None:
|
||||
self.wsServerUrl = wsServerUrl
|
||||
self.rtmpServerUrl = rtmpServerUrl
|
||||
self.username = username
|
||||
self.password = base64.b64encode(
|
||||
hmac.new(
|
||||
self.__SHA256KEY, password.encode("utf-8"), hashlib.sha256
|
||||
).digest()
|
||||
).decode("utf-8")
|
||||
self.cameraID = cameraID
|
||||
self.onReady = onReady
|
||||
self.context = context
|
||||
|
||||
self.sio.on("cameraConfigChange", self.onCameraConfigChange)
|
||||
|
||||
self.sio.on("connect", self.onConnect)
|
||||
self.sio.on("disconnect", self.onDisconnect)
|
||||
self.sio.on("connect_error", self.onConnectError)
|
||||
|
||||
print(f"ws client for camera {cameraID} created")
|
||||
pass
|
||||
|
||||
async def connect(self):
|
||||
if self.connected:
|
||||
return
|
||||
data = json.dumps(
|
||||
{
|
||||
"username": self.username,
|
||||
"password": self.password,
|
||||
"cameraID": self.cameraID,
|
||||
}
|
||||
)
|
||||
await self.sio.connect(
|
||||
self.wsServerUrl, socketio_path="/ws/ai/", headers={"data": data}
|
||||
)
|
||||
self.connected = True
|
||||
pass
|
||||
|
||||
async def sendMessage(self, msg):
|
||||
await self.sio.emit("message", msg)
|
||||
print("message sent")
|
||||
pass
|
||||
|
||||
def matchAlarmRule(self, data):
|
||||
"""
|
||||
@type data: {
|
||||
'algorithmType': 'body',
|
||||
'count': 1,
|
||||
'predictResult': Any
|
||||
}
|
||||
"""
|
||||
matchedRules = []
|
||||
if not self.ready:
|
||||
raise Exception(f"CameraID {self.cameraID} not ready or disconnected")
|
||||
if self.alarmRules is None:
|
||||
return matchedRules
|
||||
for rule in self.alarmRules:
|
||||
if (
|
||||
rule["enabled"]
|
||||
and rule["algorithmType"] == data["algorithmType"]
|
||||
and (datetime.now().weekday() + 1) in rule["triggerDayOfWeek"]
|
||||
and datetime.now().time()
|
||||
>= datetime.strptime(rule["triggerTimeStart"], "%H:%M:%S").time()
|
||||
and datetime.now().time()
|
||||
<= datetime.strptime(rule["triggerTimeEnd"], "%H:%M:%S").time()
|
||||
and data["count"] >= rule["triggerCountMin"]
|
||||
and (
|
||||
data["count"] <= rule["triggerCountMax"]
|
||||
or rule["triggerCountMax"] == -1
|
||||
)
|
||||
and (
|
||||
self.alarmThrottle.get(rule["id"]) is None
|
||||
or (datetime.now() - self.alarmThrottle[rule["id"]]).seconds
|
||||
> self.throttleTime
|
||||
)
|
||||
):
|
||||
matchedRules.append(rule)
|
||||
return matchedRules
|
||||
|
||||
async def trySendAlarm(self, data):
|
||||
"""
|
||||
@type data: {
|
||||
'algorithmType': 'body',
|
||||
'count': 1,
|
||||
'predictResult': Any
|
||||
}
|
||||
"""
|
||||
rules = self.matchAlarmRule(data)
|
||||
for rule in rules:
|
||||
"""
|
||||
AlarmData: {
|
||||
picBase64: string;
|
||||
alarmRuleID: number;
|
||||
}
|
||||
"""
|
||||
await self.sio.emit(
|
||||
"alarm",
|
||||
{
|
||||
"alarmRuleID": rule["id"],
|
||||
"picBase64": "data:image/jpg;base64,"
|
||||
+ base64.b64encode(
|
||||
cv2.imencode(".jpg", data["predictResult"].plot())[1]
|
||||
).decode("utf-8"),
|
||||
},
|
||||
)
|
||||
# 防止短时间内重复发送
|
||||
self.alarmThrottle[rule["id"]] = datetime.now()
|
||||
print(f"camera {self.cameraID} alarm sent: {rule['name']}")
|
||||
pass
|
||||
|
||||
pass
|
||||
|
||||
async def disconnect(self):
|
||||
await self.sio.disconnect()
|
||||
pass
|
||||
|
||||
async def onCameraConfigChange(self, data):
|
||||
if self.rtspUrl is not None and self.rtspUrl != data["rtspUrl"]:
|
||||
print(f"Camera {self.cameraID} rtsp url changed, disconnecting...")
|
||||
await self.disconnect()
|
||||
return
|
||||
|
||||
self.rtspUrl = data["rtspUrl"]
|
||||
self.alarmRules = data["alarmRules"]
|
||||
print(f"Camera {self.cameraID} config updated")
|
||||
|
||||
if self.connected and not self.ready:
|
||||
self.ready = True
|
||||
asyncio.get_event_loop().create_task(self.onReady(self))
|
||||
|
||||
pass
|
||||
|
||||
async def stayConnected(self):
|
||||
await self.connect()
|
||||
await self.sio.wait()
|
||||
pass
|
||||
|
||||
def onConnect(self):
|
||||
print(f"connection for camera {self.cameraID} established")
|
||||
pass
|
||||
|
||||
def onDisconnect(self):
|
||||
print(f"camera {self.cameraID} disconnected from server")
|
||||
self.connected = False
|
||||
self.ready = False
|
||||
pass
|
||||
|
||||
def onConnectError(self, err):
|
||||
print(err)
|
||||
pass
|
||||
Binary file not shown.
Reference in New Issue
Block a user