import time
from media.sensor import *
from media.display import *
from media.media import *
import math
from machine import UART
from machine import FPIOA
# ---------- 参数 ----------
LCD_W = 800
LCD_H = 480
A4_REAL_W = 210.0 # mm
A4_REAL_H = 297.0 # mm
FOCAL_PIX = 389 # 标定后填入
class Mode:
BASIC = 0 # 默认模式:找外/内框 + 圆/三角/四边形
MIN_QUAD = 1 # 最小四边形模式:只找四边形,找最小
TILT = 2 # 倾斜模式:A4纸倾斜30-60度时测量正方形边长
current_mode = Mode.BASIC
sensor = Sensor(width=1280, height=960)
sensor.reset()
sensor.set_framesize(width=320, height=240)
sensor.set_pixformat(Sensor.RGB565)
Display.init(Display.ST7701, width=LCD_W, height=LCD_H, to_ide=True)
MediaManager.init()
sensor.run()
sensor.set_hmirror(True)
fpioa = FPIOA()
fpioa.set_function(5, fpioa.UART2_TXD)
fpioa.set_function(6, fpioa.UART2_RXD)
uart = UART(UART.UART2, baudrate=115200, bits=UART.EIGHTBITS, parity=UART.PARITY_NONE, stop=UART.STOPBITS_ONE)
clk = time.clock()
def is_a4_like(rect):
w, h = rect.w(), rect.h()
ratio = max(w, h) / min(w, h)
return 1.25 < ratio < 1.55
def is_a4_like_tilt(rect):
w, h = rect.w(), rect.h()
area = w * h
ratio = max(w, h) / min(w, h)
return (0.9 < ratio < 3.8) and (area > 1000)
def inside(inner, outer):
xo, yo, wo, ho = outer.rect()
for (x, y) in inner.corners():
if not (xo < x < xo + wo and yo < y < yo + ho):
return False
return True
def dist_mm(p1, p2, current_pix_w):
pix_dist = math.sqrt((p1[0]-p2[0])**2 + (p1[1]-p2[1])**2)
return (pix_dist * A4_REAL_W) / current_pix_w
def dist_mm_tilt(p1, p2, current_pix_h):
pix_dist = math.sqrt((p1[0]-p2[0])**2 + (p1[1]-p2[1])**2)
return (pix_dist * A4_REAL_H) / current_pix_h
# -------------------------------------------------
# ROI 边界保护
# -------------------------------------------------
def clamp_roi(x, y, w, h, img_w, img_h):
x = max(0, min(x, img_w - 1))
y = max(0, min(y, img_h - 1))
w = max(1, min(w, img_w - x))
h = max(1, min(h, img_h - y))
return (x, y, w, h)
# -------------------------------------------------
# 用 draw_line() 模拟 draw_polygon()
# -------------------------------------------------
def draw_poly(img, pts, color, thickness=2):
n = len(pts)
for i in range(n):
x1, y1 = int(pts[i][0]), int(pts[i][1])
x2, y2 = int(pts[(i+1) % n][0]), int(pts[(i+1) % n][1])
img.draw_line(x1, y1, x2, y2, color=color, thickness=thickness)
# -------------------- 串口命令解析 -------------------------
def parse_uart():
global current_mode
if uart.any():
cmd = uart.read().decode().strip()
if cmd == 'detectminQuad':
current_mode = Mode.MIN_QUAD
uart.write("M\n")
elif cmd == 'basic':
current_mode = Mode.BASIC
uart.write("M\n")
elif cmd == 'tilt':
current_mode = Mode.TILT
uart.write("M\n")
while True:
clk.tick()
parse_uart() # 每帧先检查串口指令
img = sensor.snapshot()
# ---------- BASIC 模式 ----------
if current_mode == Mode.BASIC:
rects = img.find_rects(threshold=10000)
rects = sorted(rects, key=lambda r: r.w()*r.h(), reverse=True)
outer = inner = None
if len(rects) >= 2:
cand = [r for r in rects[:2] if is_a4_like_tilt(r)]
if len(cand) == 2:
outer, inner = cand[0], cand[1]
if not inside(inner, outer):
outer = inner = None
dist_mm_a4 = 0
if outer and inner:
img.draw_rectangle(outer.rect(), color=(255, 0, 0), thickness=2)
for p in outer.corners():
img.draw_circle(p[0], p[1], 5, color=(0, 255, 0))
img.draw_rectangle(inner.rect(), color=(0, 255, 255), thickness=2)
for p in inner.corners():
img.draw_circle(p[0], p[1], 5, color=(0, 0, 255))
pixel_width = outer.w()
dist_mm_a4 = (A4_REAL_W * FOCAL_PIX) / pixel_width
img.draw_string(5, 5, "A4:%.1f cm" % (dist_mm_a4/10),
color=(255, 255, 255), scale=2)
uart.write("D%d\n" % int (dist_mm_a4))
img.draw_string(5, 200, "w_px=%d" % pixel_width,
color=(255, 255, 255), scale=2)
# 圆/三角/四边形逻辑
shrink = 5
x0, y0, w0, h0 = inner.rect()
x0, y0, w0, h0 = clamp_roi(x0 + shrink, y0 + shrink,
w0 - 2*shrink, h0 - 2*shrink,
img.width(), img.height())
circles = img.find_circles(threshold=3000,
x_margin=10, y_margin=10, r_margin=10,
r_min=2, r_max=100, r_step=2,
roi=(x0, y0, w0, h0))
has_circle = False
for c in circles:
has_circle = True
dia_mm = 2 * c.r() * A4_REAL_W / pixel_width
img.draw_circle(c.x(), c.y(), c.r(), color=(0, 255, 0), thickness=2)
img.draw_string(int(c.x()-10), int(c.y()-10),
"C:%.1f mm" % dia_mm,
color=(0, 255, 0), scale=1)
uart.write("R%d\n" % int(dia_mm))
if not has_circle:
gray = img.to_grayscale(roi=(x0, y0, w0, h0))
bw = gray.binary([(0, 80)],invert=False)
inner_area = w0 * h0
MIN_AREA = int(inner_area * 0.1)
MAX_AREA = int(inner_area * 0.7)
center_x = w0 // 2
center_y = h0 // 2
blobs = sorted(
bw.find_blobs([(255, 255)],
pixels_threshold=100,
area_threshold=MIN_AREA,
roi=(0, 0, w0, h0)),
key=lambda b: (b.cx() - center_x) ** 2 + (b.cy() - center_y) ** 2
)
for blob in blobs:
corners = blob.min_corners()
corners = [(cx + x0, cy + y0) for (cx, cy) in corners]
blob_area = blob.pixels()
bbox_area = 0.5 * abs(sum(corners[i][0] * corners[(i+1)%4][1] -
corners[(i+1)%4][0] * corners[i][1] for i in range(4)))
area_ratio = blob_area / bbox_area if bbox_area > 0 else 1
if ( area_ratio < 0.9):
a = dist_mm(corners[0], corners[1], pixel_width)
b = dist_mm(corners[1], corners[2], pixel_width)
c = dist_mm(corners[2], corners[0], pixel_width)
info = "T:%.1f/%.1f/%.1f mm" % (a, b, c)
img.draw_string(int(blob.cx()+x0), int(blob.cy()+y0), info,
color=(255, 255, 0), scale=1)
uart.write("T%d\n" % int(a))
else:
sides = []
for i in range(4):
sides.append(dist_mm(corners[i], corners[(i+1)%4], pixel_width))
draw_poly(img, corners, color=(255, 0, 255), thickness=2)
info = "Q:%.1f/%.1f/%.1f/%.1f mm" % tuple(sides)
img.draw_string(int(blob.cx()+x0), int(blob.cy()+y0), info,
color=(255, 0, 255), scale=1)
avg_side = sum(sides) / 4
uart.write("Q%d\n" % int(avg_side))
elif current_mode == Mode.TILT:
rects = img.find_rects(threshold=10000)
rects = sorted(rects, key=lambda r: r.w()*r.h(), reverse=True)
outer = inner = None
if len(rects) >= 2:
cand = [r for r in rects[:2] if is_a4_like_tilt(r)]
if len(cand) == 2:
outer, inner = cand[0], cand[1]
if not inside(inner, outer):
outer = inner = None
if not outer:
Display.show_image(img,
x=(LCD_W - img.width())//2,
y=(LCD_H - img.height())//2)
continue
draw_poly(img, outer.corners(), color=(255, 0, 0), thickness=2)
if inner:
draw_poly(img, inner.corners(), color=(0, 255, 255), thickness=2)
corners = outer.corners()
edges = []
for i in range(4):
edge_length = math.sqrt((corners[i][0]-corners[(i+1)%4][0])**2 + (corners[i][1]-corners[(i+1)%4][1])**2)
edges.append(edge_length)
edges_sorted = sorted(edges)
long_edge = edges_sorted[-1]
pixel_width = long_edge
dist_mm_a4 = (A4_REAL_H * FOCAL_PIX) / pixel_width
img.draw_string(5, 5, "TILT %.1f cm" % (dist_mm_a4/10),
color=(255, 255, 255), scale=2)
uart.write("D%d\n" % int(dist_mm_a4))
# 内框ROI - 使用外框区域作为ROI
shrink = 4
x0, y0, w0, h0 = outer.rect()
x0, y0, w0, h0 = clamp_roi(x0 + shrink, y0 + shrink,
w0 - 2*shrink, h0 - 2*shrink,
img.width(), img.height())
gray = img.to_grayscale(roi=(x0, y0, w0, h0))
bw = gray.binary([(0, 80)], invert=False)
inner_area = w0 * h0
MIN_AREA = int(inner_area * 0.06)
MAX_AREA = int(inner_area * 0.7)
blobs = bw.find_blobs([(255, 255)],
pixels_threshold=50,
area_threshold=MIN_AREA,
roi=(0, 0, w0, h0))
# 收集所有四边形
quad_list = []
for blob in blobs:
corners = blob.min_corners()
corners = [(cx + x0, cy + y0) for (cx, cy) in corners]
blob_area = blob.pixels()
bbox_area = 0.5 * abs(sum(corners[i][0] * corners[(i+1)%4][1] -
corners[(i+1)%4][0] * corners[i][1] for i in range(4)))
area_ratio = blob_area / bbox_area if bbox_area > 0 else 1
if area_ratio >= 0.8:
sides = [dist_mm_tilt(corners[i], corners[(i+1)%4], pixel_width) for i in range(4)]
avg_side = sum(sides) / 4
max_diff = max(abs(s - avg_side) for s in sides)
if max_diff < avg_side * 0.2:
quad_list.append((corners, avg_side))
if quad_list:
# 按面积排序,取最大的正方形
quad_list.sort(key=lambda q:
abs((q[0][1][0]-q[0][0][0])*(q[0][2][1]-q[0][1][1])),
reverse=True)
square_corners, square_side = quad_list[0]
# 画正方形
draw_poly(img, square_corners, color=(0, 255, 0), thickness=3)
center_x = sum(p[0] for p in square_corners) / 4
center_y = sum(p[1] for p in square_corners) / 4
img.draw_string(int(center_x), int(center_y),
"SQ:%.1f mm" % square_side,
color=(0, 255, 0), scale=2)
# 发送正方形边长
uart.write("Q%d\n" % int(square_side))
img.draw_string(5, 30, "SQ:%.1f mm" % square_side,
color=(0, 255, 0), scale=2)
# ---------- MIN_QUAD 模式 ----------
elif current_mode == Mode.MIN_QUAD:
rects = img.find_rects(threshold=10000)
rects = sorted(rects, key=lambda r: r.w()*r.h(), reverse=True)
outer = inner = None
if len(rects) >= 2:
cand = [r for r in rects[:2] if is_a4_like_tilt(r)]
if len(cand) == 2:
outer, inner = cand[0], cand[1]
if not inside(inner, outer):
outer = inner = None
if not outer:
Display.show_image(img,
x=(LCD_W - img.width())//2,
y=(LCD_H - img.height())//2)
continue
# 画外框和内框
draw_poly(img, outer.corners(), color=(255, 0, 0), thickness=2)
if inner:
draw_poly(img, inner.corners(), color=(0, 255, 255), thickness=2)
corners = outer.corners()
edges = []
for i in range(4):
edge_length = math.sqrt((corners[i][0]-corners[(i+1)%4][0])**2 + (corners[i][1]-corners[(i+1)%4][1])**2)
edges.append(edge_length)
edges_sorted = sorted(edges)
pixel_width = edges_sorted[0]
dist_mm_a4 = (A4_REAL_W * FOCAL_PIX) / pixel_width
img.draw_string(5, 5, "MIN_QUAD %.1f cm" % (dist_mm_a4/10),
color=(255, 255, 255), scale=2)
uart.write("D%d\n" % int (dist_mm_a4))
# ROI
shrink = 3
x0, y0, w0, h0 = outer.rect()
x0, y0, w0, h0 = clamp_roi(x0 + shrink, y0 + shrink,
w0 - 2*shrink, h0 - 2*shrink,
img.width(), img.height())
gray = img.to_grayscale(roi=(x0, y0, w0, h0))
bw = gray.binary([(0, 80)], invert=False)
inner_area = w0 * h0
MIN_AREA = int(inner_area * 0.04)
MAX_AREA = int(inner_area * 0.7)
blobs = bw.find_blobs([(255, 255)],
pixels_threshold=50,
area_threshold=MIN_AREA,
roi=(0, 0, w0, h0))
quad_list = []
for blob in blobs:
corners = blob.min_corners()
corners = [(cx + x0, cy + y0) for (cx, cy) in corners]
blob_area = blob.pixels()
bbox_area = 0.5 * abs(sum(corners[i][0] * corners[(i+1)%4][1] -
corners[(i+1)%4][0] * corners[i][1] for i in range(4)))
area_ratio = blob_area / bbox_area if bbox_area > 0 else 1
if area_ratio >= 0.9:
sides = [dist_mm(corners[i], corners[(i+1)%4], pixel_width) for i in range(4)]
min_side = min(sides)
quad_list.append((corners, min_side, sides))
if quad_list:
quad_list.sort(key=lambda q: q[1])
min_corners, min_side, min_sides = quad_list[0]
for corners, _, sides in quad_list:
color = (255, 0, 0) if corners == min_corners else (255, 0, 255)
draw_poly(img, corners, color=color, thickness=2)
info = "Q:%.1f/%.1f/%.1f/%.1f" % tuple(sides)
img.draw_string(int(sum(p[0] for p in corners)/4),
int(sum(p[1] for p in corners)/4),
info, color=color, scale=1)
# 发送最小四边形最短边
uart.write("Q%d\n" % int(min_side))
img.draw_string(5, 30, "MIN:%.1f mm" % min_side,
color=(255, 0, 0), scale=2)
# 统一显示
Display.show_image(img,
x=(LCD_W - img.width())//2,
y=(LCD_H - img.height())//2)