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

简介: 本文聚焦核心报错解决和可直接运行的代码两大重点,帮你快速实现题目自动切分。

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

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()
目录
相关文章
|
Web App开发 存储 人工智能
从入门到进阶-如何基于FreeSWITCH搭建呼叫中心平台
FreeSWITCH 是一个开源的电话交换平台。博主从事多年AI技术,外呼中心搭建,需要演示站或技术支持联系博主。官方给它的定义是–世界上第一个跨平台的、伸缩性极好的、免费的、多协议的电话软交换平台
2542 0
|
6月前
|
Windows
Cadence SPB 24 电子设计自动化安装教程PCB设计
本教程详解Cadence SPB 24(64位)安装与激活全流程,涵盖环境准备、主程序安装、Crack补丁配置及验证步骤,助你快速搭建PCB设计环境,开展原理图绘制与布局工作。(239字)
|
5月前
|
人工智能 自然语言处理 定位技术
我把我自己蒸馏成了老金决策.skill并开源了,它甚至教你落地搞钱!
​​ ![Image](https://ucc.alicdn.com/pic/developer-ecology/p3shvhj26rigq_dae64872eaa74d0698b4abe5351c0950.png) 老金把自己的决策模型,蒸馏成了一个老金决策.skill,并且开源了。 它不只是一个让 AI 学老金说话的提示词。 更直接一点说,它甚至能帮你搞钱。 你问 AI:我想学 A
|
5月前
|
人工智能 安全 算法
GEO 行业大清洗:倒闭的不是公司,是整个行业的投机小聪明
本文深度剖析GEO行业2026年集体暴雷的根源:AI技术迭代彻底颠覆旧有商业逻辑。指出虚假繁荣源于信息差红利,而今算法升级、监管加码与品牌认知觉醒共同终结“铺量套利”模式。文章穿透表象,从商业本质、技术底层、人性痛点、产业终局四维拆解,宣告低端中介退场,真GEO已升级为以AI认知基建、品牌信用沉淀为核心的高价值赛道。(239字)
|
5月前
|
机器学习/深度学习 人工智能 弹性计算
阿里云服务器经济型e实例2核2G配置99元怎么样?具体性能、购买资格与适用场景解析
阿里云ECS经济型e实例(2核2G/3M带宽/40G ESSD云盘)以99元/年特惠价面向个人及企业用户开放,活动截止2027年3月31日,新老同享且续费同价。该实例基于Intel至强可扩展处理器,采用共享CPU架构,适合个人博客、开发测试、轻量网站等低负载场景,不适合高并发与深度学习等重负载任务。另有u1实例(2核4G/199元/年)供企业用户选择,提供100%算力保障。
|
5月前
|
存储 运维 关系型数据库
分布式数据库架构演进:从集中式到分布式,三大路线一次讲清楚
本文深入浅出解析分布式数据库选型逻辑:厘清集中式与分布式适用边界,剖析分片、事务、一致性三大技术难点,对比分库分表、原生分布式、共享存储集群三类架构。务实提醒——分布式非银弹,够用才是硬道理。
|
5月前
|
存储 缓存 Java
Java在配置中心(Apollo/Nacos)设计中的核心贡献
微服务架构下,配置文件分散在各处,修改一个配置需要重启服务,效率低下且易出错。配置中心实现了配置的统一管理、动态刷新、版本控制、灰度发布。
393 2
|
5月前
|
存储 人工智能 安全
OpenHuman:本地优先的开源Agent
OpenHuman 展示了一条本地优先的 Agent 路线:把个人记忆、数据连接、自动更新结合,让 Agent 更懂你,也更尊重数据边界。
627 1
|
5月前
|
算法 NoSQL Java
Java在分布式ID生成器(雪花算法)中的实现与优化
在分布式系统中,需要全局唯一、趋势递增、高性能的ID(如订单号、消息ID)。数据库自增ID在分库分表后不再唯一;UUID无序且过长,影响索引性能。
551 1
|
5月前
|
存储 人工智能 前端开发
本地离线知识库的隐私底线 数据落用户目录不出域,察元AI还超级智能体
察元AI桌面版坚守“数据不出域”核心承诺:所有对话、知识库、文件、密钥、日志均本地存储(CHAYUAN_ROOT下),默认禁用遥测,Sidecar绑定127.0.0.1,模型密钥强加密。支持Ollama等本地模型实现完全闭环,0字节外传,满足政企等保与国产化合规要求。(239字)