import cv2
import numpy as np
import time
# A4纸标准尺寸 (单位:mm)
A4_WIDTH = 210
A4_HEIGHT = 297
BLACK_EDGE_WIDTH_MM = 20 # 2cm黑边
def detect_a4_paper_with_black_edge(frame):
"""检测带有2cm黑边的A4纸"""
start_time = time.time()
# 调整输入图像尺寸
frame = cv2.resize(frame, (800, 600))
output_frame = frame.copy()
# 创建灰度处理图像
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (7, 7), 0)
edged = cv2.Canny(blurred, 30, 120)
# 灰度图像显示准备
gray_display = cv2.cvtColor(edged, cv2.COLOR_GRAY2BGR)
# 查找A4纸轮廓 - 特别关注外边缘
contours, _ = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
a4_contour = None
for contour in contours:
if cv2.contourArea(contour) < 5000:
continue
peri = cv2.arcLength(contour, True)
approx = cv2.approxPolyDP(contour, 0.02 * peri, True)
# 只考虑凸四边形
if len(approx) == 4 and cv2.isContourConvex(approx):
a4_contour = approx
break
extracted_image = None
has_black_edge = False
if a4_contour is not None:
# 绘制A4纸外边缘轮廓(黑色边框外边缘)
cv2.drawContours(output_frame, [a4_contour], -1, (0, 0, 255), 2)
cv2.putText(output_frame, "A4纸外边缘",
(a4_contour[0][0][0], a4_contour[0][0][1] - 10),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 2)
# 透视变换准备
pts = a4_contour.reshape(4, 2)
rect = np.zeros((4, 2), dtype="float32")
s = pts.sum(axis=1)
rect[0] = pts[np.argmin(s)] # 左上
rect[2] = pts[np.argmax(s)] # 右下
diff = np.diff(pts, axis=1)
rect[1] = pts[np.argmin(diff)] # 右上
rect[3] = pts[np.argmax(diff)] # 左下
# 计算A4纸尺寸
(tl, tr, br, bl) = rect
widthA = np.linalg.norm(br - bl)
widthB = np.linalg.norm(tr - tl)
maxWidth = max(int(widthA), int(widthB))
heightA = np.linalg.norm(tr - br)
heightB = np.linalg.norm(tl - bl)
maxHeight = max(int(heightA), int(heightB))
# 目标点定义
dst = np.array([
[0, 0],
[maxWidth - 1, 0],
[maxWidth - 1, maxHeight - 1],
[0, maxHeight - 1]], dtype="float32")
try:
# 执行透视变换
M = cv2.getPerspectiveTransform(rect, dst)
warped = cv2.warpPerspective(frame, M, (maxWidth, maxHeight))
extracted_image = warped.copy()
# 计算像素到毫米的转换比例
px_per_mm_width = maxWidth / A4_WIDTH
px_per_mm_height = maxHeight / A4_HEIGHT
px_per_mm = min(px_per_mm_width, px_per_mm_height)
# 计算2cm黑边对应的像素宽度
edge_px = int(BLACK_EDGE_WIDTH_MM * px_per_mm)
# 创建黑边检测区域 (向内缩进2cm)
inner_rect = np.array([
[edge_px, edge_px],
[maxWidth - 1 - edge_px, edge_px],
[maxWidth - 1 - edge_px, maxHeight - 1 - edge_px],
[edge_px, maxHeight - 1 - edge_px]
], dtype=np.int32)
# 检测黑边 - 使用HSV颜色空间提高准确性
warped_hsv = cv2.cvtColor(warped, cv2.COLOR_BGR2HSV)
# 创建黑边检测掩码
edge_mask = np.zeros(warped.shape[:2], dtype=np.uint8)
cv2.fillPoly(edge_mask, [inner_rect], 255)
edge_mask = cv2.bitwise_not(edge_mask)
# 分析边缘区域颜色 (使用HSV值)
edge_region = cv2.bitwise_and(warped_hsv, warped_hsv, mask=edge_mask)
edge_v = edge_region[:, :, 2] # 提取亮度通道
# 判断是否为黑边 (平均亮度值 < 30)
if np.mean(edge_v) < 30:
has_black_edge = True
cv2.putText(output_frame, "检测到2cm黑边", (10, 60),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 255), 2)
# 在矫正图像上绘制黑边区域
cv2.polylines(warped, [inner_rect], True, (0, 255, 255), 3)
cv2.putText(warped, "黑边区域", (inner_rect[0][0], inner_rect[0][1] - 5),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 255), 2)
# 提取内部图像 (去除黑边)
inner_mask = np.zeros(warped.shape[:2], dtype=np.uint8)
cv2.fillPoly(inner_mask, [inner_rect], 255)
extracted_image = cv2.bitwise_and(warped, warped, mask=inner_mask)
# 在输出帧左上角显示矫正后的图像
h, w = warped.shape[:2]
scale = min(300 / h, 300 / w)
warped_display = cv2.resize(warped, (int(w * scale), int(h * scale)))
h_disp, w_disp = warped_display.shape[:2]
output_frame[10:10 + h_disp, 10:10 + w_disp] = warped_display
# 在右上角显示提取的内部图像
if extracted_image is not None:
h_inner, w_inner = extracted_image.shape[:2]
scale_inner = min(200 / h_inner, 200 / w_inner)
extracted_display = cv2.resize(extracted_image,
(int(w_inner * scale_inner), int(h_inner * scale_inner)))
h_ext, w_ext = extracted_display.shape[:2]
output_frame[10:10 + h_ext,
output_frame.shape[1] - w_ext - 10:output_frame.shape[1] - 10] = extracted_display
cv2.putText(output_frame, "内部图像", (output_frame.shape[1] - w_ext - 10, 5),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 0), 1)
except Exception as e:
print(f"处理出错: {e}")
# 显示帧率
fps = 1.0 / (time.time() - start_time)
cv2.putText(output_frame, f"帧率: {int(fps)}", (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
return output_frame, gray_display, extracted_image
def main():
cap = cv2.VideoCapture(0)
if not cap.isOpened():
print("无法打开摄像头")
return
print("程序已启动 - 检测带黑边的A4纸")
print("按 'q' 键退出程序")
# 创建三个命名窗口并设置初始位置
cv2.namedWindow("A4纸检测")
cv2.moveWindow("A4纸检测", 50, 50)
cv2.namedWindow("边缘处理")
cv2.moveWindow("边缘处理", 700, 50)
cv2.namedWindow("提取内容")
cv2.moveWindow("提取内容", 50, 400)
try:
while True:
ret, frame = cap.read()
if not ret:
break
# 处理帧
processed_frame, gray_frame, extracted_img = detect_a4_paper_with_black_edge(frame)
# 显示三个窗口
cv2.imshow("A4纸检测", processed_frame)
cv2.imshow("边缘处理", gray_frame)
# 显示提取的图像
if extracted_img is not None:
cv2.imshow("提取内容", extracted_img)
else:
placeholder = np.zeros((300, 300, 3), dtype=np.uint8)
cv2.putText(placeholder, "等待检测带黑边的A4纸...", (20, 150),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
cv2.imshow("提取内容", placeholder)
# 调整窗口位置确保不重叠
cv2.moveWindow("A4纸检测", 50, 50)
cv2.moveWindow("边缘处理", 700, 50)
cv2.moveWindow("提取内容", 50, 400)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
finally:
cap.release()
cv2.destroyAllWindows()
print("程序已退出")
if __name__ == "__main__":
main()
测量出图中a4纸的长度与宽度,以及三角形或矩形或圆形的面积