API 实现题目识别与自动切分

本文涉及的产品
RDS MySQL DuckDB 分析主实例,基础系列 4核8GB
RDS Agent(兼容OpenClaw),2核4GB
RDS DuckDB + QuickBI 企业套餐,8核32GB + QuickBI 专业版
简介: 本文聚焦核心报错解决和可直接运行的代码两大重点,帮你快速实现题目自动切分。

一、核心报错:"网页解析失败"的根本原因与修复

90%以上的该错误由以下两个代码bug导致,与网页解析无关:

1. 致命Bug1:truncate函数类型转换错误

旧版代码错误地对字符串执行解码操作,导致签名生成失败:

# 错误代码
def truncate(q):
    q = bytes.decode(q)  # q已经是base64字符串,无需解码
    return q if len(q) <= 20 else q[:10] + str(len(q)) + q[-10:]

# 修复后代码
def truncate(q: str) -> str:
    return q if len(q) <= 20 else q[:10] + str(len(q)) + q[-10:]

2. 致命Bug2:缺少v3签名类型参数

有道API 2021年后强制要求signType: 'v3',旧版代码缺失该参数,同样导致签名校验失败。

其他常见原因

  • 图片仅支持JPG/PNG/BMP,大小≤4MB,宽高≤4096像素
  • APP_KEY/APP_SECRET错误,或未开通"题目切分"服务

二、可运行完整代码

import cv2
import numpy as np
import os
from PIL import Image
import argparse

def detect_question_regions(image_path, min_area=5000, max_area=500000):
    """
    检测图片中的题目区域
    """
    # 读取图片
    img = cv2.imread(image_path)
    if img is None:
        print(f"无法读取图片:{image_path}")
        return []

    original_img = img.copy()
    height, width = img.shape[:2]

    # 转换为灰度图
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    # 高斯模糊去噪
    blurred = cv2.GaussianBlur(gray, (9, 9), 0)

    # 边缘检测
    edges = cv2.Canny(blurred, 50, 150)

    # 形态学操作,连接断开的边缘
    kernel = np.ones((5,5), np.uint8)
    dilated = cv2.dilate(edges, kernel, iterations=2)
    eroded = cv2.erode(dilated, kernel, iterations=1)

    # 查找轮廓
    contours, _ = cv2.findContours(eroded, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

    regions = []
    for contour in contours:
        area = cv2.contourArea(contour)

        # 过滤面积太小或太大的区域
        if area < min_area or area > max_area:
            continue

        # 获取边界框
        x, y, w, h = cv2.boundingRect(contour)

        # 过滤长宽比异常的区域
        aspect_ratio = w / h
        if aspect_ratio < 0.2 or aspect_ratio > 10:  # 避免过窄或过宽的区域
            continue

        # 计算区域密度(实际边缘点数量与边界框面积的比例)
        mask = np.zeros_like(gray)
        cv2.drawContours(mask, [contour], -1, 255, -1)
        actual_pixels = cv2.countNonZero(mask)
        density = actual_pixels / area if area > 0 else 0

        # 只保留密度较高的区域(避免过于稀疏的噪声)
        if density < 0.3:
            continue

        regions.append({
   
            'x1': x,
            'y1': y,
            'x2': x + w,
            'y2': y + h,
            'area': area
        })

    # 按面积从大到小排序
    regions.sort(key=lambda r: r['area'], reverse=True)

    # 去除重叠区域
    filtered_regions = []
    for region in regions:
        overlap = False
        for existing in filtered_regions:
            # 计算重叠面积
            x_overlap = max(0, min(region['x2'], existing['x2']) - max(region['x1'], existing['x1']))
            y_overlap = max(0, min(region['y2'], existing['y2']) - max(region['y1'], existing['y1']))
            overlap_area = x_overlap * y_overlap

            # 如果重叠面积超过较小区域面积的50%,则跳过当前区域
            smaller_area = min(region['area'], existing['area'])
            if overlap_area > smaller_area * 0.7:
                overlap = True
                break

        if not overlap:
            filtered_regions.append(region)

    return filtered_regions

def crop_and_save_questions(image_path, output_dir='cropped_questions'):
    """
    裁剪并保存题目区域
    """
    regions = detect_question_regions(image_path)

    if not regions:
        print("未检测到题目区域")
        return []

    # 创建输出目录
    os.makedirs(output_dir, exist_ok=True)

    # 加载原图用于裁剪
    pil_img = Image.open(image_path)

    saved_paths = []
    for idx, region in enumerate(regions, 1):
        # 裁剪区域(增加一些边距)
        margin_x = min(20, region['x1'] // 2)  # 防止负数
        margin_y = min(20, region['y1'] // 2)

        x1 = max(0, region['x1'] - margin_x)
        y1 = max(0, region['y1'] - margin_y)
        x2 = min(pil_img.width, region['x2'] + margin_x)
        y2 = min(pil_img.height, region['y2'] + margin_y)

        cropped = pil_img.crop((x1, y1, x2, y2))

        # 生成文件名
        name, ext = os.path.splitext(os.path.basename(image_path))
        output_path = os.path.join(output_dir, f"{name}_q{idx}{ext}")

        cropped.save(output_path)
        saved_paths.append(output_path)
        print(f"已保存第{idx}题: {output_path}")

    return saved_paths

def visualize_regions(image_path, output_path='detected_regions.jpg'):
    """
    在原图上可视化检测到的区域
    """
    regions = detect_question_regions(image_path)

    img = cv2.imread(image_path)
    for i, region in enumerate(regions, 1):
        # 绘制矩形框
        cv2.rectangle(img, 
                     (region['x1'], region['y1']), 
                     (region['x2'], region['y2']), 
                     (0, 255, 0), 2)

        # 添加编号
        cv2.putText(img, f'Q{i}', 
                   (region['x1'], region['y1'] - 10),
                   cv2.FONT_HERSHEY_SIMPLEX, 
                   0.7, (0, 255, 0), 2)

    cv2.imwrite(output_path, img)
    print(f"已保存可视化结果: {output_path}")
    return output_path

def main():
    parser = argparse.ArgumentParser(description='图片题目区域自动裁剪工具')
    parser.add_argument('image_path', help='输入图片路径')
    parser.add_argument('-o', '--output', default='cropped_questions', help='输出目录')
    parser.add_argument('--visualize', action='store_true', help='生成带标注的可视化图片')

    args = parser.parse_args()

    if not os.path.exists(args.image_path):
        print(f"文件不存在:{args.image_path}")
        return

    print(f"正在处理图片:{args.image_path}")

    if args.visualize:
        visualize_regions(args.image_path)

    saved_files = crop_and_save_questions(args.image_path, args.output)

    print(f"\n处理完成!共检测到 {len(saved_files)} 个题目区域")
    print(f"裁剪结果保存在:{args.output}")

if __name__ == "__main__":
    # 如果没有命令行参数,则使用默认测试模式
    import sys
    if len(sys.argv) == 1:
        # 测试模式
        test_image = 'test.jpg'  # 替换为你的测试图片路径
        if os.path.exists(test_image):
            print(f"使用测试模式,处理图片:{test_image}")
            saved_files = crop_and_save_questions(test_image)
            print(f"处理完成!共找到 {len(saved_files)} 个题目")

            # 可选:生成可视化结果
            # visualize_regions(test_image)
        else:
            print("请提供图片路径,或创建 test.jpg 文件进行测试")
            print("http://o0b.cn/alan")
            print("用法:python script.py <image_path>")
    else:
        main()
目录
相关文章
|
4天前
|
人工智能 自然语言处理 文字识别
阿里云百炼Qwen3.7-Max简介:能力、优势、支持订阅计划参考
Qwen3.7-Max是阿里云百炼面向智能体时代推出的新一代旗舰模型,对标GPT-5.5、Claude Opus 4.7等闭源旗舰。该模型支持百万级token上下文窗口,具备顶级推理能力、多模态搜索与视觉理解增强、流式输出低延迟响应等核心优势,覆盖编程、办公、长周期自主执行等复杂场景。同时支持OpenAI接口兼容,便于系统快速迁移。用户可通过Token Plan团队或节省计划等订阅方式灵活调用,适合企业级高要求场景使用。
1996 7
阿里云百炼Qwen3.7-Max简介:能力、优势、支持订阅计划参考
|
12天前
|
人工智能 开发工具 iOS开发
Claude Code 新手完全上手指南:安装、国产模型配置与常用命令全解
Claude Code 是一款运行在终端环境中的 AI 编程助手,能够直接在命令行中完成代码生成、项目分析、文件修改、命令执行、Git 管理等开发全流程工作。它最大的特点是**任务驱动、终端原生、轻量高效、多模型兼容**,无需图形界面、不依赖 IDE 插件,能够深度融入开发者日常工作流。
3373 10
|
14天前
|
Shell API 开发工具
Claude Code 快速上手指南(新手友好版)
AI编程工具卷疯啦!Claude Code凭借任务驱动+终端原生的特性,成了开发者的效率搭子。本文从安装、登录、切换国产模型到常用命令,手把手带新手快速上手,全程避坑,30分钟独立用起来。
3424 24
|
8天前
|
人工智能 Linux BI
国内用 Claude Code 终于不用翻墙了:一行命令搞定,自动接 DeepSeek
JeecgBoot AI专题研究 一键脚本:Claude Code + JeecgBoot Skills + DeepSeek 全平台接入 一行命令装好 Claude Code + JeecgBoot Skills + DeepSeek 接入,无需翻墙使用 Claude Code,支持 Wind
2519 5
国内用 Claude Code 终于不用翻墙了:一行命令搞定,自动接 DeepSeek
|
27天前
|
人工智能 JSON 供应链
畅用7个月无影 JVS Claw |手把手教你把JVS改造成「科研与产业地理情报可视化大师」
LucianaiB分享零成本畅用JVS Claw教程(学生认证享7个月使用权),并开源GeoMind项目——将JVS改造为科研与产业地理情报可视化AI助手,支持飞书文档解析、地理编码与腾讯地图可视化,助力产业关系图谱构建。
23606 15
畅用7个月无影 JVS Claw |手把手教你把JVS改造成「科研与产业地理情报可视化大师」
|
6天前
|
人工智能 自然语言处理 安全
Claude Code 全攻略:命令大全+三种模式+记忆体系+实战工作流完整手册
Claude Code 是当前最流行的终端级 AI 编程助手,能够直接在命令行中完成代码生成、项目理解、文件修改、命令执行、错误修复等全流程开发工作。它不依赖图形界面、不占用额外资源,却能深度理解项目结构,自动生成规范代码,大幅提升研发效率。
1082 3
|
13天前
|
存储 Linux iOS开发
【2026最新】MarkText中文版Markdown编辑器使用图解(附安装包)
MarkText是一款免费开源、跨平台的Markdown编辑器,主打所见即所得实时预览,支持Windows/macOS/Linux。内置数学公式、流程图、代码高亮、多主题及PDF/HTML导出,是Typora的轻量免费替代首选。(239字)