From 0c2859b0db58e2e8288c3ca1b6fd72d4e2685960 Mon Sep 17 00:00:00 2001 From: BBIT-Kai <2911862937@qq.com> Date: Thu, 26 Mar 2026 17:48:20 +0800 Subject: [PATCH] =?UTF-8?q?=E5=90=8E=E7=AB=AF=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bbit_ai/app/agent/vehicleImageAgent.py | 17 +- bbit_ai/app/ai/plate/__init__.py | 0 bbit_ai/app/ai/plate/detect_rec_plate.py | 433 ++++++++++++++++++ bbit_ai/app/ai/plate/my_plate.py | 98 ++++ .../double_plate_split_merge.py | 15 + .../ai/plate/plate_recognition/plateNet.py | 203 ++++++++ .../ai/plate/plate_recognition/plate_rec.py | 134 ++++++ bbit_ai/app/app.py | 25 +- bbit_ai/app/config/minIO.py | 30 +- bbit_ai/app/config/rabbitMQ.py | 14 +- bbit_ai/app/config/yolo.py | 2 +- bbit_ai/app/db/postgres/annual_meeting.py | 38 +- bbit_ai/app/db/postgres/sentinel.py | 75 +-- .../app/models/SentinelRecordFrontRequest.py | 7 + bbit_ai/app/models/SentinelRecordRequest.py | 5 +- .../app/models/SentinelRecordSideRequest.py | 8 + .../e707cc77bd1a4342b63e30321556a184.jpg | Bin 0 -> 118735 bytes bbit_ai/app/routers/Public.py | 14 +- bbit_ai/app/routers/Sentinel.py | 109 +++-- bbit_ai/app/service/RabbitMQ.py | 148 +++--- bbit_ai/app/service/vision.py | 120 +++-- bbit_ai/app/utils/MyUtils.py | 54 +++ 22 files changed, 1336 insertions(+), 213 deletions(-) create mode 100644 bbit_ai/app/ai/plate/__init__.py create mode 100644 bbit_ai/app/ai/plate/detect_rec_plate.py create mode 100644 bbit_ai/app/ai/plate/my_plate.py create mode 100644 bbit_ai/app/ai/plate/plate_recognition/double_plate_split_merge.py create mode 100644 bbit_ai/app/ai/plate/plate_recognition/plateNet.py create mode 100644 bbit_ai/app/ai/plate/plate_recognition/plate_rec.py create mode 100644 bbit_ai/app/models/SentinelRecordFrontRequest.py create mode 100644 bbit_ai/app/models/SentinelRecordSideRequest.py create mode 100644 bbit_ai/app/result/e707cc77bd1a4342b63e30321556a184.jpg diff --git a/bbit_ai/app/agent/vehicleImageAgent.py b/bbit_ai/app/agent/vehicleImageAgent.py index 8fbaa69..6647154 100644 --- a/bbit_ai/app/agent/vehicleImageAgent.py +++ b/bbit_ai/app/agent/vehicleImageAgent.py @@ -28,20 +28,21 @@ def send_analyze(state: State, prompt_text: str): def analysis(state: State): state["content"] = send_analyze( - state, + state, # todo """ -提示词示例 -你是一个图像分析助手。现在给你一张车的侧身照片,请你从图中分析车上运输的牲畜种类。 +你是一个图像分析助手。现在给你一张道路图片,你需要观察远离你的第二根车道上,画面中心的车辆,请你从分析该车辆。 要求: -1. 牲畜种类可能是:牛、羊、猪、鸡、鸭、鹅。 -2. 如果图中无法判断牲畜类型,请在备注字段 remark 中写明“无法识别”或你观察到的情况。 +1. have_animal 字段中填写 true +2. livestock_type 字段中填写 “货物种类”,例如 牛、羊、猪、鸡、鸭、鹅、钢管、土渣等任何你观察到的 +3. remark 字段 需要你简短的描述该车辆车身情况,例如什么颜色的车身,你需要完整的尽你所能形容一下。 3. 不允许输出多余文字,直接返回 JSON。 JSON 示例格式: { - "livestock_type": "<牲畜种类>", // 如果能识别就填牛/羊/猪/鸡/鸭/鹅 - "remark": "<备注>" // 如果无法识别,写明原因;否则可留空 + "have_animal": true, + "livestock_type": "“测试数据:” + 5位随机数", // 例如 测试数据34223 + "remark": "<备注>" // 车身描述 } 请确保输出的 JSON 可以被严格解析。 """, @@ -59,8 +60,6 @@ graph = workflow.compile() # 执行函数 - - async def get_vehicle_response(image_url: str): final_state = graph.invoke( { diff --git a/bbit_ai/app/ai/plate/__init__.py b/bbit_ai/app/ai/plate/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bbit_ai/app/ai/plate/detect_rec_plate.py b/bbit_ai/app/ai/plate/detect_rec_plate.py new file mode 100644 index 0000000..1c17fc7 --- /dev/null +++ b/bbit_ai/app/ai/plate/detect_rec_plate.py @@ -0,0 +1,433 @@ +import os +from dataclasses import dataclass + +import cv2 +import numpy as np +from PIL import Image, ImageDraw, ImageFont +from ultralytics import YOLO + +from ai.plate.plate_recognition.double_plate_split_merge import get_split_merge +from ai.plate.plate_recognition.plate_rec import get_plate_result + + +@dataclass +class PlateResult: + plate: str + color: str + + +def collect_files(root_path): + file_list = [] + for root, _, files in os.walk(root_path): + for name in files: + file_list.append(os.path.join(root, name)) + return sorted(file_list) + + +def four_point_transform(image, pts): + rect = pts.astype(np.float32) + (tl, tr, br, bl) = rect + + width_a = np.sqrt(((br[0] - bl[0]) ** 2) + ((br[1] - bl[1]) ** 2)) + width_b = np.sqrt(((tr[0] - tl[0]) ** 2) + ((tr[1] - tl[1]) ** 2)) + max_width = max(int(width_a), int(width_b)) + + height_a = np.sqrt(((tr[0] - br[0]) ** 2) + ((tr[1] - br[1]) ** 2)) + height_b = np.sqrt(((tl[0] - bl[0]) ** 2) + ((tl[1] - bl[1]) ** 2)) + max_height = max(int(height_a), int(height_b)) + + dst = np.array( + [ + [0, 0], + [max_width - 1, 0], + [max_width - 1, max_height - 1], + [0, max_height - 1], + ], + dtype=np.float32, + ) + matrix = cv2.getPerspectiveTransform(rect, dst) + return cv2.warpPerspective(image, matrix, (max_width, max_height)) + + +def load_model(weights, device): + model = YOLO(weights) + model.to(device) + return model + + +def det_rec_plate(img_ori, detect_model, plate_rec_model, device, conf=0.3, iou=0.5): + result_list = [] + results = detect_model(img_ori, conf=conf, iou=iou, verbose=False) + + for result in results: + boxes = result.boxes + keypoints = result.keypoints + if len(boxes) == 0 or keypoints is None: + continue + + kpts_xy = keypoints.xy + num_det = min(len(boxes), len(kpts_xy)) + for idx in range(num_det): + box = boxes.xyxy[idx].cpu().numpy() + det_conf = float(boxes.conf[idx]) + plate_type = int(boxes.cls[idx]) + + landmarks = kpts_xy[idx].cpu().numpy().astype(np.int64) + roi_img = four_point_transform(img_ori, landmarks) + if plate_type == 1: + roi_img = get_split_merge(roi_img) + + plate_number, _, plate_color, color_conf = get_plate_result( + roi_img, device, plate_rec_model, is_color=True + ) + + result_list.append( + { + "plate_no": plate_number, + "plate_color": plate_color, + "rect": [int(v) for v in box], + "detect_conf": det_conf, + "landmarks": landmarks.tolist(), + "roi_height": roi_img.shape[0], + "color_conf": color_conf, + "plate_type": plate_type, # 0: 单层, 1: 双层 + } + ) + return result_list + + +def _clamp(value, low, high): + return max(low, min(high, value)) + + +def _normalize_rect(rect): + if not rect or len(rect) < 4: + return None + x1 = int(round(float(rect[0]))) + y1 = int(round(float(rect[1]))) + x2 = int(round(float(rect[2]))) + y2 = int(round(float(rect[3]))) + left = min(x1, x2) + top = min(y1, y2) + right = max(x1, x2) + bottom = max(y1, y2) + if right <= left or bottom <= top: + return None + return [left, top, right, bottom] + + +def _get_plate_theme(plate_color): + theme_map = { + "蓝色": { + "bg": (72, 33, 6), + "border": (250, 165, 96), + "text": (254, 242, 224), + "glow": (246, 130, 59), + }, + "黄色": { + "bg": (0, 49, 74), + "border": (21, 204, 250), + "text": (195, 249, 254), + "glow": (8, 179, 234), + }, + "绿色": { + "bg": (27, 53, 4), + "border": (128, 222, 74), + "text": (231, 252, 220), + "glow": (94, 197, 34), + }, + "白色": { + "bg": (68, 50, 38), + "border": (240, 232, 226), + "text": (250, 250, 248), + "glow": (184, 163, 148), + }, + "黑色": { + "bg": (20, 12, 8), + "border": (184, 163, 148), + "text": (240, 232, 226), + "glow": (139, 116, 100), + }, + } + return theme_map.get( + plate_color, + { + "bg": (43, 24, 8), + "border": (248, 189, 56), + "text": (255, 248, 232), + "glow": (200, 140, 32), + }, + ) + + +def _draw_alpha_rect(img, x1, y1, x2, y2, color, alpha=0.75): + h, w = img.shape[:2] + x1 = _clamp(x1, 0, w - 1) + y1 = _clamp(y1, 0, h - 1) + x2 = _clamp(x2, 0, w) + y2 = _clamp(y2, 0, h) + if x2 <= x1 or y2 <= y1: + return + roi = img[y1:y2, x1:x2] + overlay = np.full_like(roi, color, dtype=np.uint8) + cv2.addWeighted(overlay, alpha, roi, 1 - alpha, 0, roi) + + +def _measure_text(text, text_size=16): + try: + font = ImageFont.truetype(get_font_file_path(), text_size, encoding="utf-8") + left, top, right, bottom = font.getbbox(text) + return right - left, bottom - top + except Exception: + print("字体文件加载失败") + size, baseline = cv2.getTextSize(text, cv2.FONT_HERSHEY_SIMPLEX, 0.45, 1) + return size[0], size[1] + baseline + + +def _draw_glow_border(img, x1, y1, x2, y2, border_color, glow_color): + h, w = img.shape[:2] + x1 = _clamp(x1, 0, w - 1) + y1 = _clamp(y1, 0, h - 1) + x2 = _clamp(x2, 0, w - 1) + y2 = _clamp(y2, 0, h - 1) + if x2 <= x1 or y2 <= y1: + return + glow_layer = np.zeros_like(img) + cv2.rectangle(glow_layer, (x1, y1), (x2, y2), glow_color, 2) + glow_layer = cv2.GaussianBlur(glow_layer, (0, 0), sigmaX=1.6, sigmaY=1.6) + cv2.addWeighted(glow_layer, 0.45, img, 1.0, 0, img) + cv2.rectangle(img, (x1, y1), (x2, y2), border_color, 1) + + +def _draw_tech_box(img, x1, y1, x2, y2, border_color, glow_color, track_id=None): + h, w = img.shape[:2] + x1 = _clamp(x1, 0, w - 1) + y1 = _clamp(y1, 0, h - 1) + x2 = _clamp(x2, 0, w - 1) + y2 = _clamp(y2, 0, h - 1) + if x2 <= x1 or y2 <= y1: + return + + bw = x2 - x1 + bh = y2 - y1 + diag = float(np.hypot(bw, bh)) + base_thick = _clamp(int(round(diag / 70.0)), 2, 5) + glow_sigma = _clamp(diag / 55.0, 1.2, 3.6) + + glow_layer = np.zeros_like(img) + cv2.rectangle(glow_layer, (x1, y1), (x2, y2), glow_color, max(1, base_thick - 1)) + glow_layer = cv2.GaussianBlur( + glow_layer, (0, 0), sigmaX=glow_sigma, sigmaY=glow_sigma + ) + cv2.addWeighted(glow_layer, 0.48, img, 1.0, 0, img) + cv2.rectangle(img, (x1, y1), (x2, y2), border_color, max(1, base_thick - 1)) + + corner_len = _clamp(int(round(min(bw, bh) * 0.28)), 8, 20) + t = base_thick + cv2.line(img, (x1, y1), (x1 + corner_len, y1), border_color, t) + cv2.line(img, (x1, y1), (x1, y1 + corner_len), border_color, t) + cv2.line(img, (x2, y1), (x2 - corner_len, y1), border_color, t) + cv2.line(img, (x2, y1), (x2, y1 + corner_len), border_color, t) + cv2.line(img, (x1, y2), (x1 + corner_len, y2), border_color, t) + cv2.line(img, (x1, y2), (x1, y2 - corner_len), border_color, t) + cv2.line(img, (x2, y2), (x2 - corner_len, y2), border_color, t) + cv2.line(img, (x2, y2), (x2, y2 - corner_len), border_color, t) + + if track_id is not None: + badge = "T%02d" % track_id + badge_w_txt, badge_h_txt = _measure_text(badge, text_size=12) + pad_x = 5 + pad_y = 3 + badge_w = badge_w_txt + pad_x * 2 + badge_h = badge_h_txt + pad_y * 2 + bx2 = _clamp(x2, badge_w + 2, w - 2) + by1 = _clamp(y1 - badge_h - 2, 2, h - badge_h - 2) + bx1 = bx2 - badge_w + by2 = by1 + badge_h + _draw_alpha_rect(img, bx1, by1, bx2, by2, (18, 18, 18), alpha=0.65) + cv2.rectangle(img, (bx1, by1), (bx2, by2), border_color, 1) + text_x = bx1 + pad_x + text_y = by1 + max(1, int((badge_h - badge_h_txt) / 2)) + img[:] = cv2ImgAddText(img, badge, text_x, text_y, border_color, 12) + + +def cv2ImgAddText(img, text, left, top, textColor=(0, 255, 0), textSize=20): + if isinstance(img, np.ndarray): # 判断是否OpenCV图片类型 + img = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)) + draw = ImageDraw.Draw(img) + fontText = ImageFont.truetype(get_font_file_path(), textSize, encoding="utf-8") + draw.text((left, top), text, textColor, font=fontText) + return cv2.cvtColor(np.asarray(img), cv2.COLOR_RGB2BGR) + + +def get_font_file_path(): + # win + # font_path = r"C:\Users\BBIT\Desktop\yolo26-plate-main\fonts\platech.ttf" + # linux + font_path = r"/app/models/sentinel/platech.ttf" + + return font_path + + +def _draw_tech_landmark(img, x, y, border_color, glow_color): + h, w = img.shape[:2] + x = _clamp(int(round(x)), 0, w - 1) + y = _clamp(int(round(y)), 0, h - 1) + glow_layer = np.zeros_like(img) + cv2.circle(glow_layer, (x, y), 5, glow_color, -1) + glow_layer = cv2.GaussianBlur(glow_layer, (0, 0), sigmaX=1.2, sigmaY=1.2) + cv2.addWeighted(glow_layer, 0.5, img, 1.0, 0, img) + cv2.circle(img, (x, y), 2, border_color, -1) + cv2.circle(img, (x, y), 4, border_color, 1) + + +def _plate_width_from_landmarks(landmarks, fallback_width): + if not landmarks or len(landmarks) < 4: + return float(fallback_width) + try: + p0 = np.array(landmarks[0], dtype=np.float32) + p1 = np.array(landmarks[1], dtype=np.float32) + p2 = np.array(landmarks[2], dtype=np.float32) + p3 = np.array(landmarks[3], dtype=np.float32) + top_w = float(np.linalg.norm(p1 - p0)) + bottom_w = float(np.linalg.norm(p2 - p3)) + width = (top_w + bottom_w) / 2.0 + if np.isfinite(width) and width > 1: + return width + except Exception: + pass + return float(fallback_width) + + +def _plate_height_from_landmarks(landmarks, fallback_height): + if not landmarks or len(landmarks) < 4: + return float(fallback_height) + try: + p0 = np.array(landmarks[0], dtype=np.float32) + p1 = np.array(landmarks[1], dtype=np.float32) + p2 = np.array(landmarks[2], dtype=np.float32) + p3 = np.array(landmarks[3], dtype=np.float32) + left_h = float(np.linalg.norm(p3 - p0)) + right_h = float(np.linalg.norm(p2 - p1)) + height = (left_h + right_h) / 2.0 + if np.isfinite(height) and height > 1: + return height + except Exception: + pass + return float(fallback_height) + + +def _fit_font_size(text, max_w, max_h, min_size=10, max_size=24): + if max_w <= 0 or max_h <= 0: + return min_size + for size in range(max_size, min_size - 1, -1): + tw, th = _measure_text(text, text_size=size) + if tw <= max_w and th <= max_h: + return size + return min_size + + +def draw_result(orgimg, result_list): + landmark_colors = [(255, 0, 0), (0, 255, 0), (0, 0, 255), (255, 255, 0)] + result_str = [] + img_h, img_w = orgimg.shape[:2] + for idx, result in enumerate(result_list, start=1): + raw_rect = _normalize_rect(result.get("rect")) + if raw_rect is None: + continue + + x1, y1, x2, y2 = raw_rect + w = x2 - x1 + h = y2 - y1 + padding_w = int(round(0.05 * w)) + padding_h = int(round(0.11 * h)) + rx1 = _clamp(x1 - padding_w, 0, img_w - 1) + ry1 = _clamp(y1 - padding_h, 0, img_h - 1) + rx2 = _clamp(x2 + padding_w, 0, img_w - 1) + ry2 = _clamp(y2 + padding_h, 0, img_h - 1) + + landmarks = result.get("landmarks", []) + plate_no = result.get("plate_no", "") + plate_color = result.get("plate_color", "") + if result.get("plate_type", 0) == 1: + result_p = "%s %s双层" % (plate_no, plate_color) + else: + result_p = "%s %s" % (plate_no, plate_color) + result_str.append(result_p) + + theme = _get_plate_theme(plate_color) + for i in range(min(4, len(landmarks))): + point = landmarks[i] + if len(point) < 2: + continue + point_color = landmark_colors[i] + _draw_tech_landmark(orgimg, point[0], point[1], point_color, point_color) + + _draw_tech_box( + orgimg, rx1, ry1, rx2, ry2, theme["border"], theme["glow"], track_id=idx + ) + + label = "%s | %s" % (plate_no, plate_color) + plate_w = _plate_width_from_landmarks(landmarks, rx2 - rx1) + plate_h = _plate_height_from_landmarks(landmarks, ry2 - ry1) + pre_card_h = _clamp(int(round(plate_h)), 24, min(110, img_h - 4)) + pre_pad_y = _clamp(int(round(pre_card_h * 0.16)), 3, 10) + pre_inner_h = max(8, pre_card_h - pre_pad_y * 2) + pre_max_font = _clamp(int(round(pre_card_h * 0.72)), 14, 44) + pre_min_font = _clamp(int(round(pre_card_h * 0.42)), 10, pre_max_font) + pre_font_size = _fit_font_size( + label, 4096, pre_inner_h, min_size=pre_min_font, max_size=pre_max_font + ) + pre_text_w, _ = _measure_text(label, text_size=pre_font_size) + + min_w_by_text = pre_text_w + 20 + base_w_by_plate = int(round(plate_w * 1.05)) + card_w = max(90, base_w_by_plate, min_w_by_text) + card_w = min(card_w, img_w - 8) + + card_h = pre_card_h + card_pad_x = _clamp(int(round(card_w * 0.08)), 8, 18) + card_pad_y = _clamp(int(round(card_h * 0.16)), 3, 10) + + card_x = int(rx1 + (rx2 - rx1 - card_w) / 2) + card_x = _clamp(card_x, 4, max(4, img_w - card_w - 4)) + card_y = ry1 - card_h - 2 + if card_y < 2: + card_y = _clamp(ry1 + 2, 2, max(2, img_h - card_h - 2)) + + _draw_alpha_rect( + orgimg, + card_x, + card_y, + card_x + card_w, + card_y + card_h, + theme["bg"], + alpha=0.78, + ) + _draw_glow_border( + orgimg, + card_x, + card_y, + card_x + card_w, + card_y + card_h, + theme["border"], + theme["glow"], + ) + + inner_w = max(8, card_w - card_pad_x * 2) + inner_h = max(8, card_h - card_pad_y * 2) + dynamic_max_font = _clamp(int(round(card_h * 0.72)), 14, 44) + dynamic_min_font = _clamp(int(round(card_h * 0.42)), 10, dynamic_max_font) + font_size = _fit_font_size( + label, + inner_w, + inner_h, + min_size=dynamic_min_font, + max_size=dynamic_max_font, + ) + text_w, text_h = _measure_text(label, text_size=font_size) + text_x = card_x + max(card_pad_x, int((card_w - text_w) / 2)) + text_y = card_y + max(card_pad_y - 1, int((card_h - text_h) / 2)) + orgimg = cv2ImgAddText(orgimg, label, text_x, text_y, theme["text"], font_size) + + return orgimg, result_str diff --git a/bbit_ai/app/ai/plate/my_plate.py b/bbit_ai/app/ai/plate/my_plate.py new file mode 100644 index 0000000..d318f6d --- /dev/null +++ b/bbit_ai/app/ai/plate/my_plate.py @@ -0,0 +1,98 @@ +import os +from dataclasses import dataclass + +import cv2 +import torch +from ultralytics import YOLO + +from ai.plate.detect_rec_plate import det_rec_plate, draw_result +from ai.plate.plate_recognition.plate_rec import init_model +from config.yolo import logger + + +@dataclass +class PlateInfo: + plate_no: str + plate_color: str + result_img_path: str + + +class PlateRecognizer: + _instance = None + _ready = False + + def __new__(cls, *args, **kwargs): + if cls._instance is None: + cls._instance = super(PlateRecognizer, cls).__new__(cls) + return cls._instance + + def __init__( + self, + detect_model_path, + rec_model_path, + output_dir="result", + device="cuda" if torch.cuda.is_available() else "cpu", + ): + if hasattr(self, "_initialized") and self._initialized: + return + self.device = torch.device(device) + self.output_dir = output_dir + os.makedirs(self.output_dir, exist_ok=True) + + try: + # 模型加载 + self.detect_model = YOLO(detect_model_path) + self.detect_model.to(self.device) + self.detect_model.eval() + self.plate_rec_model = init_model( + self.device, rec_model_path, is_color=True + ) + self._initialized = True + self._ready = True + except Exception as e: + self.detect_model = None + self._ready = False + logger.error(f"❌车牌 YOLO 模型加载失败: {e}") + + def analyze_image(self, image_path): + img = cv2.imread(image_path) + if not self._ready or img is None: + return [] + + # 检测与识别 + result_list = det_rec_plate( + img, self.detect_model, self.plate_rec_model, self.device + ) + + # 可视化 & 保存 + vis_img, _ = draw_result(img, result_list) + result_img_path = os.path.join(self.output_dir, os.path.basename(image_path)) + cv2.imwrite(result_img_path, vis_img) + + # 构建返回结果 + results = [ + PlateInfo( + plate_no=r["plate_no"], + plate_color=r["plate_color"], + result_img_path=result_img_path, + ) + for r in result_list + ] + return results + + +# 初始化一次 +recognizer = PlateRecognizer( + detect_model_path="/app/models/sentinel/yolo26s-plate-detect.pt", + rec_model_path="/app/models/sentinel/plate_rec_color.pth", + output_dir="result", +) + + +# base_dir = Path(r"C:\Users\BBIT\Desktop\yolo26-plate-main") +# +# recognizer = PlateRecognizer( +# detect_model_path=str(base_dir / "weights" / "yolo26s-plate-detect.pt"), +# rec_model_path=str(base_dir / "weights" / "plate_rec_color.pth"), +# output_dir="result", +# ) diff --git a/bbit_ai/app/ai/plate/plate_recognition/double_plate_split_merge.py b/bbit_ai/app/ai/plate/plate_recognition/double_plate_split_merge.py new file mode 100644 index 0000000..24c6537 --- /dev/null +++ b/bbit_ai/app/ai/plate/plate_recognition/double_plate_split_merge.py @@ -0,0 +1,15 @@ +import os +import cv2 +import numpy as np +def get_split_merge(img): + h,w,c = img.shape + img_upper = img[0:int(5/12*h),:] + img_lower = img[int(1/3*h):,:] + img_upper = cv2.resize(img_upper,(img_lower.shape[1],img_lower.shape[0])) + new_img = np.hstack((img_upper,img_lower)) + return new_img + +if __name__=="__main__": + img = cv2.imread("double_plate/tmp8078.png") + new_img =get_split_merge(img) + cv2.imwrite("double_plate/new.jpg",new_img) diff --git a/bbit_ai/app/ai/plate/plate_recognition/plateNet.py b/bbit_ai/app/ai/plate/plate_recognition/plateNet.py new file mode 100644 index 0000000..ce9a982 --- /dev/null +++ b/bbit_ai/app/ai/plate/plate_recognition/plateNet.py @@ -0,0 +1,203 @@ +import torch.nn as nn +import torch + + +class myNet_ocr(nn.Module): + def __init__(self,cfg=None,num_classes=78,export=False): + super(myNet_ocr, self).__init__() + if cfg is None: + cfg =[32,32,64,64,'M',128,128,'M',196,196,'M',256,256] + # cfg =[32,32,'M',64,64,'M',128,128,'M',256,256] + self.feature = self.make_layers(cfg, True) + self.export = export + # self.classifier = nn.Linear(cfg[-1], num_classes) + # self.loc = nn.MaxPool2d((2, 2), (5, 1), (0, 1),ceil_mode=True) + # self.loc = nn.AvgPool2d((2, 2), (5, 2), (0, 1),ceil_mode=False) + self.loc = nn.MaxPool2d((5, 2), (1, 1),(0,1),ceil_mode=False) + self.newCnn=nn.Conv2d(cfg[-1],num_classes,1,1) + # self.newBn=nn.BatchNorm2d(num_classes) + def make_layers(self, cfg, batch_norm=False): + layers = [] + in_channels = 3 + for i in range(len(cfg)): + if i == 0: + conv2d =nn.Conv2d(in_channels, cfg[i], kernel_size=5,stride =1) + if batch_norm: + layers += [conv2d, nn.BatchNorm2d(cfg[i]), nn.ReLU(inplace=True)] + else: + layers += [conv2d, nn.ReLU(inplace=True)] + in_channels = cfg[i] + else : + if cfg[i] == 'M': + layers += [nn.MaxPool2d(kernel_size=3, stride=2,ceil_mode=True)] + else: + conv2d = nn.Conv2d(in_channels, cfg[i], kernel_size=3, padding=(1,1),stride =1) + if batch_norm: + layers += [conv2d, nn.BatchNorm2d(cfg[i]), nn.ReLU(inplace=True)] + else: + layers += [conv2d, nn.ReLU(inplace=True)] + in_channels = cfg[i] + return nn.Sequential(*layers) + + def forward(self, x): + x = self.feature(x) + x=self.loc(x) + x=self.newCnn(x) + # x=self.newBn(x) + if self.export: + conv = x.squeeze(2) # b *512 * width + conv = conv.transpose(2,1) # [w, b, c] + # conv =conv.argmax(dim=2) + return conv + else: + b, c, h, w = x.size() + assert h == 1, "the height of conv must be 1" + conv = x.squeeze(2) # b *512 * width + conv = conv.permute(2, 0, 1) # [w, b, c] + # output = F.log_softmax(self.rnn(conv), dim=2) + output = torch.softmax(conv, dim=2) + return output + +myCfg = [32,'M',64,'M',96,'M',128,'M',256] +class myNet(nn.Module): + def __init__(self,cfg=None,num_classes=3): + super(myNet, self).__init__() + if cfg is None: + cfg = myCfg + self.feature = self.make_layers(cfg, True) + self.classifier = nn.Linear(cfg[-1], num_classes) + def make_layers(self, cfg, batch_norm=False): + layers = [] + in_channels = 3 + for i in range(len(cfg)): + if i == 0: + conv2d =nn.Conv2d(in_channels, cfg[i], kernel_size=5,stride =1) + if batch_norm: + layers += [conv2d, nn.BatchNorm2d(cfg[i]), nn.ReLU(inplace=True)] + else: + layers += [conv2d, nn.ReLU(inplace=True)] + in_channels = cfg[i] + else : + if cfg[i] == 'M': + layers += [nn.MaxPool2d(kernel_size=3, stride=2,ceil_mode=True)] + else: + conv2d = nn.Conv2d(in_channels, cfg[i], kernel_size=3, padding=1,stride =1) + if batch_norm: + layers += [conv2d, nn.BatchNorm2d(cfg[i]), nn.ReLU(inplace=True)] + else: + layers += [conv2d, nn.ReLU(inplace=True)] + in_channels = cfg[i] + return nn.Sequential(*layers) + + def forward(self, x): + x = self.feature(x) + x = nn.AvgPool2d(kernel_size=3, stride=1)(x) + x = x.view(x.size(0), -1) + y = self.classifier(x) + return y + + +class MyNet_color(nn.Module): + def __init__(self, class_num=6): + super(MyNet_color, self).__init__() + self.class_num = class_num + self.backbone = nn.Sequential( + nn.Conv2d(in_channels=3, out_channels=16, kernel_size=(5, 5), stride=(1, 1)), # 0 + torch.nn.BatchNorm2d(16), + nn.ReLU(), + nn.MaxPool2d(kernel_size=(2, 2)), + nn.Dropout(0), + nn.Flatten(), + nn.Linear(480, 64), + nn.Dropout(0), + nn.ReLU(), + nn.Linear(64, class_num), + nn.Dropout(0), + nn.Softmax(1) + ) + + def forward(self, x): + logits = self.backbone(x) + + return logits + + +class myNet_ocr_color(nn.Module): + def __init__(self,cfg=None,num_classes=78,export=False,color_num=None): + super(myNet_ocr_color, self).__init__() + if cfg is None: + cfg =[32,32,64,64,'M',128,128,'M',196,196,'M',256,256] + # cfg =[32,32,'M',64,64,'M',128,128,'M',256,256] + self.feature = self.make_layers(cfg, True) + self.export = export + self.color_num=color_num + self.conv_out_num=12 #颜色第一个卷积层输出通道12 + if self.color_num: + self.conv1=nn.Conv2d(cfg[-1],self.conv_out_num,kernel_size=3,stride=2) + self.bn1=nn.BatchNorm2d(self.conv_out_num) + self.relu1=nn.ReLU(inplace=True) + self.gap =nn.AdaptiveAvgPool2d(output_size=1) + self.color_classifier=nn.Conv2d(self.conv_out_num,self.color_num,kernel_size=1,stride=1) + self.color_bn = nn.BatchNorm2d(self.color_num) + self.flatten = nn.Flatten() + self.loc = nn.MaxPool2d((5, 2), (1, 1),(0,1),ceil_mode=False) + self.newCnn=nn.Conv2d(cfg[-1],num_classes,1,1) + # self.newBn=nn.BatchNorm2d(num_classes) + def make_layers(self, cfg, batch_norm=False): + layers = [] + in_channels = 3 + for i in range(len(cfg)): + if i == 0: + conv2d =nn.Conv2d(in_channels, cfg[i], kernel_size=5,stride =1) + if batch_norm: + layers += [conv2d, nn.BatchNorm2d(cfg[i]), nn.ReLU(inplace=True)] + else: + layers += [conv2d, nn.ReLU(inplace=True)] + in_channels = cfg[i] + else : + if cfg[i] == 'M': + layers += [nn.MaxPool2d(kernel_size=3, stride=2,ceil_mode=True)] + else: + conv2d = nn.Conv2d(in_channels, cfg[i], kernel_size=3, padding=(1,1),stride =1) + if batch_norm: + layers += [conv2d, nn.BatchNorm2d(cfg[i]), nn.ReLU(inplace=True)] + else: + layers += [conv2d, nn.ReLU(inplace=True)] + in_channels = cfg[i] + return nn.Sequential(*layers) + + def forward(self, x): + x = self.feature(x) + if self.color_num: + x_color=self.conv1(x) + x_color=self.bn1(x_color) + x_color =self.relu1(x_color) + x_color = self.color_classifier(x_color) + x_color = self.color_bn(x_color) + x_color =self.gap(x_color) + x_color = self.flatten(x_color) + x=self.loc(x) + x=self.newCnn(x) + + if self.export: + conv = x.squeeze(2) # b *512 * width + conv = conv.transpose(2,1) # [w, b, c] + if self.color_num: + return conv,x_color + return conv + else: + b, c, h, w = x.size() + assert h == 1, "the height of conv must be 1" + conv = x.squeeze(2) # b *512 * width + conv = conv.permute(2, 0, 1) # [w, b, c] + output = F.log_softmax(conv, dim=2) + if self.color_num: + return output,x_color + return output + + +if __name__ == '__main__': + x = torch.randn(1,3,48,216) + model = myNet_ocr(num_classes=78,export=True) + out = model(x) + print(out.shape) \ No newline at end of file diff --git a/bbit_ai/app/ai/plate/plate_recognition/plate_rec.py b/bbit_ai/app/ai/plate/plate_recognition/plate_rec.py new file mode 100644 index 0000000..d64f9ac --- /dev/null +++ b/bbit_ai/app/ai/plate/plate_recognition/plate_rec.py @@ -0,0 +1,134 @@ +import os +import time + +import cv2 +import numpy as np +import torch + +from ai.plate.plate_recognition.plateNet import myNet_ocr_color + + +def cv_imread(path): # 可以读取中文路径的图片 + img = cv2.imdecode(np.fromfile(path, dtype=np.uint8), -1) + return img + + +def allFilePath(rootPath, allFIleList): + fileList = os.listdir(rootPath) + for temp in fileList: + if os.path.isfile(os.path.join(rootPath, temp)): + if temp.endswith(".jpg") or temp.endswith(".png") or temp.endswith(".JPG"): + allFIleList.append(os.path.join(rootPath, temp)) + else: + allFilePath(os.path.join(rootPath, temp), allFIleList) + + +device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") +color = ["黑色", "蓝色", "绿色", "白色", "黄色"] +plateName = r"#京沪津渝冀晋蒙辽吉黑苏浙皖闽赣鲁豫鄂湘粤桂琼川贵云藏陕甘青宁新学警港澳挂使领民航危0123456789ABCDEFGHJKLMNPQRSTUVWXYZ险品" +mean_value, std_value = (0.588, 0.193) + + +def decodePlate(preds): + pre = 0 + newPreds = [] + index = [] + for i in range(len(preds)): + if preds[i] != 0 and preds[i] != pre: + newPreds.append(preds[i]) + index.append(i) + pre = preds[i] + return newPreds, index + + +def image_processing(img, device): + img = cv2.resize(img, (168, 48)) + img = np.reshape(img, (48, 168, 3)) + + # normalize + img = img.astype(np.float32) + img = (img / 255.0 - mean_value) / std_value + img = img.transpose([2, 0, 1]) + img = torch.from_numpy(img) + + img = img.to(device) + img = img.view(1, *img.size()) + return img + + +def get_plate_result(img, device, model, is_color=False): + input = image_processing(img, device) + if is_color: # 是否识别颜色 + preds, color_preds = model(input) + color_preds = torch.softmax(color_preds, dim=-1) + color_conf, color_index = torch.max(color_preds, dim=-1) + color_conf = color_conf.item() + else: + preds = model(input) + preds = torch.softmax(preds, dim=-1) + prob, index = preds.max(dim=-1) + index = index.view(-1).detach().cpu().numpy() + prob = prob.view(-1).detach().cpu().numpy() + + # preds=preds.view(-1).detach().cpu().numpy() + newPreds, new_index = decodePlate(index) + prob = prob[new_index] + plate = "" + for i in newPreds: + plate += plateName[i] + # if not (plate[0] in plateName[1:44] ): + # return "" + if is_color: + return ( + plate, + prob, + color[color_index], + color_conf, + ) # 返回车牌号以及每个字符的概率,以及颜色,和颜色的概率 + else: + return plate, prob + + +def init_model(device, model_path, is_color=False): + # print( print(sys.path)) + # model_path ="plate_recognition/model/checkpoint_61_acc_0.9715.pth" + check_point = torch.load(model_path, map_location=device) + model_state = check_point["state_dict"] + cfg = check_point["cfg"] + color_classes = 0 + if is_color: + color_classes = 5 # 颜色类别数 + model = myNet_ocr_color( + num_classes=len(plateName), export=True, cfg=cfg, color_num=color_classes + ) + + model.load_state_dict(model_state, strict=False) + model.to(device) + model.eval() + return model + + +# model = init_model(device) +if __name__ == "__main__": + model_path = r"weights/plate_rec_color.pth" + image_path = "images/tmp2424.png" + testPath = r"/mnt/Gpan/Mydata/pytorchPorject/CRNN/crnn_plate_recognition/images" + fileList = [] + allFilePath(testPath, fileList) + # result = get_plate_result(image_path,device) + # print(result) + is_color = False + model = init_model(device, model_path, is_color=is_color) + right = 0 + begin = time.time() + + for imge_path in fileList: + img = cv2.imread(imge_path) + if is_color: + plate, _, plate_color, _ = get_plate_result( + img, device, model, is_color=is_color + ) + print(plate) + else: + plate, _ = get_plate_result(img, device, model, is_color=is_color) + print(plate, imge_path) diff --git a/bbit_ai/app/app.py b/bbit_ai/app/app.py index a63b018..f934c73 100644 --- a/bbit_ai/app/app.py +++ b/bbit_ai/app/app.py @@ -1,4 +1,5 @@ import asyncio +from contextlib import asynccontextmanager from fastapi import FastAPI, Request from fastapi.middleware.cors import CORSMiddleware @@ -21,12 +22,25 @@ from routers.Service import serviceRouter from routers.System import systemRouter from routers.Vision import visionRouter from routers.WS import iot_ws_router -from service.RabbitMQ import sentinel_pull_analysis_async +from service.RabbitMQ import ( + mq_client, +) + + +@asynccontextmanager +async def lifespan(app: FastAPI): + # 应用启动时初始化 MQ + await mq_client.init() + # 启动消费者 + await mq_client.start_all_consumer() + yield + # 应用关闭时关闭 MQ 连接 + if mq_client._connection: + await mq_client._connection.close() async def ai_lab(): - app = FastAPI(title="BBIT_AI") - + app = FastAPI(title="BBIT_AI", lifespan=lifespan) origins = [ "http://localhost:8091", # Vite dev 默认端口 "https://ai.ronsunny.cn:8090", @@ -71,16 +85,13 @@ async def ai_lab(): async def main(): - # 初始化模型 YOLOSingleton.init_model() # 主干AI实验室FastAPI服务 task_api = asyncio.create_task(ai_lab()) - # RabbitMQ服务 - task_mq = asyncio.create_task(sentinel_pull_analysis_async()) # 等 HTTP 服务启动后再启动 MQTT task_mqtt = asyncio.create_task(mqtt_client_runner()) - await asyncio.gather(task_api, task_mq, task_mqtt) + await asyncio.gather(task_api, task_mqtt) # MCP服务-ailab # endpoint_url_ai_lab = "wss://ai.ronsunny.cn:8090/aimcp/mcp_endpoint/mcp/?token=TsSP9lBq6Oa1WMkachHoS2TtNt4GKV/Gli24pk5Rjpk%3D" diff --git a/bbit_ai/app/config/minIO.py b/bbit_ai/app/config/minIO.py index 3b12aa7..4ed84d9 100644 --- a/bbit_ai/app/config/minIO.py +++ b/bbit_ai/app/config/minIO.py @@ -1,6 +1,7 @@ from datetime import timedelta from minio import Minio +from minio.commonconfig import CopySource # MinIO 客户端初始化 minio_client = Minio( @@ -28,7 +29,7 @@ def get_upload_token(bucket_name, object_name, xpires=timedelta(minutes=15)): ) -def get_temp_url(bucket_name, object_name): +def get_temp_url(bucket_name, object_name, seconds: float = 3600): # 如果 object_name 为 None 或空字符串,则返回默认图片 if not object_name or not bucket_name: bucket_name = "system" @@ -36,7 +37,7 @@ def get_temp_url(bucket_name, object_name): # 使用 presigned_get_object 获取临时 URL return minio_client.presigned_get_object( - bucket_name, object_name, expires=timedelta(seconds=3600) + bucket_name, object_name, expires=timedelta(seconds=seconds) ) @@ -51,3 +52,28 @@ def get_temp_url_dict(bucket_name, object_dict, object_name): return minio_client.presigned_get_object( bucket_name, object_dict + "/" + object_name, expires=timedelta(seconds=3600) ) + + +# 移动文件(实际上是 copy + delete) +def move_file(bucket_name, source_object_name, target_object_name): + """ + bucket_name: bucket名称 + source_object_name: 原对象路径,例如 folder1/test.jpg + target_object_name: 目标对象路径,例如 folder2/test.jpg + """ + # 复制到新位置 + minio_client.copy_object( + bucket_name, target_object_name, CopySource(bucket_name, source_object_name) + ) + + # 删除原文件 + minio_client.remove_object(bucket_name, source_object_name) + + +# 删除文件 +def delete_file(bucket_name, object_name): + """ + bucket_name: bucket名称 + object_name: 文件路径,例如 folder/test.jpg + """ + minio_client.remove_object(bucket_name, object_name) diff --git a/bbit_ai/app/config/rabbitMQ.py b/bbit_ai/app/config/rabbitMQ.py index 33fa827..1cd7712 100644 --- a/bbit_ai/app/config/rabbitMQ.py +++ b/bbit_ai/app/config/rabbitMQ.py @@ -3,7 +3,19 @@ from utils.GlobalVariable import LOCAL_IP RABBIT_HOST = LOCAL_IP RABBIT_USER = "ai_lab" RABBIT_PASSWORD = "123456" -QUEUE_NAME = "analysis_queue" + + RABBIT_VHOST = "bbit_ai" +QUEUE_NAME = "analysis_queue" + SENTINEL_VHOST = "sentinel" +SENTINEL_ANALYSIS_ALL_QUEUE_NAME = "sentinel.analysis_all_queue" +SENTINEL_ANALYSIS_SIDE_QUEUE_NAME = "sentinel.analysis_side_queue" +SENTINEL_ANALYSIS_FRONT_QUEUE_NAME = "sentinel.analysis_front_queue" + +SENTINEL_FRONT_REQUEST_QUEUE = "sentinel.front_pic" + + +def get_sentinel_front_queue_name(device_id): + return f"{SENTINEL_FRONT_REQUEST_QUEUE}.{device_id}" diff --git a/bbit_ai/app/config/yolo.py b/bbit_ai/app/config/yolo.py index 742cd1f..ce2c4cb 100644 --- a/bbit_ai/app/config/yolo.py +++ b/bbit_ai/app/config/yolo.py @@ -99,7 +99,7 @@ class YOLOSingleton: except Exception as e: cls._model = None cls._ready = False - logger.error(f"❌ YOLO 模型加载失败: {e}") + logger.error(f"❌蚕茧 YOLO 模型加载失败: {e}") @classmethod def detect(cls, img_bytes: bytes): diff --git a/bbit_ai/app/db/postgres/annual_meeting.py b/bbit_ai/app/db/postgres/annual_meeting.py index b42c75e..0c97631 100644 --- a/bbit_ai/app/db/postgres/annual_meeting.py +++ b/bbit_ai/app/db/postgres/annual_meeting.py @@ -35,34 +35,46 @@ import time def reset_all_exchange_status(): - """将所有记录 is_finished 置为 False,并随机 position(以当前时间作为随机种子)""" + """将所有记录 is_finished 置为 False, + 且 gift_code == 2 的记录强制排在最后,其余随机排序 + """ with pg_pool.getConn() as conn: with conn.cursor() as cur: - # 获取总记录数 - cur.execute("SELECT id FROM annual_meeting_exchange") - ids = [row[0] for row in cur.fetchall()] + # 取出 id 和 gift_code + cur.execute("SELECT id, gift_code FROM annual_meeting_exchange") + rows = cur.fetchall() - # 用当前时间戳作为随机种子 - seed = int(time.time() * 1000) # 毫秒级 + # 分组 + normal_ids = [r[0] for r in rows if r[1] != 2] + tail_ids = [r[0] for r in rows if r[1] == 2] + + # 随机种子 + seed = int(time.time() * 1000) random.seed(seed) - # 生成随机顺序的 position - positions = list(range(1, len(ids) + 1)) - random.shuffle(positions) + # 只打乱非 gift_code == 2 的部分 + random.shuffle(normal_ids) - # 更新每条记录 - for record_id, pos in zip(ids, positions): + # 合并顺序:普通在前,gift_code==2 在后 + ordered_ids = normal_ids + tail_ids + + # 依次更新 sort + for idx, record_id in enumerate(ordered_ids, start=1): cur.execute( """ UPDATE annual_meeting_exchange SET is_finished = FALSE, sort = %s WHERE id = %s """, - (pos, record_id), + (idx, record_id), ) conn.commit() - return {"updated_count": len(ids), "seed_used": seed} + return { + "updated_count": len(ordered_ids), + "seed_used": seed, + "tail_count": len(tail_ids), + } def reset_user_status(target_user_id: str): diff --git a/bbit_ai/app/db/postgres/sentinel.py b/bbit_ai/app/db/postgres/sentinel.py index 1bb4d30..f0bd53d 100644 --- a/bbit_ai/app/db/postgres/sentinel.py +++ b/bbit_ai/app/db/postgres/sentinel.py @@ -1,6 +1,5 @@ from config.minIO import get_temp_url_dict from config.pgDb import pg_pool -from models.SentinelRecordRequest import SentinelRecordRequest from utils.MyUtils import format_datetime @@ -90,6 +89,7 @@ def get_sentinel_record_list_db_page( r.vehicle_image, r.livestock_type, r.livestock_source, + r.license_plate_color, r.is_inspected, r.dept_id, sd.name AS dept_name, @@ -116,6 +116,7 @@ def get_sentinel_record_list_db_page( vehicle_image, livestock_type, livestock_source, + license_plate_color, is_inspected, dept_id, dept_name, @@ -130,13 +131,14 @@ def get_sentinel_record_list_db_page( "license_plate": license_plate, "vehicle_type": vehicle_type, "license_plate_image": get_temp_url_dict( - "sentinel", "license_plate", license_plate_image + "sentinel", "vehicle_image_front", license_plate_image ), "vehicle_image": get_temp_url_dict( - "sentinel", "vehicle_image", vehicle_image + "sentinel", "vehicle_image_side", vehicle_image ), "livestock_type": livestock_type, "livestock_source": livestock_source, + "license_plate_color": license_plate_color, "is_inspected": 1 if is_inspected else 0, "dept_id": dept_id, "dept_name": dept_name, @@ -161,6 +163,7 @@ def get_sentinel_record_by_id(record_id): r.license_plate_image, r.vehicle_image, r.livestock_type, + r.license_plate_color, r.livestock_source, r.is_inspected, r.dept_id, @@ -188,6 +191,7 @@ def get_sentinel_record_by_id(record_id): license_plate_image, vehicle_image, livestock_type, + license_plate_color, livestock_source, is_inspected, dept_id, @@ -202,12 +206,13 @@ def get_sentinel_record_by_id(record_id): "license_plate": license_plate, "vehicle_type": vehicle_type, "license_plate_image": get_temp_url_dict( - "sentinel", "license_plate", license_plate_image + "sentinel", "vehicle_image_front", license_plate_image ), "vehicle_image": get_temp_url_dict( - "sentinel", "vehicle_image", vehicle_image + "sentinel", "vehicle_image_side", vehicle_image ), "livestock_type": livestock_type, + "license_plate_color": license_plate_color, "livestock_source": livestock_source, "is_inspected": 1 if is_inspected else 0, "dept_id": str(dept_id), @@ -333,15 +338,30 @@ def delete_sentinel_record_db(id: str) -> int: return cursor.rowcount -def saveSentinelRecord(data: SentinelRecordRequest) -> str: +def saveSentinelRecord( + id: str, + vehicle_type: str, + vehicle_image: str, + livestock_type: str, + remark: str, + dept_id: str, + license_plate: str, + license_plate_image: str, + license_plate_color: int = 0, +) -> str: sql = """ INSERT INTO sentinel_records ( + id, + vehicle_type, + vehicle_image, + livestock_type, + remark, + dept_id, license_plate, license_plate_image, - vehicle_type, - vehicle_image + license_plate_color ) - VALUES (%s, %s, %s, %s) + VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING id; """ @@ -350,36 +370,17 @@ def saveSentinelRecord(data: SentinelRecordRequest) -> str: cursor.execute( sql, ( - data.LicensePlate, - data.LicensePlateImage, - data.VehicleType, - data.VehicleImage, + id, + vehicle_type, + vehicle_image, + livestock_type, + remark, + dept_id, + license_plate, + license_plate_image, + license_plate_color, ), ) new_id = cursor.fetchone()[0] conn.commit() return str(new_id) - - -def update_sentinel_record( - id: str, livestock_type: str, remark: str, dept_id: str -) -> bool: - """ - 根据 id 更新 sentinel_records 表中的 livestock_type 和 dept_id - """ - sql = """ - UPDATE sentinel_records - SET livestock_type = %s, - remark = %s, - dept_id = %s, - updated_at = now() - WHERE id = %s - RETURNING id; - """ - - with pg_pool.getConn() as conn: - with conn.cursor() as cursor: - cursor.execute(sql, (livestock_type, remark, dept_id, id)) - record = cursor.fetchone() - conn.commit() - return record is not None diff --git a/bbit_ai/app/models/SentinelRecordFrontRequest.py b/bbit_ai/app/models/SentinelRecordFrontRequest.py new file mode 100644 index 0000000..2aec87a --- /dev/null +++ b/bbit_ai/app/models/SentinelRecordFrontRequest.py @@ -0,0 +1,7 @@ +from pydantic import BaseModel + + +class SentinelRecordFrontRequest(BaseModel): + Id: str | None = None + DeviceId: str + VehicleImage: str | None = None diff --git a/bbit_ai/app/models/SentinelRecordRequest.py b/bbit_ai/app/models/SentinelRecordRequest.py index 8cc941b..5186306 100644 --- a/bbit_ai/app/models/SentinelRecordRequest.py +++ b/bbit_ai/app/models/SentinelRecordRequest.py @@ -4,7 +4,6 @@ from pydantic import BaseModel class SentinelRecordRequest(BaseModel): Id: str | None = None DeviceId: str - LicensePlate: str | None = None - LicensePlateImage: str | None = None VehicleType: str | None = None - VehicleImage: str | None = None + vehicleImageFront: str | None = None + vehicleImageSide: str | None = None diff --git a/bbit_ai/app/models/SentinelRecordSideRequest.py b/bbit_ai/app/models/SentinelRecordSideRequest.py new file mode 100644 index 0000000..2ce65bb --- /dev/null +++ b/bbit_ai/app/models/SentinelRecordSideRequest.py @@ -0,0 +1,8 @@ +from pydantic import BaseModel + + +class SentinelRecordSideRequest(BaseModel): + Id: str | None = None + DeviceId: str + VehicleType: str | None = None + VehicleImage: str | None = None diff --git a/bbit_ai/app/result/e707cc77bd1a4342b63e30321556a184.jpg b/bbit_ai/app/result/e707cc77bd1a4342b63e30321556a184.jpg new file mode 100644 index 0000000000000000000000000000000000000000..78a9e6bcddc57a5a7c8b49434531a1b2ca299989 GIT binary patch literal 118735 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�?#n)hRaL%Zog!5b>Rzo~TEbdRp&PU4lH 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 KPZ&Jkb^brlXltJU literal 0 HcmV?d00001 diff --git a/bbit_ai/app/routers/Public.py b/bbit_ai/app/routers/Public.py index d39cb36..3cb8dc6 100644 --- a/bbit_ai/app/routers/Public.py +++ b/bbit_ai/app/routers/Public.py @@ -1,14 +1,16 @@ +import asyncio import base64 from fastapi import APIRouter from config.app import F8_SERVER_USER_ID -from db.postgres.sentinel import saveSentinelRecord from models.BaseResponse import BaseResponse from models.F8ImageRequest import F8ImageRequest from models.F8ImageRequestV2 import F8ImageRequestV2 from models.SentinelRecordRequest import SentinelRecordRequest -from service.RabbitMQ import sentinel_new_analysis +from service.RabbitMQ import ( + mq_client, +) from service.vision import ( process_ticket_image, process_license_image, @@ -85,8 +87,6 @@ async def recognize_silkworm_cocoon(data: F8ImageRequest): @publicRouter.post("/sentinel-record-analytics") async def delete_sentinel_record(data: SentinelRecordRequest): - # 保存部分数据到数据库 - data.Id = saveSentinelRecord(data) - # 发送请求给RabbitMQ - res = await sentinel_new_analysis(data) - return BaseResponse(data=res) + # 发送全盘分析请求给RabbitMQ + asyncio.create_task(mq_client.send_all_analysis(data)) + return BaseResponse(data="submitted") diff --git a/bbit_ai/app/routers/Sentinel.py b/bbit_ai/app/routers/Sentinel.py index 3777ecb..7f9cee3 100644 --- a/bbit_ai/app/routers/Sentinel.py +++ b/bbit_ai/app/routers/Sentinel.py @@ -107,22 +107,51 @@ async def get_sentinel_monitor_promotional_list( ): if not user_id: return {"error": "userId is required"} + # 图片过期时间:7天 + expiration_time = 60 * 60 * 24 * 7 return BaseResponse( data=[ { "id": 1, "remark": "人员公示及岗位职责", - "url": get_temp_url("sentinel", "promotional/promotional (2).jpg"), + "url": get_temp_url( + "sentinel", "promotional/promotional (2).jpg", expiration_time + ), }, { "id": 2, "remark": "入川动物监督检查工作流程图", - "url": get_temp_url("sentinel", "promotional/promotional (1).jpg"), + "url": get_temp_url( + "sentinel", "promotional/promotional (1).jpg", expiration_time + ), }, { "id": 3, - "remark": "四川省人民政府关于设立人川动物运输指定通道的通告", - "url": get_temp_url("sentinel", "promotional/promotional (3).jpg"), + "remark": "四川省人民政府关于设立入川动物运输指定通道的通告", + "url": get_temp_url( + "sentinel", "promotional/promotional (3).jpg", expiration_time + ), + }, + { + "id": 4, + "remark": "四川省动物卫生监督检查站工作程序", + "url": get_temp_url( + "sentinel", "promotional/promotional (4).jpg", expiration_time + ), + }, + { + "id": 5, + "remark": "四川省动物卫生监督检查站无害化处理制度", + "url": get_temp_url( + "sentinel", "promotional/promotional (5).jpg", expiration_time + ), + }, + { + "id": 6, + "remark": "四川省动物卫生监督检查站工作人员行为规范", + "url": get_temp_url( + "sentinel", "promotional/promotional (6).jpg", expiration_time + ), }, ] ) @@ -153,40 +182,48 @@ async def get_sentinel_monitor_list( ) url = "https://open.ys7.com/api/lapp/v2/live/address/get" - # device_serials = ["BG2493625"] - device_serials = ["BG2493625", "GH3713250", "GH3714496", "GH3714497"] + + device_serials = { + # "GG9175589": [1, 2, 3, 4], + "GG9175589": [1, 3], + "GG9175555": [1, 2], + } video_expire_time = 25 * 24 * 60 * 60 # 25 天 res = [] - for device_serial in device_serials: - live_key = f"ys7:live:{device_serial}" - cached_live = redis_client.get_value(live_key) + for device_serial, channels in device_serials.items(): + for channelNo in channels: + live_key = f"ys7:live:{device_serial}:{channelNo}" + cached_live = redis_client.get_value(live_key) + + if cached_live: + video_id = cached_live.get("id") + video_url = cached_live.get("url") + else: + payload = { + "accessToken": access_token, + "deviceSerial": device_serial, + "channelNo": channelNo, + "protocol": 4, + "expireTime": video_expire_time, + "supportH265": 0, + "quality": 2, + } + result = await http_client.post(url, data=payload) + + video_data = result.get("data") + if not video_data: + continue # 或者记录异常日志 + + video_id = video_data["id"] + video_url = video_data["url"] + + redis_client.set_value( + live_key, + {"id": video_id, "url": video_url}, + expire=video_expire_time, + ) + + res.append({"id": video_id, "url": video_url}) - if cached_live: - video_id = cached_live.get("id") - video_url = cached_live.get("url") - else: - payload = { - "accessToken": access_token, - "deviceSerial": device_serial, - "protocol": 4, # 流播放协议,1-ezopen、2-hls、3-rtmp、4-flv,默认为1 - "expireTime": video_expire_time, # 25天 - "supportH265": 0, - "quality": 2, - } - result = await http_client.post(url, data=payload) - video_id = result["data"]["id"] - video_url = result["data"]["url"] - # 存到 Redis,自动序列化为 JSON,过期 25天 - redis_client.set_value( - live_key, - {"id": video_id, "url": video_url}, - expire=video_expire_time, - ) - res.append( - { - "id": video_id, - "url": video_url, - } - ) return BaseResponse(data=res) diff --git a/bbit_ai/app/service/RabbitMQ.py b/bbit_ai/app/service/RabbitMQ.py index 826f956..b1a69f8 100644 --- a/bbit_ai/app/service/RabbitMQ.py +++ b/bbit_ai/app/service/RabbitMQ.py @@ -1,97 +1,103 @@ # consumer.py + import asyncio import json +import traceback import aio_pika from config.rabbitMQ import * -from models.AnalysisRequest import AnalysisRequest from models.SentinelRecordRequest import SentinelRecordRequest -from service.vision import process_vehicle_animal_image +from service.vision import ( + process_all_vehicle_animal_image, +) -async def mq_new_analysis_test(req: dict): - """将分析请求发送到 RabbitMQ 队列(异步版)""" - connection = await aio_pika.connect_robust( - f"amqp://{RABBIT_USER}:{RABBIT_PASSWORD}@{RABBIT_HOST}/{RABBIT_VHOST}" - ) +class MQClient: + """RabbitMQ 单例客户端,支持生产和消费""" - async with connection: - channel = await connection.channel() - # 声明队列,确保队列存在 - queue = await channel.declare_queue(QUEUE_NAME, durable=True) + _instance = None - message_body = json.dumps(req) + def __new__(cls): + if cls._instance is None: + cls._instance = super().__new__(cls) + return cls._instance + + def __init__(self): + self._connection = None + self._channel = None + self._consumer_tasks = [] + + # ---------------- 连接初始化 ---------------- + async def init(self, prefetch_count: int = 10): + """启动时初始化连接和通道""" + if self._connection is None: + self._connection = await aio_pika.connect_robust( + f"amqp://{RABBIT_USER}:{RABBIT_PASSWORD}@{RABBIT_HOST}/{SENTINEL_VHOST}" + ) + self._channel = await self._connection.channel() + await self._channel.set_qos(prefetch_count=prefetch_count) + + # ---------------- 发布消息 ---------------- + async def publish(self, queue_name: str, message_body: str): + """向指定队列发送消息""" + if self._channel is None: + raise RuntimeError("MQClient 未初始化") + # 队列幂等声明 + queue = await self._channel.declare_queue(queue_name, durable=True) message = aio_pika.Message( - body=message_body.encode(), - delivery_mode=aio_pika.DeliveryMode.PERSISTENT, # 持久化 + body=message_body.encode(), delivery_mode=aio_pika.DeliveryMode.PERSISTENT + ) + await self._channel.default_exchange.publish(message, routing_key=queue_name) + + async def send_all_analysis(self, req: SentinelRecordRequest): + await self.publish( + SENTINEL_ANALYSIS_ALL_QUEUE_NAME, json.dumps(req.model_dump()) ) - await channel.default_exchange.publish(message, routing_key=QUEUE_NAME) + # ---------------- 消费消息 ---------------- + async def consume_queue(self, queue_name: str, process_func): + """ + 持续消费队列 + process_func: async function 接收 dict 或 Request 对象 + """ + if self._channel is None: + raise RuntimeError("MQClient 未初始化") - -async def mq_pull_analysis_async_test(): - """ - 从队列拉取分析任务并处理 - process_func: 一个函数,接收 AnalysisRequest 对象处理分析逻辑 - """ - connection = await aio_pika.connect_robust( - f"amqp://{RABBIT_USER}:{RABBIT_PASSWORD}@{RABBIT_HOST}/{RABBIT_VHOST}" - ) - async with connection: - queue_name = QUEUE_NAME - channel = await connection.channel() - await channel.set_qos(prefetch_count=1) - queue = await channel.declare_queue(queue_name, durable=True) + queue = await self._channel.declare_queue(queue_name, durable=True) async with queue.iterator() as queue_iter: async for message in queue_iter: async with message.process(): - data = json.loads(message.body) - req = AnalysisRequest(**data) - print(f"收到任务: {req}") - await asyncio.sleep(5) # 模拟处理 - print(f"完成任务: {req}") + try: + body = message.body.decode() + data = json.loads(body) + await process_func(data) + except Exception as e: + print(f"[MQ Consume Error] {e}") + traceback.print_exc() + # ---------------- 启动全局分析消费者 ---------------- + async def start_all_consumer(self): + async def _process(data: dict): + req = SentinelRecordRequest(**data) + await process_all_vehicle_animal_image(req) + print(f"完成全局分析任务: {req}") -async def sentinel_new_analysis(req: SentinelRecordRequest): - """将分析请求发送到 RabbitMQ 队列(异步版)""" - connection = await aio_pika.connect_robust( - f"amqp://{RABBIT_USER}:{RABBIT_PASSWORD}@{RABBIT_HOST}/{SENTINEL_VHOST}" - ) - - async with connection: - channel = await connection.channel() - # 声明队列,确保队列存在 - queue = await channel.declare_queue(QUEUE_NAME, durable=True) - - message_body = json.dumps(req.model_dump()) - message = aio_pika.Message( - body=message_body.encode(), - delivery_mode=aio_pika.DeliveryMode.PERSISTENT, # 持久化 + task = asyncio.create_task( + self.consume_queue(SENTINEL_ANALYSIS_ALL_QUEUE_NAME, _process) ) + self._consumer_tasks.append(task) - await channel.default_exchange.publish(message, routing_key=QUEUE_NAME) + # ---------------- 关闭连接 ---------------- + async def close(self): + for task in self._consumer_tasks: + task.cancel() + if self._channel: + await self._channel.close() + if self._connection: + await self._connection.close() -async def sentinel_pull_analysis_async(): - """ - 从队列拉取分析任务并处理 - process_func: 一个函数,接收 AnalysisRequest 对象处理分析逻辑 - """ - connection = await aio_pika.connect_robust( - f"amqp://{RABBIT_USER}:{RABBIT_PASSWORD}@{RABBIT_HOST}/{SENTINEL_VHOST}" - ) - async with connection: - queue_name = QUEUE_NAME - channel = await connection.channel() - await channel.set_qos(prefetch_count=1) - queue = await channel.declare_queue(queue_name, durable=True) - - async with queue.iterator() as queue_iter: - async for message in queue_iter: - async with message.process(): - data = json.loads(message.body) - req = SentinelRecordRequest(**data) - await process_vehicle_animal_image(req) # 处理 - print(f"完成任务: {req}") +# ---------------- 全局单例 ---------------- +mq_client = MQClient() diff --git a/bbit_ai/app/service/vision.py b/bbit_ai/app/service/vision.py index 2f9f08c..6e03ba3 100644 --- a/bbit_ai/app/service/vision.py +++ b/bbit_ai/app/service/vision.py @@ -1,21 +1,27 @@ import uuid +from pathlib import Path from uuid import UUID import config.minIO as minIO import db.postgres as pg from agent.licenseImageAgent import get_license_response from agent.vehicleImageAgent import get_vehicle_response +from ai.plate.my_plate import recognizer from config.minIO import minio_client, get_temp_url from config.yolo import YOLOSingleton from db.postgres import ( get_dept_id_by_iot_user_name, get_dept_ids_by_dept_id, ) -from db.postgres.sentinel import update_sentinel_record, get_sentinel_record_by_id +from db.postgres.sentinel import ( + get_sentinel_record_by_id, + saveSentinelRecord, +) from llm.ticketLLM import * from llm.ticketLLMv2 import get_ticket_response_v2 from models.SentinelRecordRequest import SentinelRecordRequest from routers.WS import ws_manager +from utils import validate_plate def process_ticket_image( @@ -189,35 +195,97 @@ def process_silkworm_cocoon_image( } -async def process_vehicle_animal_image( +# 处理车牌照片 +async def process_all_vehicle_animal_image( data: SentinelRecordRequest, ): - # 通过设备id获得组织id - dept_id = get_dept_id_by_iot_user_name(data.DeviceId) + oss_url = minIO.get_temp_url( + "sentinel", "vehicle_image_side/" + data.vehicleImageSide + ) - # 得到动物类型 - oss_url = minIO.get_temp_url("sentinel", "vehicle_image/" + data.VehicleImage) + # LLM得到车身信息 analysis_result = await get_vehicle_response(oss_url) livestock_type = analysis_result.get("livestock_type", "") remark = analysis_result.get("remark", "") - # 保存到数据库 - update_sentinel_record(data.Id, livestock_type, remark, dept_id) - # 可以通知的部门ids - available_departments = get_dept_ids_by_dept_id(dept_id) + have_animal = analysis_result.get("have_animal", False) - # 通知控制界面 - await ws_manager.noticeSentinel( - { - "content": f"载有{livestock_type}的车辆即将进入关卡,请准备检查", - "type": "vehicle_alert", - }, - available_departments, - ) - # 通知大屏界面 - await ws_manager.noticeSentinelMonitorStatus( - { - "content": get_sentinel_record_by_id(data.Id), - "type": "vehicle_alert", - }, - available_departments, - ) + if not have_animal: + minIO.delete_file("sentinel", "vehicle_image_side/" + data.vehicleImageSide) + minIO.delete_file("sentinel", "vehicle_image_front/" + data.vehicleImageFront) + else: + # 通过设备id获得组织id + dept_id = get_dept_id_by_iot_user_name(data.DeviceId) + + # 处理汽车正面照-------------------------- + + # 从OSS下载 + oss_url = minIO.get_temp_url( + "sentinel", "vehicle_image_front/" + data.vehicleImageFront + ) + + # 获取系统临时目录(自动兼容 Windows / Linux) + tmp_dir = Path(tempfile.gettempdir()) + tmp_dir.mkdir(parents=True, exist_ok=True) + + tmp_path = tmp_dir / data.vehicleImageFront + + # 下载图片到 tmp_path + response = requests.get(oss_url, stream=True) + if response.status_code == 200: + with open(tmp_path, "wb") as f: + for chunk in response.iter_content(chunk_size=8192): + f.write(chunk) + else: + raise Exception(f"下载失败: {oss_url}, status_code={response.status_code}") + + # 调用识别 + results = recognizer.analyze_image(str(tmp_path)) + # 车牌号一次识别 + license_plate = results[0].plate_no if results else "" + # 车牌号二次校准 + license_plate = validate_plate(license_plate) + license_plate_color_str = results[0].plate_color if results else "" + color_map = { + "蓝色": 0, + "黄色": 1, + "绿色": 2, + "黑色": 3, + "白色": 4, + } + + license_plate_color = color_map.get(license_plate_color_str, 0) + license_plate_image = data.vehicleImageFront + # 保存到数据库 + saveSentinelRecord( + data.Id, + data.VehicleType, + data.vehicleImageSide, + livestock_type, + remark, + dept_id, + license_plate, + license_plate_image, + license_plate_color, + ) + # 识别完成后删除临时文件 + os.remove(tmp_path) + + # 可以通知的部门ids + available_departments = get_dept_ids_by_dept_id(dept_id) + # 通知控制界面 + await ws_manager.noticeSentinel( + { + "content": f"车辆即将进入关卡,请准备检查", + "license_plate": license_plate, + "type": "vehicle_alert", + }, + available_departments, + ) + # 通知大屏界面 + await ws_manager.noticeSentinelMonitorStatus( + { + "content": get_sentinel_record_by_id(data.Id), + "type": "vehicle_alert", + }, + available_departments, + ) diff --git a/bbit_ai/app/utils/MyUtils.py b/bbit_ai/app/utils/MyUtils.py index 2f62940..acb009d 100644 --- a/bbit_ai/app/utils/MyUtils.py +++ b/bbit_ai/app/utils/MyUtils.py @@ -100,3 +100,57 @@ def get_memory_total(): def get_disk_total(): return psutil.disk_usage("/").total + + +def translate_vehicle_type(vehicle_type: str) -> str: + mapping = { + "coupe": "双门跑车", + "largevehicle": "大型车辆", + "sedan": "三厢轿车", + "suv": "运动型多用途车", + "truck": "卡车", + "van": "面包车", + } + + return mapping.get(vehicle_type.lower(), vehicle_type) + + +import re + + +def validate_plate(plate: str) -> str: + if not plate: + return "车速过快,无法识别" + + plate = plate.strip().upper() + + # 普通车牌(燃油) + normal_pattern = r"^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼][A-Z][A-Z0-9]{5}$" + + # 新能源车 + new_energy_pattern = r"^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼][A-Z][A-Z0-9]{6}$" + + # 武警车 + wj_pattern = ( + r"^WJ[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼]?\d{5}$" + ) + + # 外交车 + diplomatic_pattern = r"^(使|领)\d{5}$" + + # 港澳车辆 + hk_macau_pattern = r"^粤Z[A-Z0-9]{4}(港|澳)$" + + patterns = [ + normal_pattern, + new_energy_pattern, + wj_pattern, + diplomatic_pattern, + hk_macau_pattern, + ] + + for p in patterns: + if re.match(p, plate): + return plate + + return "车速过快,无法识别"