回到首页 返回首页
回到顶部 回到顶部
返回上一页 返回上一页

行空板M10扩展板 —— 实时绘图共享屏幕,无线投屏无限精彩 简单

头像 sky007 2025.06.25 29 0

实时绘图共享屏幕,无线投屏无限精彩

随着科技的飞速发展,Python编程语言在教育领域的应用越来越广泛。DFRobot推出的行空板M10,以其强大的功能和易用性,成为Python教学与创意开发的理想工具。本次创意开发活动旨在通过行空板M10扩展板组合,进一步拓展其使用场景,助力功能升级与应用进阶,为教育工作者和创客爱好者提供更丰富的教学资源和创作灵感。

行空板M10扩展板组合包括电机IO扩展板、金手指扩展板和800mAh电池扩展板,为参与者提供了丰富的硬件支持。活动鼓励参与者围绕智能机器人、STEAM创意结构和AI物联网应用等方向进行创作,以激发创新思维和实践能力。

非常高兴有幸参加这次活动,在制作小智MCP控制行空板的过程中,借助大模型的强大能力得以实现,无意完成了这个之前想做,但做不成的项目。这里我跟大家分享一下我的探索过程。

圆的绘制方法

根据行空板官方文档-unihiker库,圆的绘制可以通过以下代码实现:

代码
from unihiker import GUI   #导入包
gui=GUI()  #实例化GUI类
gui.fill_circle(x=100, y=150, r=80, color="blue", onclick=lambda: print("fill circle clicked"))

import time
while True:
    #增加等待,防止程序退出和卡住
    time.sleep(1)

功能描述提示词

 

把驱动行空板绘制点的方式给模型后,再提供结构化的功能需求提示词,向大模型提出功能需求,如果需要进一步调整,可多轮提出具体要求进行完善:

 

项目目标

实现电脑端与行空板端图形同步显示。

——功能需求

1.电脑端

~创建240×320窗口,可放大。

~提供调色板更换颜色。

~鼠标绘制图形。

~UDP发送图形坐标。

2.行空板端

~接收UDP发送的坐标。

~根据坐标绘制图形。

3.其他

~两个屏幕显示相同,控制保持一致。

~'q': 退出程序,'c': 清空画布,'+': 增大画笔,'-': 减小画笔。

 

——程序结构

1.发送端(电脑端)

~语言:Python

~库:OpenCV

~功能:绘制图形并发送坐标

2.接收端(行空板端)

~语言:Python

~库:unihiker

~功能:接收坐标并绘制图形

代码阅读

在几轮交流后,模型基本了解你的具体需求,然后测试具体功能,相信应该可以完成你想要的功能.我这里做了一个比较简单的.代码如下,仅供参考:

——电脑端:

代码
      
import cv2
import numpy as np
import socket
import pickle
from datetime import datetime

# 设置
SCALE_FACTOR = 2
WIDTH, HEIGHT = 240, 320
SCALED_WIDTH = WIDTH * SCALE_FACTOR
SCALED_HEIGHT = HEIGHT * SCALE_FACTOR

# 颜色调色板
COLORS = [
    (255, 0, 0),    # 红
    (0, 255, 0),    # 绿
    (0, 0, 255),    # 蓝
    (0, 255, 255),  # 黄
    (255, 0, 255),  # 紫
    (255, 255, 0),  # 青
    (0, 0, 0),      # 黑
    (255, 255, 255) # 白
]
COLOR_NAMES = ["红", "绿", "蓝", "黄", "紫", "青", "黑", "白"]

# 初始化画布
canvas = np.ones((HEIGHT, WIDTH, 3), dtype=np.uint8) * 255
scaled_canvas = cv2.resize(canvas, (SCALED_WIDTH, SCALED_HEIGHT), interpolation=cv2.INTER_NEAREST)

# 初始化绘图状态
drawing = False
current_color = (0, 0, 0)  # 默认黑色
current_color_index = 6     # 黑色
brush_size = 3

# UDP设置
UDP_IP = "192.168.31.134"  # 接收端IP地址
UDP_PORT = 5005
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

def send_data(event_type, x, y):
    """发送坐标点数据给接收端"""
    # 构建数据包 (事件类型, 坐标, 颜色, 画笔大小)
    data = {
        'event': event_type,  # 'start', 'draw', 'end'
        'x': x,
        'y': y,
        'color': current_color,
        'brush_size': brush_size
    }
    
    # 序列化并发送
    serialized_data = pickle.dumps(data)
    sock.sendto(serialized_data, (UDP_IP, UDP_PORT))
    
    # 调试信息
    now = datetime.now().strftime("%H:%M:%S.%f")[:-3]
    print(f"[{now}] 发送: {event_type} @ ({x}, {y}), 颜色 {COLOR_NAMES[current_color_index]}, 大小 {brush_size}")

def draw_circle(event, x, y, flags, param):
    global drawing
    
    # 缩放坐标到实际画布大小
    x_raw = int(x / SCALE_FACTOR)
    y_raw = int(y / SCALE_FACTOR)
    
    if event == cv2.EVENT_LBUTTONDOWN:
        drawing = True
        cv2.circle(canvas, (x_raw, y_raw), brush_size, current_color, -1)
        send_data('start', x_raw, y_raw)  # 开始绘制
    
    elif event == cv2.EVENT_MOUSEMOVE:
        if drawing:
            cv2.circle(canvas, (x_raw, y_raw), brush_size, current_color, -1)
            send_data('draw', x_raw, y_raw)  # 绘制中
    
    elif event == cv2.EVENT_LBUTTONUP:
        drawing = False
        cv2.circle(canvas, (x_raw, y_raw), brush_size, current_color, -1)
        send_data('end', x_raw, y_raw)  # 结束绘制

# 创建窗口
cv2.namedWindow("Drawing Board")
cv2.setMouseCallback("Drawing Board", draw_circle)

# 创建调色板图像
palette = np.zeros((50, len(COLORS)*50, 3), dtype=np.uint8)
for i, color in enumerate(COLORS):
    palette[:, i*50:(i+1)*50] = color

def select_color(event, x, y, flags, param):
    global current_color, current_color_index
    if event == cv2.EVENT_LBUTTONDOWN:
        color_index = x // 50
        if color_index < len(COLORS):
            current_color = COLORS[color_index]
            current_color_index = color_index
            print(f"选择颜色: {COLOR_NAMES[color_index]} (RGB: {current_color})")

cv2.namedWindow("Palette")
cv2.setMouseCallback("Palette", select_color)

print("绘图程序启动(坐标传输模式)")
print("使用说明:")
print("- 在Drawing Board窗口上绘制")
print("- 在Palette窗口选择颜色")
print("- 按'q'退出, 'c'清空画布, '+/-'调整画笔大小")

while True:
    scaled_canvas = cv2.resize(canvas, (SCALED_WIDTH, SCALED_HEIGHT), interpolation=cv2.INTER_NEAREST)
    cv2.imshow("Drawing Board", scaled_canvas)
    cv2.imshow("Palette", palette)
    
    key = cv2.waitKey(1) & 0xFF
    if key == ord('q'):
        print("退出程序")
        # 发送退出指令
        sock.sendto(pickle.dumps({'command': 'quit'}), (UDP_IP, UDP_PORT))
        break
    elif key == ord('c'):
        canvas.fill(255)
        print("清空画布")
        # 发送清屏指令
        sock.sendto(pickle.dumps({'command': 'clear'}), (UDP_IP, UDP_PORT))
    elif key == ord('+'):
        brush_size = min(brush_size + 1, 20)
        print(f"增大画笔大小: {brush_size}")
    elif key == ord('-'):
        brush_size = max(brush_size - 1, 1)
        print(f"减小画笔大小: {brush_size}")

cv2.destroyAllWindows()
sock.close()

    

——行空板端:

代码
      
from unihiker import GUI
import socket
import pickle
import time

# 初始化UniHiker GUI
gui = GUI()
WIDTH, HEIGHT = 240, 320  # 与发送端保持一致

# 存储所有绘制的圆点对象
drawn_circles = []
background = gui.draw_rect(x=0, y=0, w=WIDTH, h=HEIGHT, color="#FFFFFF")  # 白色背景

# UDP设置
UDP_IP = "0.0.0.0"  # 监听所有网络接口
UDP_PORT = 5005     # 与发送端端口一致

# 创建UDP socket
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind((UDP_IP, UDP_PORT))
sock.settimeout(0.1)  # 设置超时时间

print("接收端已启动,等待绘图数据...")

def rgb_to_hex(rgb):
    """将RGB元组转换为十六进制颜色代码"""
    return '#{:02x}{:02x}{:02x}'.format(rgb[0], rgb[1], rgb[2])

def process_data(data_dict):
    """处理接收到的绘图数据"""
    global drawn_circles, background
    
    # 处理命令类型
    if 'command' in data_dict:
        if data_dict['command'] == 'clear':
            # 高效清空画布
            gui.clear()  # 清除所有图形
            # 重新创建白色背景
            background = gui.draw_rect(x=0, y=0, w=WIDTH, h=HEIGHT, color="#FFFFFF")
            drawn_circles = []  # 清空圆点列表
            print("清空画布")
            return True
        
        elif data_dict['command'] == 'quit':
            print("收到退出指令")
            return False
    
    # 处理绘图事件
    if 'event' in data_dict:
        x = data_dict['x']
        y = data_dict['y']
        color = data_dict.get('color', (0, 0, 0))
        brush_size = data_dict.get('brush_size', 3)
        
        # 转换为Unihiker可用的颜色格式
        hex_color = rgb_to_hex(color)
        
        # 绘制实心圆点
        circle = gui.fill_circle(
            x=x, 
            y=y, 
            r=brush_size, 
            color=hex_color
        )
        drawn_circles.append(circle)
        
        print(f"绘制点: ({x}, {y}), 大小 {brush_size}, 颜色 {hex_color}")
        return True
    
    return True  # 继续运行

while True:
    try:
        # 接收数据
        data, addr = sock.recvfrom(65507)  # UDP最大包大小
        
        # 反序列化数据
        try:
            data_dict = pickle.loads(data)
            # print(f"收到数据: {data_dict}")
            
            # 处理数据并检查是否需要退出
            if not process_data(data_dict):
                break
                
        except Exception as e:
            print(f"数据解析错误: {str(e)}")
        
    except socket.timeout:
        # 超时处理,继续循环
        pass
    except Exception as e:
        print(f"接收错误: {str(e)}")
        break
    
    # 短暂休眠
    time.sleep(0.01)

# 清理所有图形
gui.clear()  # 清除所有图形

sock.close()
print("接收端已关闭")

    

这是一个意外的收获,我自己感觉挺有意思,分享给大家.如果你也在做类似投屏功能,希望对你可以有所帮助.

评论

user-avatar