detect_plate_20260130.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723
  1. import argparse
  2. import copy
  3. import os
  4. import re
  5. import time
  6. from collections import deque
  7. from datetime import datetime
  8. from pprint import pprint
  9. import cv2
  10. import numpy as np
  11. import torch
  12. import redis
  13. import sys
  14. from models.experimental import attempt_load
  15. from plate_recognition.double_plate_split_merge import get_split_merge
  16. from plate_recognition.plate_rec import (
  17. allFilePath,
  18. cv_imread,
  19. get_plate_result,
  20. init_model,
  21. )
  22. from utils.datasets import letterbox
  23. from utils.general import check_img_size, non_max_suppression_face, scale_coords
  24. # Redis连接配置
  25. REDIS_HOST = 'localhost'
  26. REDIS_PORT = 6379
  27. REDIS_DB = 0
  28. REDIS_PASSWORD = None
  29. REDIS_KEY = 'plate_results'
  30. WINDOW_SIZE = 5
  31. # 新增:重连相关配置
  32. MAX_RECONNECT_ATTEMPTS = 10
  33. RECONNECT_DELAY = 5
  34. MAX_CONSECUTIVE_FAILURES = 5
  35. # 调整阈值设置 - 提高以减少误报
  36. DETECT_THRESH = 0.65
  37. COLOR_THRESH = 0.85
  38. REC_THRESH = 0.9
  39. # 初始化Redis连接
  40. try:
  41. redis_client = redis.Redis(
  42. host=REDIS_HOST,
  43. port=REDIS_PORT,
  44. db=REDIS_DB,
  45. password=REDIS_PASSWORD,
  46. decode_responses=True
  47. )
  48. redis_client.ping()
  49. print("Redis连接成功")
  50. except Exception as e:
  51. print(f"Redis连接失败: {e}")
  52. redis_client = None
  53. def get_current_time():
  54. """获取当前时间字符串"""
  55. return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
  56. def get_current_timestamp():
  57. """获取当前时间戳(秒级)"""
  58. return int(time.time())
  59. def connect_stream(stream_url, cap_options=""):
  60. """建立视频流连接,带重试机制"""
  61. attempt = 0
  62. while attempt < MAX_RECONNECT_ATTEMPTS:
  63. try:
  64. print(f"[{get_current_time()}] 尝试连接视频流: {stream_url} (第{attempt + 1}次)")
  65. # 设置FFMPEG选项
  66. if cap_options:
  67. cap = cv2.VideoCapture()
  68. os.environ["OPENCV_FFMPEG_CAPTURE_OPTIONS"] = cap_options
  69. success = cap.open(stream_url, cv2.CAP_FFMPEG)
  70. else:
  71. cap = cv2.VideoCapture(stream_url)
  72. success = cap.isOpened()
  73. if success:
  74. # 设置缓冲区优化参数
  75. cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) # 减少缓冲区大小
  76. cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc('H', '2', '6', '4')) # H264
  77. # 测试读取一帧
  78. ret, frame = cap.read()
  79. if ret:
  80. print(f"[{get_current_time()}] 视频流连接成功")
  81. return cap, True
  82. else:
  83. print(f"[{get_current_time()}] 视频流打开但无法读取帧")
  84. cap.release()
  85. else:
  86. print(f"[{get_current_time()}] 无法打开视频流")
  87. print(f"[{get_current_time()}] 连接失败,{RECONNECT_DELAY}秒后重试...")
  88. time.sleep(RECONNECT_DELAY)
  89. attempt += 1
  90. except Exception as e:
  91. print(f"[{get_current_time()}] 连接异常: {str(e)}")
  92. time.sleep(RECONNECT_DELAY)
  93. attempt += 1
  94. print(f"[{get_current_time()}] 达到最大重连次数({MAX_RECONNECT_ATTEMPTS}),退出")
  95. return None, False
  96. def reconnect_stream(stream_url, cap_options=""):
  97. """重新连接视频流"""
  98. print(f"[{get_current_time()}] 开始重新连接视频流...")
  99. if 'cap' in globals() and cap:
  100. cap.release()
  101. time.sleep(2) # 等待2秒再重连
  102. return connect_stream(stream_url, cap_options)
  103. def clean_expired_data():
  104. """清理超过5秒的旧数据"""
  105. if redis_client is None:
  106. return 0
  107. try:
  108. current_ts = get_current_timestamp()
  109. cutoff_ts = current_ts - WINDOW_SIZE
  110. pipe = redis_client.pipeline()
  111. zset_key = f"{REDIS_KEY}:sorted"
  112. expired_timestamps = redis_client.zrangebyscore(zset_key, 0, cutoff_ts)
  113. if expired_timestamps:
  114. hash_key = f"{REDIS_KEY}:data"
  115. pipe.hdel(hash_key, *expired_timestamps)
  116. pipe.zremrangebyscore(zset_key, 0, cutoff_ts)
  117. pipe.execute()
  118. deleted_count = len(expired_timestamps)
  119. if deleted_count > 0:
  120. print(f"[{get_current_time()}] 清理过期数据: {deleted_count}条 (>5秒)")
  121. return deleted_count
  122. else:
  123. return 0
  124. except Exception as e:
  125. print(f"清理过期数据失败: {e}")
  126. return 0
  127. def save_to_redis(plate_no, plate_color, detect_conf, color_conf, rec_avg):
  128. """将车牌识别结果保存到Redis - 5秒滑动窗口"""
  129. if redis_client is None:
  130. return False, "Redis未连接"
  131. try:
  132. clean_expired_data()
  133. timestamp = get_current_timestamp()
  134. timestamp_ms = int(time.time() * 1000)
  135. entry_data = {
  136. "plate_no": plate_no,
  137. "plate_color": plate_color,
  138. "detect_conf": str(detect_conf),
  139. "color_conf": str(color_conf),
  140. "rec_avg": str(rec_avg),
  141. "timestamp": str(timestamp),
  142. "timestamp_ms": str(timestamp_ms),
  143. "datetime": get_current_time(),
  144. "source": "rtsp_stream"
  145. }
  146. pipe = redis_client.pipeline()
  147. hash_key = f"{REDIS_KEY}:data"
  148. pipe.hset(hash_key, timestamp, str(entry_data))
  149. zset_key = f"{REDIS_KEY}:sorted"
  150. pipe.zadd(zset_key, {timestamp: timestamp})
  151. pipe.execute()
  152. return True, f"保存到Redis成功: {timestamp}"
  153. except Exception as e:
  154. return False, f"保存到Redis失败: {e}"
  155. def get_recent_plates_from_redis():
  156. """从Redis获取最近5秒内的所有车牌记录"""
  157. if redis_client is None:
  158. return []
  159. try:
  160. clean_expired_data()
  161. zset_key = f"{REDIS_KEY}:sorted"
  162. hash_key = f"{REDIS_KEY}:data"
  163. current_ts = get_current_timestamp()
  164. cutoff_ts = current_ts - WINDOW_SIZE
  165. recent_timestamps = redis_client.zrangebyscore(zset_key, cutoff_ts, current_ts)
  166. recent_timestamps = sorted(recent_timestamps, key=int, reverse=True)
  167. results = []
  168. for ts in recent_timestamps:
  169. entry_str = redis_client.hget(hash_key, ts)
  170. if entry_str:
  171. try:
  172. data = eval(entry_str)
  173. results.append({
  174. 'timestamp': int(ts),
  175. 'data': data
  176. })
  177. except:
  178. continue
  179. return results
  180. except Exception as e:
  181. print(f"从Redis读取数据失败: {e}")
  182. return []
  183. def get_window_info():
  184. """获取滑动窗口的统计信息"""
  185. if redis_client is None:
  186. return {"count": 0, "window_size": WINDOW_SIZE}
  187. try:
  188. clean_expired_data()
  189. hash_key = f"{REDIS_KEY}:data"
  190. zset_key = f"{REDIS_KEY}:sorted"
  191. data_count = redis_client.hlen(hash_key)
  192. sorted_count = redis_client.zcard(zset_key)
  193. if sorted_count > 0:
  194. timestamps_with_scores = redis_client.zrange(zset_key, 0, -1, withscores=True)
  195. if timestamps_with_scores:
  196. timestamps = []
  197. for member, score in timestamps_with_scores:
  198. try:
  199. timestamps.append(int(float(score)))
  200. except:
  201. continue
  202. if timestamps:
  203. oldest_ts = min(timestamps)
  204. newest_ts = max(timestamps)
  205. time_range = newest_ts - oldest_ts
  206. else:
  207. time_range = 0
  208. oldest_ts = newest_ts = int(time.time())
  209. else:
  210. time_range = 0
  211. oldest_ts = newest_ts = int(time.time())
  212. else:
  213. time_range = 0
  214. oldest_ts = newest_ts = int(time.time())
  215. return {
  216. "count": data_count,
  217. "window_size": WINDOW_SIZE,
  218. "time_range": time_range,
  219. "oldest_record": datetime.fromtimestamp(oldest_ts).strftime("%H:%M:%S") if sorted_count > 0 else "无",
  220. "newest_record": datetime.fromtimestamp(newest_ts).strftime("%H:%M:%S") if sorted_count > 0 else "无"
  221. }
  222. except Exception as e:
  223. print(f"获取窗口信息失败: {e}")
  224. return {"count": 0, "window_size": WINDOW_SIZE}
  225. def should_output(plate_no, last_output_dict, current_time):
  226. """优化的去重逻辑:支持模糊匹配和更长的冷却时间"""
  227. clean_plate = plate_no.strip().replace(' ', '').upper()
  228. if len(clean_plate) < 5:
  229. return False, "车牌太短"
  230. similar_found = None
  231. for existing_plate in list(last_output_dict.keys()):
  232. if (clean_plate == existing_plate or
  233. (len(clean_plate) >= 5 and len(existing_plate) >= 5 and
  234. clean_plate[:5] == existing_plate[:5])):
  235. similar_found = existing_plate
  236. break
  237. if similar_found is None:
  238. last_output_dict[clean_plate] = current_time
  239. return True, "新车牌"
  240. else:
  241. last_time = last_output_dict[similar_found]
  242. time_diff = current_time - last_time
  243. if time_diff >= 3.0:
  244. del last_output_dict[similar_found]
  245. last_output_dict[clean_plate] = current_time
  246. return True, "更新识别"
  247. else:
  248. return False, "冷却期内"
  249. def simple_plate_check(plate_no):
  250. """最简单的车牌检查:只要不是空的和unknown就通过"""
  251. plate_no = plate_no.strip()
  252. if len(plate_no) < 4 or plate_no.lower() in ['unknown', '']:
  253. return False, "太短或未知"
  254. return True, "通过"
  255. def order_points(pts):
  256. rect = np.zeros((4, 2), dtype="float32")
  257. s = pts.sum(axis=1)
  258. rect[0] = pts[np.argmin(s)]
  259. rect[2] = pts[np.argmax(s)]
  260. diff = np.diff(pts, axis=1)
  261. rect[1] = pts[np.argmin(diff)]
  262. rect[3] = pts[np.argmax(diff)]
  263. return rect
  264. def four_point_transform(image, pts):
  265. """透视变换得到车牌小图"""
  266. rect = pts.astype("float32")
  267. (tl, tr, br, bl) = rect
  268. widthA = np.sqrt(((br[0] - bl[0]) ** 2) + ((br[1] - bl[1]) ** 2))
  269. widthB = np.sqrt(((tr[0] - tl[0]) ** 2) + ((tr[1] - tl[1]) ** 2))
  270. maxWidth = max(int(widthA), int(widthB))
  271. heightA = np.sqrt(((tr[0] - br[0]) ** 2) + ((tr[1] - br[1]) ** 2))
  272. heightB = np.sqrt(((tl[0] - bl[0]) ** 2) + ((tl[1] - bl[1]) ** 2))
  273. maxHeight = max(int(heightA), int(heightB))
  274. dst = np.array(
  275. [[0, 0], [maxWidth - 1, 0], [maxWidth - 1, maxHeight - 1], [0, maxHeight - 1]],
  276. dtype="float32",
  277. )
  278. M = cv2.getPerspectiveTransform(rect, dst)
  279. warped = cv2.warpPerspective(image, M, (maxWidth, maxHeight))
  280. return warped
  281. def load_model(weights, device):
  282. """加载检测模型"""
  283. model = attempt_load(weights, map_location=device)
  284. return model
  285. def scale_coords_landmarks(img1_shape, coords, img0_shape, ratio_pad=None):
  286. """返回到原图坐标"""
  287. if ratio_pad is None:
  288. gain = min(img1_shape[0] / img0_shape[0], img1_shape[1] / img0_shape[1])
  289. pad = ((img1_shape[1] - img0_shape[1] * gain) / 2, (img1_shape[0] - img0_shape[0] * gain) / 2)
  290. else:
  291. gain = ratio_pad[0][0]
  292. pad = ratio_pad[1]
  293. coords[:, [0, 2, 4, 6]] -= pad[0]
  294. coords[:, [1, 3, 5, 7]] -= pad[1]
  295. coords[:, :8] /= gain
  296. coords[:, 0].clamp_(0, img0_shape[1])
  297. coords[:, 1].clamp_(0, img0_shape[0])
  298. coords[:, 2].clamp_(0, img0_shape[1])
  299. coords[:, 3].clamp_(0, img0_shape[0])
  300. coords[:, 4].clamp_(0, img0_shape[1])
  301. coords[:, 5].clamp_(0, img0_shape[0])
  302. coords[:, 6].clamp_(0, img0_shape[1])
  303. coords[:, 7].clamp_(0, img0_shape[0])
  304. return coords
  305. def get_plate_rec_landmark(img, xyxy, conf, landmarks, class_num, device, plate_rec_model, is_color=False):
  306. """获取车牌坐标以及四个角点坐标并识别车牌号"""
  307. h, w, c = img.shape
  308. result_dict = {}
  309. x1 = int(xyxy[0])
  310. y1 = int(xyxy[1])
  311. x2 = int(xyxy[2])
  312. y2 = int(xyxy[3])
  313. height = y2 - y1
  314. landmarks_np = np.zeros((4, 2))
  315. rect = [x1, y1, x2, y2]
  316. for i in range(4):
  317. point_x = int(landmarks[2 * i])
  318. point_y = int(landmarks[2 * i + 1])
  319. landmarks_np[i] = np.array([point_x, point_y])
  320. class_label = int(class_num)
  321. roi_img = four_point_transform(img, landmarks_np)
  322. if class_label:
  323. roi_img = get_split_merge(roi_img)
  324. if not is_color:
  325. plate_number, rec_prob = get_plate_result(roi_img, device, plate_rec_model, is_color=is_color)
  326. else:
  327. plate_number, rec_prob, plate_color, color_conf = get_plate_result(roi_img, device, plate_rec_model,
  328. is_color=is_color)
  329. result_dict["rect"] = rect
  330. result_dict["detect_conf"] = conf
  331. result_dict["landmarks"] = landmarks_np.tolist()
  332. result_dict["plate_no"] = plate_number
  333. result_dict["rec_conf"] = rec_prob
  334. result_dict["roi_height"] = roi_img.shape[0]
  335. result_dict["plate_color"] = ""
  336. if is_color:
  337. result_dict["plate_color"] = plate_color
  338. result_dict["color_conf"] = color_conf
  339. result_dict["plate_type"] = class_num
  340. return result_dict
  341. def detect_Recognition_plate(model, orgimg, device, plate_rec_model, img_size, is_color=False):
  342. """获取车牌信息"""
  343. conf_thres = 0.3
  344. iou_thres = 0.5
  345. dict_list = []
  346. img0 = copy.deepcopy(orgimg)
  347. assert orgimg is not None, "Image Not Found "
  348. h0, w0 = orgimg.shape[:2]
  349. r = img_size / max(h0, w0)
  350. if r != 1:
  351. interp = cv2.INTER_AREA if r < 1 else cv2.INTER_LINEAR
  352. img0 = cv2.resize(img0, (int(w0 * r), int(h0 * r)), interpolation=interp)
  353. imgsz = check_img_size(img_size, s=model.stride.max())
  354. img = letterbox(img0, new_shape=imgsz)[0]
  355. img = img[:, :, ::-1].transpose(2, 0, 1).copy()
  356. t0 = time.time()
  357. img = torch.from_numpy(img).to(device)
  358. img = img.float()
  359. img /= 255.0
  360. if img.ndimension() == 3:
  361. img = img.unsqueeze(0)
  362. pred = model(img)[0]
  363. pred = non_max_suppression_face(pred, conf_thres, iou_thres)
  364. for i, det in enumerate(pred):
  365. if len(det):
  366. det[:, :4] = scale_coords(img.shape[2:], det[:, :4], orgimg.shape).round()
  367. for c in det[:, -1].unique():
  368. n = (det[:, -1] == c).sum()
  369. det[:, 5:13] = scale_coords_landmarks(img.shape[2:], det[:, 5:13], orgimg.shape).round()
  370. for j in range(det.size()[0]):
  371. xyxy = det[j, :4].view(-1).tolist()
  372. conf = det[j, 4].cpu().numpy()
  373. landmarks = det[j, 5:13].view(-1).tolist()
  374. class_num = det[j, 13].cpu().numpy()
  375. result_dict = get_plate_rec_landmark(orgimg, xyxy, conf, landmarks, class_num, device, plate_rec_model,
  376. is_color=is_color)
  377. dict_list.append(result_dict)
  378. return dict_list
  379. def start(image_path="imgs"):
  380. """主函数"""
  381. parser = argparse.ArgumentParser()
  382. parser.add_argument("--detect_model", nargs="+", type=str, default="weights/plate_detect.pt",
  383. help="model.pt path(s)")
  384. parser.add_argument("--rec_model", type=str, default="weights/plate_rec_color.pth", help="model.pt path(s)")
  385. parser.add_argument("--is_color", type=bool, default=True, help="plate color")
  386. parser.add_argument("--image_path", type=str, default=image_path, help="source")
  387. parser.add_argument("--img_size", type=int, default=640, help="inference size (pixels)")
  388. parser.add_argument("--output", type=str, default="result", help="source")
  389. parser.add_argument("--video", type=str, default="", help="source")
  390. parser.add_argument("--stream", type=str, default="", help="RTSP/RTMP video stream URL")
  391. parser.add_argument("--redis_host", type=str, default="localhost", help="Redis host")
  392. parser.add_argument("--redis_port", type=int, default=6379, help="Redis port")
  393. parser.add_argument("--redis_key", type=str, default="plate_results", help="Redis key name")
  394. parser.add_argument("--window_size", type=int, default=5, help="Sliding window size in seconds")
  395. device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
  396. opt = parser.parse_args()
  397. global REDIS_HOST, REDIS_PORT, REDIS_KEY, WINDOW_SIZE, redis_client
  398. REDIS_HOST = opt.redis_host
  399. REDIS_PORT = opt.redis_port
  400. REDIS_KEY = opt.redis_key
  401. WINDOW_SIZE = opt.window_size
  402. print("=" * 60)
  403. print("最终优化版:5秒滑动窗口,自动清理过期数据")
  404. print("=" * 60)
  405. print(f"阈值: 检测{DETECT_THRESH} 颜色{COLOR_THRESH} 识别{REC_THRESH}")
  406. print(f"Redis: {REDIS_HOST}:{REDIS_PORT}")
  407. print(f"Redis键: {REDIS_KEY}")
  408. print(f"滑动窗口: {WINDOW_SIZE}秒")
  409. print("格式验证: 放宽标准,允许不完整车牌")
  410. print("去重策略: 前5字符相同视为同一车牌,3秒冷却")
  411. print("输出内容: 车牌、时间、车牌颜色")
  412. print("=" * 60)
  413. print(opt)
  414. try:
  415. redis_client = redis.Redis(
  416. host=REDIS_HOST,
  417. port=REDIS_PORT,
  418. db=REDIS_DB,
  419. password=REDIS_PASSWORD,
  420. decode_responses=True
  421. )
  422. redis_client.ping()
  423. print("Redis连接成功")
  424. except Exception as e:
  425. print(f"Redis连接失败: {e}")
  426. redis_client = None
  427. save_path = opt.output
  428. if not os.path.exists(save_path):
  429. os.mkdir(save_path)
  430. detect_model = load_model(opt.detect_model, device)
  431. plate_rec_model = init_model(device, opt.rec_model, is_color=opt.is_color)
  432. total = sum(p.numel() for p in detect_model.parameters())
  433. total_1 = sum(p.numel() for p in plate_rec_model.parameters())
  434. print("detect params: %.2fM,rec params: %.2fM" % (total / 1e6, total_1 / 1e6))
  435. if opt.stream:
  436. # 设置FFMPEG选项
  437. cap_options = (
  438. "rtsp_transport;tcp;"
  439. "buffer_size;1024000;"
  440. "timeout;5000000"
  441. )
  442. # 初始连接
  443. cap, connected = connect_stream(opt.stream, cap_options)
  444. if not connected:
  445. print(f"[{get_current_time()}] 初始连接失败,退出程序")
  446. return
  447. consecutive_failures = 0
  448. reconnect_count = 0
  449. actual_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
  450. actual_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
  451. actual_fps = cap.get(cv2.CAP_PROP_FPS)
  452. print(f"实际视频流参数: {actual_width}x{actual_height} @ {actual_fps:.1f}fps")
  453. frame_count = 0
  454. processed_count = 0
  455. last_print_time = time.time()
  456. print_interval = 8.0
  457. print(f"开始处理: {opt.stream}")
  458. print("等待车牌出现...")
  459. inference_times = deque(maxlen=30)
  460. last_output_dict = {}
  461. output_count = 0
  462. try:
  463. while True:
  464. frame_count += 1
  465. # 读取帧
  466. ret, frame = cap.read()
  467. if not ret:
  468. consecutive_failures += 1
  469. print(f"[{get_current_time()}] 视频流中断 (连续失败{consecutive_failures}次),尝试重新连接...")
  470. # 如果连续失败太多,退出程序
  471. if consecutive_failures >= MAX_CONSECUTIVE_FAILURES:
  472. print(f"[{get_current_time()}] 连续失败次数过多,停止重连")
  473. break
  474. # 尝试重新连接
  475. cap, reconnected = reconnect_stream(opt.stream, cap_options)
  476. if reconnected:
  477. reconnect_count += 1
  478. consecutive_failures = 0 # 重置失败计数
  479. print(f"[{get_current_time()}] 重新连接成功 (第{reconnect_count}次)")
  480. frame_count = 0 # 重置帧计数
  481. continue
  482. else:
  483. print(f"[{get_current_time()}] 重新连接失败,{RECONNECT_DELAY}秒后再次尝试...")
  484. time.sleep(RECONNECT_DELAY)
  485. continue
  486. # 重置连续失败计数
  487. consecutive_failures = 0
  488. should_process = (frame_count % 3 == 0)
  489. output_this_cycle = 0
  490. if should_process:
  491. processed_count += 1
  492. try:
  493. inference_start = time.time()
  494. dict_list = detect_Recognition_plate(detect_model, frame, device, plate_rec_model, opt.img_size,
  495. is_color=opt.is_color)
  496. inference_time = time.time() - inference_start
  497. inference_times.append(inference_time)
  498. current_time = time.time()
  499. current_time_str = get_current_time()
  500. for res in dict_list:
  501. plate_no = res['plate_no'].strip()
  502. if len(plate_no) < 4 or plate_no.lower() in ['unknown', '']:
  503. continue
  504. detect_conf = float(res['detect_conf'])
  505. color_conf = res.get('color_conf', 0.0)
  506. rec_conf = res.get('rec_conf', [])
  507. rec_avg = np.mean(rec_conf) if isinstance(rec_conf, (list, np.ndarray)) and len(
  508. rec_conf) > 0 else 0.0
  509. plate_color = res.get('plate_color', '未知')
  510. if detect_conf < DETECT_THRESH or color_conf < COLOR_THRESH or rec_avg < REC_THRESH:
  511. continue
  512. is_valid, reason = simple_plate_check(plate_no)
  513. if not is_valid:
  514. continue
  515. ok, output_reason = should_output(plate_no, last_output_dict, current_time)
  516. if ok:
  517. output_line = f"[{current_time_str}] [有效] {plate_no} | 检:{detect_conf:.3f} 色:{color_conf:.3f} 识:{rec_avg:.3f} | {plate_color} | {output_reason}"
  518. print(output_line)
  519. if redis_client:
  520. save_success, save_msg = save_to_redis(plate_no, plate_color, detect_conf,
  521. color_conf, rec_avg)
  522. if save_success:
  523. output_line += f" | {save_msg}"
  524. else:
  525. output_line += f" | {save_msg}"
  526. output_count += 1
  527. last_output_dict[plate_no.replace(' ', '').upper()] = current_time
  528. output_this_cycle += 1
  529. except Exception as e:
  530. print(f"[{get_current_time()}] 处理异常: {e}")
  531. continue
  532. current_time = time.time()
  533. if current_time - last_print_time >= print_interval:
  534. avg_inference = sum(inference_times) / len(inference_times) if inference_times else 0
  535. unique_plates = len(last_output_dict)
  536. print(f"\n[{get_current_time()}] 状态 @{frame_count}")
  537. print(f"处理帧率: {processed_count / (current_time - last_print_time + 0.1):.1f}fps")
  538. print(f"平均推理: {avg_inference * 1000:.1f}ms")
  539. print(f"输出车牌: {output_count}个(累计) | 缓存种类: {unique_plates}种")
  540. if redis_client:
  541. try:
  542. window_info = get_window_info()
  543. print(f"滑动窗口: {window_info['count']}条记录/{window_info['window_size']}秒")
  544. if window_info['count'] > 0:
  545. print(f"时间范围: {window_info['oldest_record']} ~ {window_info['newest_record']}")
  546. recent_plates = get_recent_plates_from_redis()
  547. if recent_plates:
  548. print(f"窗口内车牌:")
  549. for i, record in enumerate(recent_plates, 1):
  550. data = record['data']
  551. age = get_current_timestamp() - record['timestamp']
  552. print(f" {i}. {data['plate_no']} ({data['plate_color']}) - [{age}秒前]")
  553. else:
  554. print(f"窗口内暂无记录")
  555. except Exception as e:
  556. print(f"读取窗口数据失败: {e}")
  557. last_print_time = current_time
  558. if frame_count % 100 == 0:
  559. print(f"\r[{get_current_time()}] 运行中: {frame_count}F", end="", flush=True)
  560. if cv2.waitKey(1) & 0xFF == ord('q'):
  561. break
  562. except KeyboardInterrupt:
  563. print(f"\n[{get_current_time()}] 用户中断")
  564. except Exception as e:
  565. print(f"\n[{get_current_time()}] 错误: {e}")
  566. finally:
  567. if 'cap' in globals() and cap:
  568. cap.release()
  569. cv2.destroyAllWindows()
  570. print(f"\n[{get_current_time()}] 结束报告")
  571. print(f"总帧数: {frame_count} | 处理帧: {processed_count}")
  572. if inference_times:
  573. avg_inf = sum(inference_times) / len(inference_times)
  574. print(f"平均推理: {avg_inf * 1000:.1f}ms | 实时FPS: {1 / avg_inf:.1f}")
  575. print(f"实际输出车牌: {output_count}个")
  576. print(f"识别到车牌种类: {len(last_output_dict)}种")
  577. print(f"重新连接次数: {reconnect_count}")
  578. if redis_client:
  579. try:
  580. clean_expired_data()
  581. hash_key = f"{REDIS_KEY}:data"
  582. zset_key = f"{REDIS_KEY}:sorted"
  583. data_count = redis_client.hlen(hash_key)
  584. sorted_count = redis_client.zcard(zset_key)
  585. print(f"最终窗口统计: {data_count}条记录在{WINDOW_SIZE}秒内")
  586. except:
  587. pass
  588. if __name__ == '__main__':
  589. start()