| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723 |
- import argparse
- import copy
- import os
- import re
- import time
- from collections import deque
- from datetime import datetime
- from pprint import pprint
- import cv2
- import numpy as np
- import torch
- import redis
- import sys
- from models.experimental import attempt_load
- from plate_recognition.double_plate_split_merge import get_split_merge
- from plate_recognition.plate_rec import (
- allFilePath,
- cv_imread,
- get_plate_result,
- init_model,
- )
- from utils.datasets import letterbox
- from utils.general import check_img_size, non_max_suppression_face, scale_coords
- # Redis连接配置
- REDIS_HOST = 'localhost'
- REDIS_PORT = 6379
- REDIS_DB = 0
- REDIS_PASSWORD = None
- REDIS_KEY = 'plate_results'
- WINDOW_SIZE = 5
- # 新增:重连相关配置
- MAX_RECONNECT_ATTEMPTS = 10
- RECONNECT_DELAY = 5
- MAX_CONSECUTIVE_FAILURES = 5
- # 调整阈值设置 - 提高以减少误报
- DETECT_THRESH = 0.65
- COLOR_THRESH = 0.85
- REC_THRESH = 0.9
- # 初始化Redis连接
- try:
- redis_client = redis.Redis(
- host=REDIS_HOST,
- port=REDIS_PORT,
- db=REDIS_DB,
- password=REDIS_PASSWORD,
- decode_responses=True
- )
- redis_client.ping()
- print("Redis连接成功")
- except Exception as e:
- print(f"Redis连接失败: {e}")
- redis_client = None
- def get_current_time():
- """获取当前时间字符串"""
- return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
- def get_current_timestamp():
- """获取当前时间戳(秒级)"""
- return int(time.time())
- def connect_stream(stream_url, cap_options=""):
- """建立视频流连接,带重试机制"""
- attempt = 0
- while attempt < MAX_RECONNECT_ATTEMPTS:
- try:
- print(f"[{get_current_time()}] 尝试连接视频流: {stream_url} (第{attempt + 1}次)")
- # 设置FFMPEG选项
- if cap_options:
- cap = cv2.VideoCapture()
- os.environ["OPENCV_FFMPEG_CAPTURE_OPTIONS"] = cap_options
- success = cap.open(stream_url, cv2.CAP_FFMPEG)
- else:
- cap = cv2.VideoCapture(stream_url)
- success = cap.isOpened()
- if success:
- # 设置缓冲区优化参数
- cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) # 减少缓冲区大小
- cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc('H', '2', '6', '4')) # H264
- # 测试读取一帧
- ret, frame = cap.read()
- if ret:
- print(f"[{get_current_time()}] 视频流连接成功")
- return cap, True
- else:
- print(f"[{get_current_time()}] 视频流打开但无法读取帧")
- cap.release()
- else:
- print(f"[{get_current_time()}] 无法打开视频流")
- print(f"[{get_current_time()}] 连接失败,{RECONNECT_DELAY}秒后重试...")
- time.sleep(RECONNECT_DELAY)
- attempt += 1
- except Exception as e:
- print(f"[{get_current_time()}] 连接异常: {str(e)}")
- time.sleep(RECONNECT_DELAY)
- attempt += 1
- print(f"[{get_current_time()}] 达到最大重连次数({MAX_RECONNECT_ATTEMPTS}),退出")
- return None, False
- def reconnect_stream(stream_url, cap_options=""):
- """重新连接视频流"""
- print(f"[{get_current_time()}] 开始重新连接视频流...")
- if 'cap' in globals() and cap:
- cap.release()
- time.sleep(2) # 等待2秒再重连
- return connect_stream(stream_url, cap_options)
- def clean_expired_data():
- """清理超过5秒的旧数据"""
- if redis_client is None:
- return 0
- try:
- current_ts = get_current_timestamp()
- cutoff_ts = current_ts - WINDOW_SIZE
- pipe = redis_client.pipeline()
- zset_key = f"{REDIS_KEY}:sorted"
- expired_timestamps = redis_client.zrangebyscore(zset_key, 0, cutoff_ts)
- if expired_timestamps:
- hash_key = f"{REDIS_KEY}:data"
- pipe.hdel(hash_key, *expired_timestamps)
- pipe.zremrangebyscore(zset_key, 0, cutoff_ts)
- pipe.execute()
- deleted_count = len(expired_timestamps)
- if deleted_count > 0:
- print(f"[{get_current_time()}] 清理过期数据: {deleted_count}条 (>5秒)")
- return deleted_count
- else:
- return 0
- except Exception as e:
- print(f"清理过期数据失败: {e}")
- return 0
- def save_to_redis(plate_no, plate_color, detect_conf, color_conf, rec_avg):
- """将车牌识别结果保存到Redis - 5秒滑动窗口"""
- if redis_client is None:
- return False, "Redis未连接"
- try:
- clean_expired_data()
- timestamp = get_current_timestamp()
- timestamp_ms = int(time.time() * 1000)
- entry_data = {
- "plate_no": plate_no,
- "plate_color": plate_color,
- "detect_conf": str(detect_conf),
- "color_conf": str(color_conf),
- "rec_avg": str(rec_avg),
- "timestamp": str(timestamp),
- "timestamp_ms": str(timestamp_ms),
- "datetime": get_current_time(),
- "source": "rtsp_stream"
- }
- pipe = redis_client.pipeline()
- hash_key = f"{REDIS_KEY}:data"
- pipe.hset(hash_key, timestamp, str(entry_data))
- zset_key = f"{REDIS_KEY}:sorted"
- pipe.zadd(zset_key, {timestamp: timestamp})
- pipe.execute()
- return True, f"保存到Redis成功: {timestamp}"
- except Exception as e:
- return False, f"保存到Redis失败: {e}"
- def get_recent_plates_from_redis():
- """从Redis获取最近5秒内的所有车牌记录"""
- if redis_client is None:
- return []
- try:
- clean_expired_data()
- zset_key = f"{REDIS_KEY}:sorted"
- hash_key = f"{REDIS_KEY}:data"
- current_ts = get_current_timestamp()
- cutoff_ts = current_ts - WINDOW_SIZE
- recent_timestamps = redis_client.zrangebyscore(zset_key, cutoff_ts, current_ts)
- recent_timestamps = sorted(recent_timestamps, key=int, reverse=True)
- results = []
- for ts in recent_timestamps:
- entry_str = redis_client.hget(hash_key, ts)
- if entry_str:
- try:
- data = eval(entry_str)
- results.append({
- 'timestamp': int(ts),
- 'data': data
- })
- except:
- continue
- return results
- except Exception as e:
- print(f"从Redis读取数据失败: {e}")
- return []
- def get_window_info():
- """获取滑动窗口的统计信息"""
- if redis_client is None:
- return {"count": 0, "window_size": WINDOW_SIZE}
- try:
- clean_expired_data()
- hash_key = f"{REDIS_KEY}:data"
- zset_key = f"{REDIS_KEY}:sorted"
- data_count = redis_client.hlen(hash_key)
- sorted_count = redis_client.zcard(zset_key)
- if sorted_count > 0:
- timestamps_with_scores = redis_client.zrange(zset_key, 0, -1, withscores=True)
- if timestamps_with_scores:
- timestamps = []
- for member, score in timestamps_with_scores:
- try:
- timestamps.append(int(float(score)))
- except:
- continue
- if timestamps:
- oldest_ts = min(timestamps)
- newest_ts = max(timestamps)
- time_range = newest_ts - oldest_ts
- else:
- time_range = 0
- oldest_ts = newest_ts = int(time.time())
- else:
- time_range = 0
- oldest_ts = newest_ts = int(time.time())
- else:
- time_range = 0
- oldest_ts = newest_ts = int(time.time())
- return {
- "count": data_count,
- "window_size": WINDOW_SIZE,
- "time_range": time_range,
- "oldest_record": datetime.fromtimestamp(oldest_ts).strftime("%H:%M:%S") if sorted_count > 0 else "无",
- "newest_record": datetime.fromtimestamp(newest_ts).strftime("%H:%M:%S") if sorted_count > 0 else "无"
- }
- except Exception as e:
- print(f"获取窗口信息失败: {e}")
- return {"count": 0, "window_size": WINDOW_SIZE}
- def should_output(plate_no, last_output_dict, current_time):
- """优化的去重逻辑:支持模糊匹配和更长的冷却时间"""
- clean_plate = plate_no.strip().replace(' ', '').upper()
- if len(clean_plate) < 5:
- return False, "车牌太短"
- similar_found = None
- for existing_plate in list(last_output_dict.keys()):
- if (clean_plate == existing_plate or
- (len(clean_plate) >= 5 and len(existing_plate) >= 5 and
- clean_plate[:5] == existing_plate[:5])):
- similar_found = existing_plate
- break
- if similar_found is None:
- last_output_dict[clean_plate] = current_time
- return True, "新车牌"
- else:
- last_time = last_output_dict[similar_found]
- time_diff = current_time - last_time
- if time_diff >= 3.0:
- del last_output_dict[similar_found]
- last_output_dict[clean_plate] = current_time
- return True, "更新识别"
- else:
- return False, "冷却期内"
- def simple_plate_check(plate_no):
- """最简单的车牌检查:只要不是空的和unknown就通过"""
- plate_no = plate_no.strip()
- if len(plate_no) < 4 or plate_no.lower() in ['unknown', '']:
- return False, "太短或未知"
- return True, "通过"
- def order_points(pts):
- rect = np.zeros((4, 2), dtype="float32")
- s = pts.sum(axis=1)
- rect[0] = pts[np.argmin(s)]
- rect[2] = pts[np.argmax(s)]
- diff = np.diff(pts, axis=1)
- rect[1] = pts[np.argmin(diff)]
- rect[3] = pts[np.argmax(diff)]
- return rect
- def four_point_transform(image, pts):
- """透视变换得到车牌小图"""
- rect = pts.astype("float32")
- (tl, tr, br, bl) = rect
- widthA = np.sqrt(((br[0] - bl[0]) ** 2) + ((br[1] - bl[1]) ** 2))
- widthB = np.sqrt(((tr[0] - tl[0]) ** 2) + ((tr[1] - tl[1]) ** 2))
- maxWidth = max(int(widthA), int(widthB))
- heightA = np.sqrt(((tr[0] - br[0]) ** 2) + ((tr[1] - br[1]) ** 2))
- heightB = np.sqrt(((tl[0] - bl[0]) ** 2) + ((tl[1] - bl[1]) ** 2))
- maxHeight = max(int(heightA), int(heightB))
- dst = np.array(
- [[0, 0], [maxWidth - 1, 0], [maxWidth - 1, maxHeight - 1], [0, maxHeight - 1]],
- dtype="float32",
- )
- M = cv2.getPerspectiveTransform(rect, dst)
- warped = cv2.warpPerspective(image, M, (maxWidth, maxHeight))
- return warped
- def load_model(weights, device):
- """加载检测模型"""
- model = attempt_load(weights, map_location=device)
- return model
- def scale_coords_landmarks(img1_shape, coords, img0_shape, ratio_pad=None):
- """返回到原图坐标"""
- if ratio_pad is None:
- gain = min(img1_shape[0] / img0_shape[0], img1_shape[1] / img0_shape[1])
- pad = ((img1_shape[1] - img0_shape[1] * gain) / 2, (img1_shape[0] - img0_shape[0] * gain) / 2)
- else:
- gain = ratio_pad[0][0]
- pad = ratio_pad[1]
- coords[:, [0, 2, 4, 6]] -= pad[0]
- coords[:, [1, 3, 5, 7]] -= pad[1]
- coords[:, :8] /= gain
- coords[:, 0].clamp_(0, img0_shape[1])
- coords[:, 1].clamp_(0, img0_shape[0])
- coords[:, 2].clamp_(0, img0_shape[1])
- coords[:, 3].clamp_(0, img0_shape[0])
- coords[:, 4].clamp_(0, img0_shape[1])
- coords[:, 5].clamp_(0, img0_shape[0])
- coords[:, 6].clamp_(0, img0_shape[1])
- coords[:, 7].clamp_(0, img0_shape[0])
- return coords
- def get_plate_rec_landmark(img, xyxy, conf, landmarks, class_num, device, plate_rec_model, is_color=False):
- """获取车牌坐标以及四个角点坐标并识别车牌号"""
- h, w, c = img.shape
- result_dict = {}
- x1 = int(xyxy[0])
- y1 = int(xyxy[1])
- x2 = int(xyxy[2])
- y2 = int(xyxy[3])
- height = y2 - y1
- landmarks_np = np.zeros((4, 2))
- rect = [x1, y1, x2, y2]
- for i in range(4):
- point_x = int(landmarks[2 * i])
- point_y = int(landmarks[2 * i + 1])
- landmarks_np[i] = np.array([point_x, point_y])
- class_label = int(class_num)
- roi_img = four_point_transform(img, landmarks_np)
- if class_label:
- roi_img = get_split_merge(roi_img)
- if not is_color:
- plate_number, rec_prob = get_plate_result(roi_img, device, plate_rec_model, is_color=is_color)
- else:
- plate_number, rec_prob, plate_color, color_conf = get_plate_result(roi_img, device, plate_rec_model,
- is_color=is_color)
- result_dict["rect"] = rect
- result_dict["detect_conf"] = conf
- result_dict["landmarks"] = landmarks_np.tolist()
- result_dict["plate_no"] = plate_number
- result_dict["rec_conf"] = rec_prob
- result_dict["roi_height"] = roi_img.shape[0]
- result_dict["plate_color"] = ""
- if is_color:
- result_dict["plate_color"] = plate_color
- result_dict["color_conf"] = color_conf
- result_dict["plate_type"] = class_num
- return result_dict
- def detect_Recognition_plate(model, orgimg, device, plate_rec_model, img_size, is_color=False):
- """获取车牌信息"""
- conf_thres = 0.3
- iou_thres = 0.5
- dict_list = []
- img0 = copy.deepcopy(orgimg)
- assert orgimg is not None, "Image Not Found "
- h0, w0 = orgimg.shape[:2]
- r = img_size / max(h0, w0)
- if r != 1:
- interp = cv2.INTER_AREA if r < 1 else cv2.INTER_LINEAR
- img0 = cv2.resize(img0, (int(w0 * r), int(h0 * r)), interpolation=interp)
- imgsz = check_img_size(img_size, s=model.stride.max())
- img = letterbox(img0, new_shape=imgsz)[0]
- img = img[:, :, ::-1].transpose(2, 0, 1).copy()
- t0 = time.time()
- img = torch.from_numpy(img).to(device)
- img = img.float()
- img /= 255.0
- if img.ndimension() == 3:
- img = img.unsqueeze(0)
- pred = model(img)[0]
- pred = non_max_suppression_face(pred, conf_thres, iou_thres)
- for i, det in enumerate(pred):
- if len(det):
- det[:, :4] = scale_coords(img.shape[2:], det[:, :4], orgimg.shape).round()
- for c in det[:, -1].unique():
- n = (det[:, -1] == c).sum()
- det[:, 5:13] = scale_coords_landmarks(img.shape[2:], det[:, 5:13], orgimg.shape).round()
- for j in range(det.size()[0]):
- xyxy = det[j, :4].view(-1).tolist()
- conf = det[j, 4].cpu().numpy()
- landmarks = det[j, 5:13].view(-1).tolist()
- class_num = det[j, 13].cpu().numpy()
- result_dict = get_plate_rec_landmark(orgimg, xyxy, conf, landmarks, class_num, device, plate_rec_model,
- is_color=is_color)
- dict_list.append(result_dict)
- return dict_list
- def start(image_path="imgs"):
- """主函数"""
- parser = argparse.ArgumentParser()
- parser.add_argument("--detect_model", nargs="+", type=str, default="weights/plate_detect.pt",
- help="model.pt path(s)")
- parser.add_argument("--rec_model", type=str, default="weights/plate_rec_color.pth", help="model.pt path(s)")
- parser.add_argument("--is_color", type=bool, default=True, help="plate color")
- parser.add_argument("--image_path", type=str, default=image_path, help="source")
- parser.add_argument("--img_size", type=int, default=640, help="inference size (pixels)")
- parser.add_argument("--output", type=str, default="result", help="source")
- parser.add_argument("--video", type=str, default="", help="source")
- parser.add_argument("--stream", type=str, default="", help="RTSP/RTMP video stream URL")
- parser.add_argument("--redis_host", type=str, default="localhost", help="Redis host")
- parser.add_argument("--redis_port", type=int, default=6379, help="Redis port")
- parser.add_argument("--redis_key", type=str, default="plate_results", help="Redis key name")
- parser.add_argument("--window_size", type=int, default=5, help="Sliding window size in seconds")
- device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
- opt = parser.parse_args()
- global REDIS_HOST, REDIS_PORT, REDIS_KEY, WINDOW_SIZE, redis_client
- REDIS_HOST = opt.redis_host
- REDIS_PORT = opt.redis_port
- REDIS_KEY = opt.redis_key
- WINDOW_SIZE = opt.window_size
- print("=" * 60)
- print("最终优化版:5秒滑动窗口,自动清理过期数据")
- print("=" * 60)
- print(f"阈值: 检测{DETECT_THRESH} 颜色{COLOR_THRESH} 识别{REC_THRESH}")
- print(f"Redis: {REDIS_HOST}:{REDIS_PORT}")
- print(f"Redis键: {REDIS_KEY}")
- print(f"滑动窗口: {WINDOW_SIZE}秒")
- print("格式验证: 放宽标准,允许不完整车牌")
- print("去重策略: 前5字符相同视为同一车牌,3秒冷却")
- print("输出内容: 车牌、时间、车牌颜色")
- print("=" * 60)
- print(opt)
- try:
- redis_client = redis.Redis(
- host=REDIS_HOST,
- port=REDIS_PORT,
- db=REDIS_DB,
- password=REDIS_PASSWORD,
- decode_responses=True
- )
- redis_client.ping()
- print("Redis连接成功")
- except Exception as e:
- print(f"Redis连接失败: {e}")
- redis_client = None
- save_path = opt.output
- if not os.path.exists(save_path):
- os.mkdir(save_path)
- detect_model = load_model(opt.detect_model, device)
- plate_rec_model = init_model(device, opt.rec_model, is_color=opt.is_color)
- total = sum(p.numel() for p in detect_model.parameters())
- total_1 = sum(p.numel() for p in plate_rec_model.parameters())
- print("detect params: %.2fM,rec params: %.2fM" % (total / 1e6, total_1 / 1e6))
- if opt.stream:
- # 设置FFMPEG选项
- cap_options = (
- "rtsp_transport;tcp;"
- "buffer_size;1024000;"
- "timeout;5000000"
- )
- # 初始连接
- cap, connected = connect_stream(opt.stream, cap_options)
- if not connected:
- print(f"[{get_current_time()}] 初始连接失败,退出程序")
- return
- consecutive_failures = 0
- reconnect_count = 0
- actual_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
- actual_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
- actual_fps = cap.get(cv2.CAP_PROP_FPS)
- print(f"实际视频流参数: {actual_width}x{actual_height} @ {actual_fps:.1f}fps")
- frame_count = 0
- processed_count = 0
- last_print_time = time.time()
- print_interval = 8.0
- print(f"开始处理: {opt.stream}")
- print("等待车牌出现...")
- inference_times = deque(maxlen=30)
- last_output_dict = {}
- output_count = 0
- try:
- while True:
- frame_count += 1
- # 读取帧
- ret, frame = cap.read()
- if not ret:
- consecutive_failures += 1
- print(f"[{get_current_time()}] 视频流中断 (连续失败{consecutive_failures}次),尝试重新连接...")
- # 如果连续失败太多,退出程序
- if consecutive_failures >= MAX_CONSECUTIVE_FAILURES:
- print(f"[{get_current_time()}] 连续失败次数过多,停止重连")
- break
- # 尝试重新连接
- cap, reconnected = reconnect_stream(opt.stream, cap_options)
- if reconnected:
- reconnect_count += 1
- consecutive_failures = 0 # 重置失败计数
- print(f"[{get_current_time()}] 重新连接成功 (第{reconnect_count}次)")
- frame_count = 0 # 重置帧计数
- continue
- else:
- print(f"[{get_current_time()}] 重新连接失败,{RECONNECT_DELAY}秒后再次尝试...")
- time.sleep(RECONNECT_DELAY)
- continue
- # 重置连续失败计数
- consecutive_failures = 0
- should_process = (frame_count % 3 == 0)
- output_this_cycle = 0
- if should_process:
- processed_count += 1
- try:
- inference_start = time.time()
- dict_list = detect_Recognition_plate(detect_model, frame, device, plate_rec_model, opt.img_size,
- is_color=opt.is_color)
- inference_time = time.time() - inference_start
- inference_times.append(inference_time)
- current_time = time.time()
- current_time_str = get_current_time()
- for res in dict_list:
- plate_no = res['plate_no'].strip()
- if len(plate_no) < 4 or plate_no.lower() in ['unknown', '']:
- continue
- detect_conf = float(res['detect_conf'])
- color_conf = res.get('color_conf', 0.0)
- rec_conf = res.get('rec_conf', [])
- rec_avg = np.mean(rec_conf) if isinstance(rec_conf, (list, np.ndarray)) and len(
- rec_conf) > 0 else 0.0
- plate_color = res.get('plate_color', '未知')
- if detect_conf < DETECT_THRESH or color_conf < COLOR_THRESH or rec_avg < REC_THRESH:
- continue
- is_valid, reason = simple_plate_check(plate_no)
- if not is_valid:
- continue
- ok, output_reason = should_output(plate_no, last_output_dict, current_time)
- if ok:
- output_line = f"[{current_time_str}] [有效] {plate_no} | 检:{detect_conf:.3f} 色:{color_conf:.3f} 识:{rec_avg:.3f} | {plate_color} | {output_reason}"
- print(output_line)
- if redis_client:
- save_success, save_msg = save_to_redis(plate_no, plate_color, detect_conf,
- color_conf, rec_avg)
- if save_success:
- output_line += f" | {save_msg}"
- else:
- output_line += f" | {save_msg}"
- output_count += 1
- last_output_dict[plate_no.replace(' ', '').upper()] = current_time
- output_this_cycle += 1
- except Exception as e:
- print(f"[{get_current_time()}] 处理异常: {e}")
- continue
- current_time = time.time()
- if current_time - last_print_time >= print_interval:
- avg_inference = sum(inference_times) / len(inference_times) if inference_times else 0
- unique_plates = len(last_output_dict)
- print(f"\n[{get_current_time()}] 状态 @{frame_count}")
- print(f"处理帧率: {processed_count / (current_time - last_print_time + 0.1):.1f}fps")
- print(f"平均推理: {avg_inference * 1000:.1f}ms")
- print(f"输出车牌: {output_count}个(累计) | 缓存种类: {unique_plates}种")
- if redis_client:
- try:
- window_info = get_window_info()
- print(f"滑动窗口: {window_info['count']}条记录/{window_info['window_size']}秒")
- if window_info['count'] > 0:
- print(f"时间范围: {window_info['oldest_record']} ~ {window_info['newest_record']}")
- recent_plates = get_recent_plates_from_redis()
- if recent_plates:
- print(f"窗口内车牌:")
- for i, record in enumerate(recent_plates, 1):
- data = record['data']
- age = get_current_timestamp() - record['timestamp']
- print(f" {i}. {data['plate_no']} ({data['plate_color']}) - [{age}秒前]")
- else:
- print(f"窗口内暂无记录")
- except Exception as e:
- print(f"读取窗口数据失败: {e}")
- last_print_time = current_time
- if frame_count % 100 == 0:
- print(f"\r[{get_current_time()}] 运行中: {frame_count}F", end="", flush=True)
- if cv2.waitKey(1) & 0xFF == ord('q'):
- break
- except KeyboardInterrupt:
- print(f"\n[{get_current_time()}] 用户中断")
- except Exception as e:
- print(f"\n[{get_current_time()}] 错误: {e}")
- finally:
- if 'cap' in globals() and cap:
- cap.release()
- cv2.destroyAllWindows()
- print(f"\n[{get_current_time()}] 结束报告")
- print(f"总帧数: {frame_count} | 处理帧: {processed_count}")
- if inference_times:
- avg_inf = sum(inference_times) / len(inference_times)
- print(f"平均推理: {avg_inf * 1000:.1f}ms | 实时FPS: {1 / avg_inf:.1f}")
- print(f"实际输出车牌: {output_count}个")
- print(f"识别到车牌种类: {len(last_output_dict)}种")
- print(f"重新连接次数: {reconnect_count}")
- if redis_client:
- try:
- clean_expired_data()
- hash_key = f"{REDIS_KEY}:data"
- zset_key = f"{REDIS_KEY}:sorted"
- data_count = redis_client.hlen(hash_key)
- sorted_count = redis_client.zcard(zset_key)
- print(f"最终窗口统计: {data_count}条记录在{WINDOW_SIZE}秒内")
- except:
- pass
- if __name__ == '__main__':
- start()
|