diff --git a/face_rec.py b/face_rec.py index 4e654b6..8b2e2dd 100644 --- a/face_rec.py +++ b/face_rec.py @@ -508,176 +508,8 @@ class FaceRecognitionSystem: # Composite image.alpha_composite(shadow_layer) - def _build_qrcode_instruction_canvas(self, qr_image: np.ndarray, canvas_size: Tuple[int, int]) -> Optional[np.ndarray]: - try: - canvas_width, canvas_height = canvas_size - if canvas_width <= 0 or canvas_height <= 0: - return None - - # Colors - PRIMARY_COLOR = (23, 43, 77) # Dark Blue for headings - SECONDARY_COLOR = (94, 108, 132) # Grey for secondary text - ACCENT_COLOR = (0, 82, 204) # Bright Blue for highlights/numbers - - # 1. Background - bg = self._create_gradient_background((canvas_width, canvas_height), (245, 247, 250), (223, 225, 230)).convert("RGBA") - draw = ImageDraw.Draw(bg, "RGBA") - - # Layout Constants - MARGIN = 50 - GUTTER = 40 - - # Left Panel (QR Code) - 35% width approx - left_width = int((canvas_width - 2 * MARGIN - GUTTER) * 0.35) - # Ensure minimum width for QR code - left_width = max(left_width, 400) - right_width = canvas_width - 2 * MARGIN - GUTTER - left_width - - left_box = (MARGIN, MARGIN, MARGIN + left_width, canvas_height - MARGIN) - right_box = (MARGIN + left_width + GUTTER, MARGIN, canvas_width - MARGIN, canvas_height - MARGIN) - - # --- Draw Left Panel --- - # Shadow - self._draw_shadow(bg, left_box, radius=30, offset=(0, 10), blur=20, shadow_color=(0,0,0,30)) - # Card - self._draw_rounded_rect(draw, left_box, radius=30, fill=(255, 255, 255, 255)) - - # Left Content - cx = (left_box[0] + left_box[2]) // 2 - cy_top = left_box[1] + 120 - - # Title - text = "访客登记" - try: - w = draw.textlength(text, font=self.font_qr_title) - except: - w, _ = draw.textsize(text, font=self.font_qr_title) - draw.text((cx - w/2, cy_top), text, font=self.font_qr_title, fill=PRIMARY_COLOR) - - # Subtitle - text = "Visitor Registration" - try: - w = draw.textlength(text, font=self.font_qr_subtitle) - except: - w, _ = draw.textsize(text, font=self.font_qr_subtitle) - draw.text((cx - w/2, cy_top + 70), text, font=self.font_qr_subtitle, fill=SECONDARY_COLOR) - - # QR Code - qr_size = min(left_width - 100, 500) - qr_y = cy_top + 180 - - # Resize QR - qr_pil = Image.fromarray(cv2.cvtColor(qr_image, cv2.COLOR_BGR2RGB)) - qr_pil = qr_pil.resize((qr_size, qr_size), Image.LANCZOS) - bg.paste(qr_pil, (cx - qr_size//2, qr_y)) - - # Scan Hint - hint_y = qr_y + qr_size + 50 - hint_text = "请使用微信扫码登记" - try: - w = draw.textlength(hint_text, font=self.font_qr_step_title) - except: - w, _ = draw.textsize(hint_text, font=self.font_qr_step_title) - - # Draw a pill background for hint - pill_padding = 20 - pill_box = (cx - w/2 - pill_padding, hint_y - pill_padding, cx + w/2 + pill_padding, hint_y + 40 + pill_padding) - self._draw_rounded_rect(draw, pill_box, radius=30, fill=(240, 242, 245), outline=None) - draw.text((cx - w/2, hint_y), hint_text, font=self.font_qr_step_title, fill=ACCENT_COLOR) - - # --- Draw Right Panel --- - - # Right Title Area - Compacted - rt_y = MARGIN + 20 - draw.text((right_box[0], rt_y), self.qrcode_instruction_title, font=self.font_qr_title, fill=PRIMARY_COLOR) - draw.text((right_box[0], rt_y + 60), self.qrcode_instruction_subtitle, font=self.font_qr_subtitle, fill=SECONDARY_COLOR) - - # Separator Line - sep_y = rt_y + 110 - draw.line((right_box[0], sep_y, right_box[2], sep_y), fill=(200, 200, 200), width=2) - - # Grid Configuration - grid_y_start = sep_y + 40 - grid_width = right_width - cols = 2 - col_gap = 30 - row_gap = 30 - - col_width = (grid_width - (cols - 1) * col_gap) // cols - - # Pre-calculate text layout to find uniform height - max_lines = 0 - processed_steps = [] - - padding = 30 - badge_size = 50 - text_left_margin = badge_size + 20 - - # Calculate available width for text inside a card - text_max_width = col_width - padding * 2 - text_left_margin - - for i, step in enumerate(self.qrcode_instruction_steps): - # Remove "第一步:" etc prefix if present to make it cleaner, we have badges - clean_step = step.split(":", 1)[-1] if ":" in step else step - - lines = self._wrap_text_for_width(clean_step, self.font_qr_body, text_max_width, draw) - processed_steps.append(lines) - max_lines = max(max_lines, len(lines)) - - # Calculate Card Height - # Padding top + max_lines * line_height + Padding bottom - line_height = self.font_qr_body.size + 10 - card_content_height = max(badge_size, max_lines * line_height) - uniform_card_height = int(padding * 2 + card_content_height) - - # Draw Grid - for idx, lines in enumerate(processed_steps): - row = idx // cols - col = idx % cols - - x = right_box[0] + col * (col_width + col_gap) - y = grid_y_start + row * (uniform_card_height + row_gap) - - # Ensure we don't go out of bounds - if y + uniform_card_height > canvas_height: - break - - card_box = (x, y, x + col_width, y + uniform_card_height) - - # Card Shadow - self._draw_shadow(bg, card_box, radius=20, offset=(0, 4), blur=12, shadow_color=(0,0,0,15)) - # Card Body - self._draw_rounded_rect(draw, card_box, radius=20, fill=(255, 255, 255)) - - # Badge (Step Number) - bx = x + padding - by = y + padding - draw.ellipse((bx, by, bx + badge_size, by + badge_size), fill=ACCENT_COLOR) - - num_text = str(idx + 1) - try: - nw = draw.textlength(num_text, font=self.font_qr_badge) - except: - nw, _ = draw.textsize(num_text, font=self.font_qr_badge) - # Center number - draw.text((bx + (badge_size - nw)/2, by + (badge_size - self.font_qr_badge.size)/2 - 2), - num_text, font=self.font_qr_badge, fill=(255, 255, 255)) - - # Text - tx = x + padding + text_left_margin - ty = y + padding - - for line in lines: - draw.text((tx, ty), line, font=self.font_qr_body, fill=PRIMARY_COLOR) - ty += line_height - - return cv2.cvtColor(np.array(bg.convert("RGB")), cv2.COLOR_RGB2BGR) - except Exception as e: - self.logger.error(f"渲染二维码指引面板失败: {e}") - return None - def _show_qrcode(self): - """显示二维码图片""" + """显示二维码图片 - 直接全屏铺满""" try: if not os.path.exists(self.qrcode_image_path): self.logger.error(f"二维码图片不存在: {self.qrcode_image_path}") @@ -689,48 +521,20 @@ class FaceRecognitionSystem: self.logger.error(f"无法读取二维码图片: {self.qrcode_image_path}") return False - # 获取二维码图片尺寸 - qr_height, qr_width = qr_image.shape[:2] - - # 使用屏幕尺寸,按比例缩放图片 + # 直接铺满全屏 canvas_width = self.screen_width canvas_height = self.screen_height - - # 等比例缩放:保持图片宽高比 - scale = min(canvas_width / qr_width, canvas_height / qr_height) - scaled_width = int(qr_width * scale) - scaled_height = int(qr_height * scale) - - # 缩放二维码图片 - qr_image_scaled = cv2.resize(qr_image, (scaled_width, scaled_height), interpolation=cv2.INTER_LINEAR) + qr_image_scaled = cv2.resize(qr_image, (canvas_width, canvas_height), interpolation=cv2.INTER_LINEAR) # 创建二维码窗口 cv2.namedWindow(self.qrcode_window_name, cv2.WINDOW_NORMAL | cv2.WINDOW_GUI_NORMAL) - - x_pos = 0 - y_pos = 0 - - rendered = self._build_qrcode_instruction_canvas(qr_image_scaled, (canvas_width, canvas_height)) - if rendered is None: - # 回退到原二维码展示 - resized_qr = qr_image_scaled - - # 创建背景画布 - rendered = np.zeros((canvas_height, canvas_width, 3), dtype=np.uint8) - - # 计算居中位置 - x_offset = (canvas_width - scaled_width) // 2 - y_offset = (canvas_height - scaled_height) // 2 - - rendered[y_offset:y_offset+scaled_height, x_offset:x_offset+scaled_width] = resized_qr - cv2.resizeWindow(self.qrcode_window_name, canvas_width, canvas_height) - cv2.moveWindow(self.qrcode_window_name, x_pos, y_pos) + cv2.moveWindow(self.qrcode_window_name, 0, 0) # 设置窗口全屏显示 cv2.setWindowProperty(self.qrcode_window_name, cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN) - cv2.imshow(self.qrcode_window_name, rendered) + cv2.imshow(self.qrcode_window_name, qr_image_scaled) # 设置窗口置顶 cv2.setWindowProperty(self.qrcode_window_name, cv2.WND_PROP_TOPMOST, 1) diff --git a/preview_qrcode.py b/preview_qrcode.py deleted file mode 100644 index 5c6f1f5..0000000 --- a/preview_qrcode.py +++ /dev/null @@ -1,333 +0,0 @@ -import math -import os -import cv2 -import yaml -import numpy as np -from PIL import Image, ImageDraw, ImageFont, ImageFilter - -# Canvas Configuration -CANVAS_SIZE = (1920, 1080) -BG_COLOR_START = (240, 242, 245) -BG_COLOR_END = (200, 210, 230) - -# Colors -PRIMARY_COLOR = (23, 43, 77) # Dark Blue for headings -SECONDARY_COLOR = (94, 108, 132) # Grey for secondary text -ACCENT_COLOR = (0, 82, 204) # Bright Blue for highlights/numbers -CARD_BG_COLOR = (255, 255, 255) -CARD_SHADOW_COLOR = (9, 30, 66, 40) # Shadow color - -INSTRUCTION_TITLE = "访客预约流程" -INSTRUCTION_SUBTITLE = "Visitor Registration Process" -INSTRUCTION_STEPS = [ - "第一步:扫码关注“康达新材”公众号。", - "第二步:点击【关于我们】→【我是访客】进入“访客注册”界面,填写并上传相应信息并点击“提交”。", - "第三步:将第二步信息提交完毕后在“访客预约”界面选择右下角的“+”号按钮,填写预约信息。", - "第四步:请仔细阅读安全告知书,点击我知道了。", - "第五步:填写被访人信息及来访事由等内容并提交。", - "第六步:显示提交成功,并且手机、微信会收到预约相关短信通知。", -] - -def load_config(path="config.yaml"): - try: - with open(path, "r", encoding="utf-8") as f: - return yaml.safe_load(f) - except FileNotFoundError: - return {} - -def resolve_font(size: int, preferred: str | None = None) -> ImageFont.FreeTypeFont: - candidates = [preferred] if preferred else [] - candidates += [ - "C:/Windows/Fonts/msyhbd.ttc", # Microsoft YaHei Bold - "C:/Windows/Fonts/msyh.ttc", # Microsoft YaHei - "C:/Windows/Fonts/simhei.ttf", - "/usr/share/fonts/truetype/noto/NotoSansCJK-Bold.ttc", - "/usr/share/fonts/truetype/noto/NotoSansCJK-Regular.ttc", - "/usr/share/fonts/truetype/noto/NotoMono-Regular.ttf", - ] - for path in candidates: - if path and os.path.exists(path): - try: - return ImageFont.truetype(path, size) - except OSError: - continue - return ImageFont.load_default() - -def wrap_text(text: str, font: ImageFont.FreeTypeFont, max_width: int, draw: ImageDraw.ImageDraw): - lines = [] - current = "" - for ch in text: - tentative = current + ch - # Pillow 10+ uses textlength, older uses textsize - try: - width = draw.textlength(tentative, font=font) - except AttributeError: - width, _ = draw.textsize(tentative, font=font) - - if width <= max_width: - current = tentative - else: - if current: - lines.append(current) - current = ch - if current: - lines.append(current) - return lines - -def create_gradient_background(size: tuple[int, int], start_color: tuple[int, int, int], end_color: tuple[int, int, int]) -> Image.Image: - width, height = size - # Vertical gradient - base = Image.new('RGB', size, start_color) - top = Image.new('RGB', size, end_color) - mask = Image.new('L', size) - mask_data = np.tile(np.linspace(0, 255, height, dtype=np.uint8), (width, 1)).T - mask = Image.fromarray(mask_data, 'L') - return Image.composite(top, base, mask) - -def draw_rounded_rect(draw, box, radius, fill, outline=None, width=0): - draw.rounded_rectangle(box, radius=radius, fill=fill, outline=outline, width=width) - -def draw_shadow(image, box, radius, offset=(0, 4), blur=10, shadow_color=(0,0,0,50)): - # Create a separate shadow layer - shadow_layer = Image.new('RGBA', image.size, (0,0,0,0)) - shadow_draw = ImageDraw.Draw(shadow_layer) - - sx0, sy0, sx1, sy1 = box - dx, dy = offset - - shadow_box = (sx0 + dx, sy0 + dy, sx1 + dx, sy1 + dy) - shadow_draw.rounded_rectangle(shadow_box, radius=radius, fill=shadow_color) - - # Blur the shadow - shadow_layer = shadow_layer.filter(ImageFilter.GaussianBlur(blur)) - - # Composite - image.alpha_composite(shadow_layer) - -def build_canvas(qr_image: np.ndarray, title_font, subtitle_font, body_font, step_title_font, badge_font) -> np.ndarray: - canvas_width, canvas_height = CANVAS_SIZE - - # 1. Background - bg = create_gradient_background(CANVAS_SIZE, (245, 247, 250), (223, 225, 230)).convert("RGBA") - - draw = ImageDraw.Draw(bg, "RGBA") - - # Layout Constants - MARGIN = 50 - GUTTER = 40 - - # Left Panel (QR Code) - 35% width approx - left_width = int((canvas_width - 2 * MARGIN - GUTTER) * 0.35) - right_width = canvas_width - 2 * MARGIN - GUTTER - left_width - - left_box = (MARGIN, MARGIN, MARGIN + left_width, canvas_height - MARGIN) - right_box = (MARGIN + left_width + GUTTER, MARGIN, canvas_width - MARGIN, canvas_height - MARGIN) - - # --- Draw Left Panel --- - # Shadow - draw_shadow(bg, left_box, radius=30, offset=(0, 10), blur=20, shadow_color=(0,0,0,30)) - # Card - draw_rounded_rect(draw, left_box, radius=30, fill=(255, 255, 255, 255)) - - # Left Content - cx = (left_box[0] + left_box[2]) // 2 - cy_top = left_box[1] + 120 - - # Title - text = "访客登记" - try: - w = draw.textlength(text, font=title_font) - except: - w, _ = draw.textsize(text, font=title_font) - draw.text((cx - w/2, cy_top), text, font=title_font, fill=PRIMARY_COLOR) - - # Subtitle - text = "Visitor Registration" - try: - w = draw.textlength(text, font=subtitle_font) - except: - w, _ = draw.textsize(text, font=subtitle_font) - draw.text((cx - w/2, cy_top + 70), text, font=subtitle_font, fill=SECONDARY_COLOR) - - # QR Code - qr_size = min(left_width - 100, 500) - qr_y = cy_top + 180 - - # Resize QR - qr_pil = Image.fromarray(cv2.cvtColor(qr_image, cv2.COLOR_BGR2RGB)) - qr_pil = qr_pil.resize((qr_size, qr_size), Image.LANCZOS) - bg.paste(qr_pil, (cx - qr_size//2, qr_y)) - - # Scan Hint - hint_y = qr_y + qr_size + 50 - hint_text = "请使用微信扫码登记" - try: - w = draw.textlength(hint_text, font=step_title_font) - except: - w, _ = draw.textsize(hint_text, font=step_title_font) - - # Draw a pill background for hint - pill_padding = 20 - pill_box = (cx - w/2 - pill_padding, hint_y - pill_padding, cx + w/2 + pill_padding, hint_y + 40 + pill_padding) - draw.rounded_rectangle(pill_box, radius=30, fill=(240, 242, 245), outline=None) - draw.text((cx - w/2, hint_y), hint_text, font=step_title_font, fill=ACCENT_COLOR) - - # --- Draw Right Panel --- - - # Right Title Area - Compacted - rt_y = MARGIN + 20 - draw.text((right_box[0], rt_y), INSTRUCTION_TITLE, font=title_font, fill=PRIMARY_COLOR) - draw.text((right_box[0], rt_y + 60), INSTRUCTION_SUBTITLE, font=subtitle_font, fill=SECONDARY_COLOR) - - # Separator Line - sep_y = rt_y + 110 - draw.line((right_box[0], sep_y, right_box[2], sep_y), fill=(200, 200, 200), width=2) - - # Grid Configuration - grid_y_start = sep_y + 40 - grid_width = right_width - cols = 2 - col_gap = 30 - row_gap = 30 - - col_width = (grid_width - (cols - 1) * col_gap) // cols - - # Pre-calculate text layout to find uniform height - max_lines = 0 - processed_steps = [] - - padding = 30 - badge_size = 50 - text_left_margin = badge_size + 20 - - # Calculate available width for text inside a card - text_max_width = col_width - padding * 2 - text_left_margin - - for i, step in enumerate(INSTRUCTION_STEPS): - # Remove "第一步:" etc prefix if present to make it cleaner, we have badges - clean_step = step.split(":", 1)[-1] if ":" in step else step - - lines = wrap_text(clean_step, body_font, text_max_width, draw) - processed_steps.append(lines) - max_lines = max(max_lines, len(lines)) - - # Calculate Card Height - # Padding top + max_lines * line_height + Padding bottom - line_height = body_font.size + 10 - card_content_height = max(badge_size, max_lines * line_height) - uniform_card_height = int(padding * 2 + card_content_height) - - # Check if we overflow canvas height - total_grid_height = 3 * uniform_card_height + 2 * row_gap - if grid_y_start + total_grid_height > canvas_height - MARGIN: - print(f"Warning: Content might overflow vertically. Required: {grid_y_start + total_grid_height}, Available: {canvas_height - MARGIN}") - # Dynamic adjustment if needed (e.g. reduce gaps) but for now we rely on the font resizing done in main() - - # Draw Grid - for idx, lines in enumerate(processed_steps): - row = idx // cols - col = idx % cols - - x = right_box[0] + col * (col_width + col_gap) - y = grid_y_start + row * (uniform_card_height + row_gap) - - card_box = (x, y, x + col_width, y + uniform_card_height) - - # Card Shadow - draw_shadow(bg, card_box, radius=20, offset=(0, 4), blur=12, shadow_color=(0,0,0,15)) - # Card Body - draw_rounded_rect(draw, card_box, radius=20, fill=(255, 255, 255)) - - # Badge (Step Number) - bx = x + padding - by = y + padding - draw.ellipse((bx, by, bx + badge_size, by + badge_size), fill=ACCENT_COLOR) - - num_text = str(idx + 1) - try: - nw = draw.textlength(num_text, font=badge_font) - except: - nw, _ = draw.textsize(num_text, font=badge_font) - # Center number - draw.text((bx + (badge_size - nw)/2, by + (badge_size - badge_font.size)/2 - 2), - num_text, font=badge_font, fill=(255, 255, 255)) - - # Text - tx = x + padding + text_left_margin - ty = y + padding - - for line in lines: - draw.text((tx, ty), line, font=body_font, fill=PRIMARY_COLOR) - ty += line_height - - return cv2.cvtColor(np.array(bg.convert("RGB")), cv2.COLOR_RGB2BGR) - -def create_mock_qrcode(size: int = 640) -> np.ndarray: - img = np.full((size, size, 3), 255, dtype=np.uint8) # White background - - # Draw some patterns - block = size // 15 - for y in range(0, size, block): - for x in range(0, size, block): - if (x // block + y // block) % 2 == 0: - color = (0, 0, 0) - # Corner markers - if (x < 3*block and y < 3*block) or (x > size-4*block and y < 3*block) or (x < 3*block and y > size-4*block): - color = (0, 0, 0) - elif np.random.rand() > 0.3: - color = (0, 0, 0) - else: - color = (255, 255, 255) - - cv2.rectangle(img, (x, y), (x + block, y + block), color, -1) - - # Borders for markers - marker_len = 3 * block - thickness = block - # Top Left - cv2.rectangle(img, (0,0), (marker_len, marker_len), (0,0,0), thickness) - # Top Right - cv2.rectangle(img, (size-marker_len,0), (size, marker_len), (0,0,0), thickness) - # Bottom Left - cv2.rectangle(img, (0,size-marker_len), (marker_len, size), (0,0,0), thickness) - - return img - -def main(): - config = load_config() - preferred_font = config.get("display", {}).get("font_path") if config else None - - # Define Fonts - Adjusted sizes for better fit - # Title for left/right headers - title_font = resolve_font(56, preferred_font) - # Subtitles - subtitle_font = resolve_font(30, preferred_font) - # Step text - Reduced to ensure fit - body_font = resolve_font(24, preferred_font) - # Step titles or emphasis - step_title_font = resolve_font(28, preferred_font) - # Badge numbers - badge_font = resolve_font(30, preferred_font) - - # Mock data - mock_qr = create_mock_qrcode(500) - - # Build - canvas = build_canvas(mock_qr, title_font, subtitle_font, body_font, step_title_font, badge_font) - - # Display - window_name = "Visitor Registration Preview" - cv2.namedWindow(window_name, cv2.WINDOW_NORMAL) - cv2.resizeWindow(window_name, 1280, 720) - cv2.imshow(window_name, canvas) - - print("Displaying preview. Press any key to exit.") - cv2.waitKey(0) - cv2.destroyAllWindows() - - # Save for verification - cv2.imwrite("preview_qrcode_sota.png", canvas) - print("Saved preview to preview_qrcode_sota.png") - -if __name__ == "__main__": - main()