本帖最后由 云天 于 2026-3-17 09:41 编辑
【项目简介】
你是否想过拥有一台能“看懂”你、并为你绘制素描人像的机器?本项目利用“行空板M10”作为主控,连接“二哈识图2 AI视觉传感器”进行人脸检测与定位,通过语音引导用户调整至最佳拍摄位置。然后,利用二哈2的实时图传功能获取人脸图像,上传至“SiliconFlow API”生成素描风格图片,最后通过“嵌入式热敏打印机”将素描打印出来。整个过程充满互动性与科技感,适合作为AIoT跨学科教学案例。
【硬件清单】
【硬件接线】
1.二哈2 → 行空板M10 (I2C接口)
二哈2 (4Pin Gravity接口) | 行空板M10 (I2C接口) | | 红线 (VCC) | 3.3V (或5V,取决于二哈供电需求) | | 黑线 (GND) | GND | | 蓝线 (SDA) | SDA | | 绿线 (SCL) | SCL |
注:行空板M10的I2C接口位于板载4Pin接口上。
2.热敏打印机 → USB转TTL模块 → 行空板M10
由于打印机使用TTL电平串口,而行空板USB-A口可直接识别USB转TTL模块,接线如下:
打印机 (TTL接口) | USB转TTL模块 | | TXD (黑线) | RXD | | RXD (蓝线) | TXD | | GND (绿线) | GND | | DTR (红线) | 悬空 |
然后将USB转TTL模块插入行空板的**USB-A口**。在行空板系统中,该模块通常被识别为`/dev/ttyUSB0`。
3.打印机独立供电
务必使用9-24V/2.5A以上的电源为打印机单独供电,打印图像时电流可达2.5A,仅靠USB供电无法驱动。
【结构设计】
【核心原理与代码解析】
1.人脸检测与引导逻辑
二哈2通过I2C实时返回人脸框的坐标和宽度。程序根据人脸在画面中的位置(`xCenter`, `yCenter`)和大小(`width`),通过语音模块播放相应的提示音,引导用户调整,确保人脸处于最佳拍摄区域。
- # 核心判断逻辑示例
- if xCenter > 750:
- huskylens.playMusic("right.mp3", 100) # 请向右一点
- elif xCenter < 300:
- huskylens.playMusic("left.mp3", 100) # 请向左一点
- # ... 其他方向与距离判断
复制代码
2. 图像采集与AI素描生成
当人脸位置合适时,程序利用OpenCV通过二哈2的RTSP图传地址(如`rtsp://192.168.31.180:8554/live`)抓取一帧图像。然后调用“SiliconFlow API”,将图像上传并请求生成素描风格图片。这里选择支持图像输入的模型(如`Qwen/Qwen-Image-Edit-2509`),并通过提示词引导生成“铅笔素描画”。
3.热敏打印技术要点
打印机使用ESC/POS指令集。打印图像需使用 `GS v 0` 命令,将处理好的黑白位图数据发送给打印机。
图像处理:将生成的彩色素描图缩放到打印机宽度384像素,转为灰度图,再二值化为纯黑白(1位位图)。注意:黑色像素(值为0)对应打印点。
数据编码:每行384点需编码为48字节,每字节的8个位对应一行中的8个连续像素。
模式切换:务必在打印前发送切换小票模式的指令(`0x1F 0x2F 0x0B 0x00 0x01 0x00 0x00`)和初始化命令(`ESC @`)。
- # 图像数据编码核心片段
- for y in range(img_height):
- for x_byte in range(48):
- byte_val = 0
- for bit in range(8):
- x = x_byte * 8 + bit
- if binary[y, x] == 0: # 黑色像素
- byte_val |= (1 << (7 - bit))
- image_data.append(byte_val)
复制代码
【完整代码】
获取硅基流动API:用于根据生成图片。通过第三方的硅基流动注册获取API,如方便注册,使用我的邀请码注册:https://cloud.siliconflow.cn/i/KwyEBX3e,邀请码:KwyEBX3e。共同获取免费额度。如果不方便注册,可使用我的API:sk-kxwsrzianqfxsebnihblrgyyytrrtgvvdjvdiujcuvwymrfp。
主要结构如下:
1. 初始化:导入库、初始化行空板、二哈2和打印机串口。
2. 辅助函数:`generate_sketch_from_face()` 调用API生成素描;`print_image_to_printer()` 处理图像并发送打印指令。
3. 主循环:持续获取人脸数据,判断位置并语音引导;条件满足时抓图、生成素描、显示并打印。
- # -*- coding: UTF-8 -*-
- import sys
- sys.path.append("/root/mindplus/.lib/thirdExtension/dfrobot-huskylensv2-thirdex")
- import cv2
- import time
- import base64
- import requests
- import numpy as np
- import serial
- from pinpong.board import Board
- from pinpong.extension.unihiker import *
- from dfrobot_huskylensv2 import *
- from unihiker import GUI
- runtime = time.time()
- # ==================== 配置区域 ====================
- # SiliconFlow API 配置
- API_KEY = "sk-******************************************************" # 替换为你的有效密钥
- API_URL = "https://api.siliconflow.cn/v1/images/generations"
- MODEL_NAME = "Qwen/Qwen-Image-Edit-2509" # 需支持图像输入的模型
- HEADERS = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}
-
- # 热敏打印机串口配置(通过USB转TTL模块连接)
- PRINTER_PORT = "/dev/ttyUSB0" # Unihiker上通常为 /dev/ttyUSB0
- PRINTER_BAUDRATE = 115200
- # =================================================
- u_gui=GUI()
- background=u_gui.draw_image(image="logo.png",x=0,y=0)
- # 初始化PinPong板子和HuskyLens
- Board().begin()
- print("系统初始化...")
- huskylens = HuskylensV2_I2C()
- huskylens.knock()
- huskylens.switchAlgorithm(ALGORITHM_FACE_RECOGNITION)
- time.sleep(5)
-
- # 初始化打印机串口
- try:
- printer_serial = serial.Serial(PRINTER_PORT, PRINTER_BAUDRATE, timeout=1)
- time.sleep(2) # 等待打印机就绪
- print("打印机串口已打开")
- except Exception as e:
- print(f"无法打开打印机串口: {e}")
- printer_serial = None
-
- # ==================== 辅助函数 ====================
- def generate_sketch_from_face(image_cv2):
- """
- 将OpenCV图像(BGR格式)发送给SiliconFlow API生成素描画。
- 返回: 成功返回素描图(cv2格式),失败返回None。
- """
- if image_cv2 is None:
- return None
-
- # 1. 图像编码为JPEG + Base64
- ret, buffer = cv2.imencode('.jpg', image_cv2)
- if not ret:
- print("图像编码失败")
- return None
- base64_image = base64.b64encode(buffer).decode('utf-8')
- data_uri = f"data:image/jpeg;base64,{base64_image}"
-
- # 2. 构造请求载荷
- payload = {
- "model": MODEL_NAME,
- "prompt": "将这张人脸照片变成一幅精美的铅笔素描画,黑白线条,高对比度",
- "image": data_uri,
- "image_size": "1472x1140"
- }
-
- # 3. 发送请求
- try:
- print("正在请求SiliconFlow API生成素描...")
- response = requests.post(API_URL, headers=HEADERS, json=payload, timeout=30)
- response.raise_for_status()
- result = response.json()
-
- # 4. 解析返回的图片URL
- if result.get('images') and len(result['images']) > 0:
- image_url = result['images'][0]['url']
- print(f"素描图生成成功,临时URL: {image_url}")
-
- # 5. 下载图片
- img_resp = requests.get(image_url, timeout=15)
- img_resp.raise_for_status()
- img_array = np.frombuffer(img_resp.content, dtype=np.uint8)
- sketch_img = cv2.imdecode(img_array, cv2.IMREAD_COLOR)
- return sketch_img
- else:
- print(f"API返回异常: {result}")
- return None
- except Exception as e:
- print(f"API请求失败: {e}")
- return None
-
- def print_image_to_printer(image_cv2, printer_serial):
- if image_cv2 is None or printer_serial is None:
- print("图像或打印机串口无效")
- return
-
- # 切换小票模式(可选)
- # printer_serial.write(bytes([0x1F, 0x2F, 0x0B, 0x00, 0x01, 0x00, 0x00]))
- time.sleep(0.2)
-
- # 初始化打印机
- printer_serial.write(b'\x1B\x40') # ESC @
- time.sleep(0.1)
-
- target_width = 384 # 有效打印宽度384点(48mm)
- h, w = image_cv2.shape[:2]
- target_height = int(h * (target_width / w))
- print(f"原始 {w}x{h} -> 目标 {target_width}x{target_height}")
-
- resized = cv2.resize(image_cv2, (target_width, target_height))
- gray = cv2.cvtColor(resized, cv2.COLOR_BGR2GRAY)
- _, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
- cv2.imwrite("debug_binary.png", binary)
-
- img_height = binary.shape[0]
- x_bytes = (target_width + 7) // 8 # 每行字节数
- image_data = bytearray()
-
- for y in range(img_height):
- for x_byte in range(x_bytes):
- byte_val = 0
- for bit in range(8):
- x = x_byte * 8 + bit
- if x < target_width and binary[y, x] == 0: # 黑色像素
- byte_val |= (1 << (7 - bit))
- image_data.append(byte_val)
-
- # 构建GS v 0命令头
- mode = 0x00 # 普通模式
- xL = x_bytes & 0xFF
- xH = (x_bytes >> 8) & 0xFF
- yL = img_height & 0xFF
- yH = (img_height >> 8) & 0xFF
-
- print(f"发送高度: {img_height} 点, 每行 {x_bytes} 字节")
- print(f"命令参数: xL={xL}, xH={xH}, yL={yL}, yH={yH}")
-
- # 发送指令
- try:
- printer_serial.write(bytes([0x1D, 0x76, 0x30, mode, xL, xH, yL, yH]))
- printer_serial.write(image_data)
- printer_serial.flush()
- print("图像数据发送完成")
- except Exception as e:
- print(f"发送失败: {e}")
-
- # ==================== 主循环 ====================
- huskylens.playMusic("pic.mp3", 100)
- print("进入主循环,等待人脸...")
-
- while True:
- time.sleep(0.5)
- huskylens.getResult(ALGORITHM_FACE_RECOGNITION)
- if huskylens.available(ALGORITHM_FACE_RECOGNITION):
- print("检测到人脸",end="")
- print(huskylens.getCachedCenterResult(ALGORITHM_FACE_RECOGNITION).width)
- if (time.time() - runtime) > 5: # 每5秒处理一次
- if(huskylens.getCachedCenterResult(ALGORITHM_FACE_RECOGNITION).xCenter>750):
- huskylens.playMusic("right.mp3", 100)#播报语音:请向右一点
- time.sleep(3)
- elif(huskylens.getCachedCenterResult(ALGORITHM_FACE_RECOGNITION).xCenter<300):
- huskylens.playMusic("left.mp3", 100)#播报语音:请向左一点
- time.sleep(3)
- elif(huskylens.getCachedCenterResult(ALGORITHM_FACE_RECOGNITION).yCenter<250):
- huskylens.playMusic("down.mp3", 100)#播报语音:请向下一点
- time.sleep(3)
- elif(huskylens.getCachedCenterResult(ALGORITHM_FACE_RECOGNITION).yCenter>450):
- huskylens.playMusic("up.mp3", 100)#播报语音:请向上一点
- time.sleep(3)
- elif(huskylens.getCachedCenterResult(ALGORITHM_FACE_RECOGNITION).width>250):
- huskylens.playMusic("far.mp3", 100)#播报语音:请离远一点
- time.sleep(3)
- elif(huskylens.getCachedCenterResult(ALGORITHM_FACE_RECOGNITION).width<150):
- huskylens.playMusic("near.mp3", 100)#播报语音:请离近一点
- time.sleep(3)
- else:
- runtime = time.time()
- buzzer.pitch(131, 4)# 蜂鸣提示
- huskylens.playMusic("wait.mp3", 100)#播报语音:请保持,正在拍照中
- # 1. 从RTSP流抓取一帧图像
- vd = cv2.VideoCapture("rtsp://192.168.31.180:8554/live")
- if not vd.isOpened():
- print("无法打开RTSP流")
- continue
-
- ret, frame = vd.read()
- vd.release()
- if not ret or frame is None:
- print("获取图像失败")
- continue
- huskylens.playMusic("running.mp3", 100)#播报语音:拍照完成,正在生成画像中
- # 2. 生成素描图
- sketch = generate_sketch_from_face(frame)
-
- # 3. 显示并打印
- if sketch is not None:
- print("成功获取素描图,准备显示...")
- cv2.namedWindow("Sketch", cv2.WINDOW_NORMAL)
- cv2.setWindowProperty("Sketch", cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN)
- cv2.imshow("Sketch", sketch)
- cv2.waitKey(1) # 必须加waitKey才能显示
- huskylens.playMusic("print.mp3", 100)#播报语音:画像打印中
- # 打印素描图
- if printer_serial:
- print_image_to_printer(sketch, printer_serial)
-
- # 保持显示5秒
- time.sleep(5)
- cv2.destroyAllWindows()
- vd.release()
- else:
- print("素描生成失败,显示原始图像")
- cv2.namedWindow("Original", cv2.WINDOW_NORMAL)
- cv2.setWindowProperty("Original", cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN)
- cv2.imshow("Original", frame)
- cv2.waitKey(1)
- time.sleep(5)
- cv2.destroyAllWindows()
- vd.release()
- runtime = time.time()
复制代码
【运行效果】
1. 开机:行空板屏幕显示Logo,程序启动,二哈2开始检测人脸。
2. 引导:当用户出现在镜头前,装置会根据人脸位置和大小播放语音(如“请向左一点”、“请离近一点”),引导用户至最佳拍摄点。
3. 拍摄与生成:位置合适后,蜂鸣器提示,进入拍照状态。屏幕短暂显示“正在生成画像”,同时调用云端API绘制素描。
4. 打印:素描生成后,屏幕全屏显示,同时打印机开始打印。约10-20秒后,一张专属的AI素描画像便从打印机中缓缓吐出。
【项目总结与扩展】
本项目成功地将“边缘AI计算”(二哈2)、“云端AIGC”(SiliconFlow)和“物理输出”(热敏打印)结合在一起,创造了一个完整的互动艺术装置。它展示了如何利用行空板M10强大的Python生态,快速整合多种外设与在线服务。
|