惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
PCI Perspectives
PCI Perspectives
The Hacker News
The Hacker News
Project Zero
Project Zero
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
P
Privacy International News Feed
C
Cybersecurity and Infrastructure Security Agency CISA
Schneier on Security
Schneier on Security
V
Vulnerabilities – Threatpost
T
Threat Research - Cisco Blogs
Blog — PlanetScale
Blog — PlanetScale
Hugging Face - Blog
Hugging Face - Blog
博客园_首页
C
Cisco Blogs
C
CERT Recently Published Vulnerability Notes
L
Lohrmann on Cybersecurity
Cisco Talos Blog
Cisco Talos Blog
WordPress大学
WordPress大学
Cloudbric
Cloudbric
Know Your Adversary
Know Your Adversary
博客园 - Franky
博客园 - 聂微东
S
SegmentFault 最新的问题
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
AI
AI
Latest news
Latest news
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Vercel News
Vercel News
H
Heimdal Security Blog
Security Archives - TechRepublic
Security Archives - TechRepublic
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Spread Privacy
Spread Privacy
IT之家
IT之家
N
News and Events Feed by Topic
H
Hacker News: Front Page
The Cloudflare Blog
Jina AI
Jina AI
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Webroot Blog
Webroot Blog
I
Intezer
L
LangChain Blog
T
The Blog of Author Tim Ferriss
T
Threatpost
A
About on SuperTechFans
博客园 - 叶小钗
美团技术团队
NISL@THU
NISL@THU
H
Hackread – Cybersecurity News, Data Breaches, AI and More
www.infosecurity-magazine.com
www.infosecurity-magazine.com

科技

值得入手 glm max 吗 - V2EX GPT 取消二验了 - V2EX 0 封号风险 —— Mp2RSS 把公众号和 X 装进你的阅读器 - V2EX [提问] claude 现在怎么注册验证呀 - V2EX deepseek 网页和 app 输入<think>能看到别的用户的历史对话 大家对于 Anthropic 估值的看法 美伊还不如继续打呢,一停战 AI 科技股又支棱起来了,美光、闪迪又要涨价了,普通人啥好处也没捞到除了失业 - V2EX 号池(CC Max) - V2EX 铺天盖地的 OpenClaw,到底能做什么?(不吹不黑) 区块链/LLM 这些技术随着科技发展总会到来 - V2EX 京东都是用啥脚本抢的呀 - V2EX 除了 Vibe Coding,各位所在的行业有因为 AI 应用带来革命性进展,或者底层变革的吗? - V2EX 要迎来固态电池的时代了吗? - V2EX 老罗今晚的分享大会,有你感兴趣的产品吗? - V2EX 🖖Manus 被 Meta 数十亿美元收购❗❗❗ - V2EX Excelize 开源社区荣获 2025 红山开源明星项目三等奖 Gemini 的新功能,搞笑狮子漂移(动态视图) - V2EX Google Antigravity 出来啦 - V2EX 一觉醒来, G 老师 3 Pro 已经可以用了,起飞 - V2EX AI 时代,每个月会在 AI 产品上花多少钱? - V2EX 大伙们,请教一下智能手表的问题,打算买 - V2EX 现在机器之心需要订阅才能看历史文章,有没有替代平台啊 - V2EX 西安有哪些做 OCR 做的比较好的企业啊,有项目需要合作 - V2EX 请教一下这个 h5 跳到公众号页面流程是怎么实现的 - V2EX 影石今晚发布全景无人机,看了视频感觉不错、后期可选视角,有玩无人机比较专业的佬吗?你们玩机的注重点是什么? - V2EX trae 偷我两天 pro - V2EX emby 网盘服的搭建以及关于更优方案的探讨 - V2EX 纯技术讨论,有没有详细讲解 GFW 实现原理的博客或者视频 - V2EX 手游自动化脚本 - V2EX 开源反编译器支持 dex/apk - V2EX 怪不得抖音上擦边的律师越来越多了 - V2EX 人类在劳动密集型工作中优势越来越弱了 - V2EX 小红书可以进入开发者模式? - V2EX veo3 一出,我越来越相信这个世界是虚拟的了 - V2EX 从 1947 年晶体管诞生,构建了今天的数字文明,下一个会是什么 - V2EX 请问大家的公司开发文档都是通过什么来规范的 - V2EX [AI] 有类似豆包 app 的开源项目客户端吗 - V2EX 关于“长时间计算”的请求方式的解决方案。 - V2EX [问] 求一个 ios 端 LLM 聊天应用 可调用多家 api - V2EX rebang.today 今日热榜挂了么 - V2EX 好奇, Manus 那波逆天营销最终留下了多少用户? - V2EX 全国首个!湖北为脑机接口医疗服务定价 - V2EX 为什么 AI 总在 &#34;靠谱&#34; 和 &#34;离谱&#34; 之间反复横跳? - V2EX 请教大佬谷歌验证电话号码的问题 - V2EX 如果让你们自己组装融合 [请教] 实现 Facebook 开发者接口验证是否关注我们的公共主页 - V2EX ios 有类似 cherry studio 这样的应用吗 - V2EX 过年买点啥给自己? - V2EX 有没有开源的“在线”微信备份服务? - V2EX 使用 poe 的同学,现在 Claude 3.5 都弱成这样吗 - V2EX AI 大模型佬们如何看待旧世代大数据技术? - V2EX NVIDIA 新开发者福利: 600 元 AI 课程免费学,还有机会获得 NVIDIA 培训证书 - V2EX 以当前的大模型能力,能否实现 AI 赛博飞升? - V2EX 为什么很多手机的宣传页拍照介绍的时候展示的图片都是老外呢 iqoo13 有关注的吗?
使用 OCR 和 LLM 解决实际问题---录屏题目摘录 - V2EX
lurenjiaMAX · 2025-06-16 · via 科技

1. 要求:

将提供的 Android 录屏视频中出现的题目 选项 解析摘录, 一共 755 个题目输出到一个完整的 Docx 文档中. 如有公式, 则应当以公式格式呈现. 其他干扰信息, 如 DeepSeek 广告等不应出现在解析中. 示例界面: 示例界面

视频中存在部分干扰, 如通知消息和界面动画滑动: 滑动

2. 思路

本质上是一个 OCR+结构化提取并输出的过程, 本质上并无太大难度. 难点在于如何提取视频题目帧和结构化的输出. 至于部分干扰(如遮挡等)则可不处理, 由后续对方人工按帧校对.

我的思路如下:

  • 读取每帧的画面, 并按前后帧相似度决定是否为稳定帧. 为了速度, 我们可以初步计算每个题目稳定帧之间的帧间隔, 以此为单位进行比较. 除此之外还可以事先截去状态栏和底部导航栏.
  • 使用 OCR 对提取的每帧画面进行识别, 我们可以先只保存所有的文字, 后续再处理.
    • 当然, 部分 OCR 程序是支持位置识别的, 我们只需要识别"单选", "多选"等字样就知道题目, 下面部分就是选项. 知道"解析"两个字的位置, 下面的部分自然是解析部分. 我们先不设计这种方式.
  • 对识别到的文字进行粗略的清洗, 移去明显的广告词和干扰.
  • 使用 LLM 进行结构化提取和输出, 为了保障结构化, 需要使用支持instruct的模型. 对不稳定的模型输出, 自己实现结构化提取是一件很恼火的事情...
  • 根据模板, 构造 docx 文档.

3. 选型和实现

在下面给出的实现中, 为了保障观感去掉了一些错误处理和判断.

3.1 关键帧提取
import cv2
import os
from skimage.metrics import structural_similarity as ssim
def extract_static_frames(video_path, output_dir, threshold=0.99):
	top_crop = 200
    bottom_crop = 250
    skip_frames = 6  # 每处理一次跳过的帧数
    
	cap = cv2.VideoCapture(video_path)
    success, prev_frame = cap.read()
    prev_frame = prev_frame[top_crop:-bottom_crop, :]
    
    frame_id = 0
    saved_count = 0
    while True:
        success, frame = cap.read()
        frame = frame[top_crop:-bottom_crop, :]

        # 转为灰度图做 SSIM
        gray_prev = cv2.cvtColor(prev_frame, cv2.COLOR_BGR2GRAY)
        gray_curr = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

        score, _ = ssim(gray_prev, gray_curr, full=True)

        if score > threshold:
            saved_path = os.path.join(output_dir, f"frame_{frame_id}.jpg")
            cv2.imwrite(saved_path, prev_frame)
            saved_count += 1
            # 跳过接下来的几帧
            for _ in range(skip_frames):
                cap.read()
                frame_id += 1

        prev_frame = frame
        frame_id += 1

    cap.release()
    print(f"Saved {saved_count} static frames to: {output_dir}")

对于输入格式为[email protected] 23min共 19154 帧的视频,使用 5800H 需要约 7 分钟处理完成, 最终共保存 855 帧, 基本可以做到一个题目一帧. 有精力的话可以人工从中去掉一些明显不正确的帧, 没有的话后续进行 OCR 时可进行判断.

3.2 OCR

这里我选用paddleocr来做文字识别, 不得不说即开源准确又高的东西还是非常有优势的. 它也能对中英文和公式有较好的识别率. 要识别的界面较为规整, 提取到的文字按行读取即可.

我们首先要采取一个样品, 交由大语言模型生成判断是否是题目的标志question_mark和无用的信息标志useless_mark. 之后就是简单的判断和保存了. 这一步同样不需要做的十分完美. 如下为 OCR 得到的文字样品:

<
0
答题
背题
语音
单选
6 、轻型无人机是指
质量大于 7 千克,但小于等于 116 千克
的无人机,且全马力平飞中,校正空
A
速大于 100 千米/小时(55 海里/小
时),升限大于 3000 米
质量大于等于 7 千克,但小于 116 千克
的无人机,且全马力平飞中,校正空
B
速小于 100 千米/小时( 55 海里/小
时),升限小于 3000 米
空机质量大于 7 千克,但小于等于 116
千克的无人机,且全马力平飞中,校
正空速小于 100 千米/小时
(55 海里/
小时),升限小于 3000 米
答案
c
试题详解
试题纠错
解析
该题关键点在于空机质量。参考 R1 的重量分
类,微型 0<m≤7kg ,轻型 7<m≤116kg ,小
型 116<m≤5700kg ,m>大型 5700kg 。
755
收藏
答题卡

实现:

def ocr_images_to_markdown(image_dir, output_dir):
    os.makedirs(output_dir, exist_ok=True)
    for fname in os.listdir(image_dir):
        isquestion = False
		question_mark = ["单选", "判断", "多选"]
		useless_mark = ["背题", "背题", "语音", "统计", "概述", "来", "难", "度", "使用 DeepSeek ,获取深度思考过程。", "试题详解", "试题纠错", "答题", "背题", "语音", "755"]
        image_path = os.path.join(image_dir, fname)
        md_path = os.path.join(output_dir, fname.replace(".jpg", ".md"))
        
        result_text = ocr.predict(image_path)[0]['rec_texts']
        lines = []
        for line in result_text:
	        l = str(line).strip()
	        if l in useless_mark:
		        continue
		    if l in question_mark: # 只有有该行的才视为一个正确的问题帧
			    isquestion = True
            lines.append(l)
            
        with open(md_path, "w", encoding="utf-8") as f:
            f.write("\n".join(lines))

        print(f"已提取并保存到 {md_path}")

经过处理后, 总共剩余 780 个帧. 这个结果已经足够好了, 钱不够的情况下怎么可能再人工处理呢?

3.3 LLM 处理结构化

对于给钱不够的情况下是不可能使用参数量过大的模型的. 这里我们采用qwen2.5:7b, 由搭载ollama的笔记本就可以运行.

作为小模型的参数量摆在那, 并且计算性能有限, 因而我们输入给他的提示词和用户输入需要尽量精简, 这也是为什么前面要粗略过滤一遍.

为了保障结构化, 我们需要使用第三方库instructor, 使用上十分简单, 我们只需要使用pydantic定义一个回答类即可. instructor的使用能够保障模型的输出格式正确, 但是代价为一定的模型性能下降.

class Answer(BaseModel):
    options: dict = Field(..., description="题目选项") ## 顺序很重要!!
    question: str = Field(..., description="题目内容")
    explanation: str = Field(..., description="题目解析")

    @field_validator('options')
    def options_should_have_at_least_two_keys(cls, v):
        if not all(k in v for k in ["A", "B"]):
            raise ValueError("选项内容必须包含 A, B 两个选项")
        return v
    @field_validator('options')
    def options_should_not_be_empty(cls, v):
        if any(not v.get(k) for k in ["A", "B"]):
            raise ValueError("选项内容不能有空值")
        return v
    class Config:
        json_schema_extra = {
            "example": {
                "question": "微型无人机是指?",
                "options": {
                    "A": "质量小于 7 千克的无人机。",
                    "B": "质量小于等于 7 千克的无人机。",
                    "C": "空机质量小于等于 7 千克的无人机。"
                },
                "explanation": "该题关键点在于空机质量。参考 R1 的质量分类,微型 0<m≤7kg ,轻型 7<m≤116kg ,小型 116<m≤5700kg ,m>大型 5700kg 。。"
            }
        }

在使用instructor时, 有一点需要注意: 定义的 Class 类的顺序很重要, 以 3.2 的示例样本为例, 如果按 question -> options -> explanation 的顺序定义类, 模型的输出顺序自然也会按照其进行.

那么, 模型的输出大概率就会变为:

题目: 大型无人机是指空机质量大于 5700kg 的无人机.
选项: 
A: 空机质量大于 5700kg 的无人机
B: 质量大于 5700kg 的无人机
C: 空机质量大于等于 5700kg 的无人机
解析: 该题关键点在于空机质量。参考 R1 的重量分
类,微型 0<m≤7kg ,轻型 7<m≤116kg ,小
型 116<m≤5700kg ,m>大型 5700kg 。

即便调整提示词也没有作用(如下第一和第二点). 但是按照先 options 再 question 的顺序进行模型就几乎不会出现此问题.

提示词:

system_prompt = """你是一名熟悉考试题目的内容结构化助手。
你需要将用户提供的原始 OCR 文本内容,进行结构化提取,并输出为需要的结构:
- 一共三个部分: 题目, 选项, 解析. 正确分辨题目, 选项, 解析内容.
- 题目内容中不应包含选项, 题目应当是个问句或需要填空回答的陈述句。
- 两个或三个选项, 分别是 A, B, C.
- 保留数学公式,尽量用 LaTeX 格式(如 $x^2 + y^2 = r^2$)
- 解析中, 去除无用内容, 如 DeepSeek 成绩 作答等.
"""

在使用大语言模型进行工程实践时, 顺序和提示词非常重要, 需要反复修改才可达到理想的效果. 在这个调试过程中建议使用如W&B等平台进行记录.

实现:

def process_all_ocr_markdown(input_dir, output_dir, model="qwen2.5:7b"):
    os.makedirs(output_dir, exist_ok=True)
    client = instructor.from_openai(
        OpenAI(base_url="http://localhost:11434/v1",api_key="ollama",),mode=instructor.Mode.JSON,)

    for fname in os.listdir(input_dir):
        parsed_text = parse_markdown_file(os.path.join(input_dir, fname))
        refined = ollama_structured(client, parsed_text, model=model)
        question_str = f"题目: {refined.question}"
        options_str = "\n".join([f"{k}. {v}" for k, v in refined.options.items()])
        explanation_str = f"解析: {refined.explanation}".replace("\n", "")
        refined_md = f"{question_str}\n 选项:\n{options_str}\n{explanation_str}"

        with open(os.path.join(output_dir, fname), "w", encoding="utf-8") as f:
            f.write(refined_md)
        print(f"Done: {fname}")

反复调整提示词后, 我们就可以得到较为完美的解析输出:

题目: 轻型无人机是指?
选项:
A. 质量大于 7 千克,但小于等于 116 千克的无人机,且全马力平飞中,校正空速大于 100 千米/小时(55 海里/小时),升限大于 3000 米
B. 质量大于等于 7 千克,但小于 116 千克的无人机,且全马力平飞中,校正空速小于 100 千米/小时( 55 海里/小时),升限小于 3000 米
C. 空机质量大于 7 千克,但小于等于 116 千克的无人机,且全马力平飞中,校正空速小于 100 千米/小时 (55 海里/ 小时),升限小于 3000 米
解析: 该题关键点在于空机质量。参考 R1 的质量分类,微型$0<m≤7$	ext{kg}$,轻型$7<m≤116	ext{kg}$,小型$116<m≤5700	ext{kg}$,$m>大型 5700	ext{kg}。

其实还可以提供一些样例, 供模型进行少样本学习(Few-Shot Learning), 效果会更好一些. 但是钱不够, 那这方面的测试可以等后面有兴趣了再进行.

至于公式和 Docx 输出, 使用pypandocpython-docx就可以很简单地解决, 这里就不贴代码了.

大模型通常是解决问题时懒人的大杀器, 但是如何正确使用依然是费力且玄学的事情. 在某些情况下, 提供的样本越多模型的性能反而会下降, 至于其故事就等后续另开新篇章再讲.

如果各位有更好的解决方法也可以提出来相互交流.