ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

圈一下就能让AI找到文件:屏幕感知与本地检索实战

圈一下就能让AI找到文件:屏幕感知与本地检索实战 我自己的电脑里装了不止一个AI助手但每次让它们“帮我找个文件”体验基本都是灾难。要么它打开一个文件管理器窗口开始瞎逛要么它自以为聪明地检索了一堆我根本不要的东西。问题出在哪因为你在对话框里告诉它的是一段被转译过的文字而它面对的是整个文件系统——信息损耗太大了。于是我做了一个小工具思路特别朴素别再让AI瞎逛了我在屏幕上圈一下它就知道我指的是哪个文件然后直接拿给我。这个项目说白了就是解决一件事如何让本地AI助手真正理解“你正在看哪个文件、你想要哪个文件”而不是靠猜。做法是给AI加一双会看屏幕的眼睛再加一套能定位文件的检索系统。它不是我瞎想出来的交互而是最近AI代理工具里很火的一个方向——把“屏幕理解”和“本地文件检索”拼起来。这篇文章我会把完整的原理拆解、最小可用的实现步骤以及我踩过的坑都写出来适合正在做桌面级AI助手、效率工具或者对Agent本地化能力感兴趣的朋友参考。1. 为什么“对话框里说半天”不如“屏幕上圈一下”1.1 对话式找文件的本质缺陷先说个很典型的使用场景。我经常在月底整理项目材料屏幕上正开着一个PDF报表我想让AI助手从本地找出和这个报表关联的原始Excel。正常对话是这样的“帮我找一下上个月的销售明细表。”AI会怎么做它会遍历文件名搜“销售”、搜“明细”、搜“上月”——然后给你返回二十多个结果。为什么因为你在对话框里给出的信息太少了。它不知道这个“上个月”是指哪个时间段不知道“销售明细表”是哪个项目里的更不知道你屏幕上正打开的报表其实就是一个最好的线索。这就是对话式交互的天然瓶颈自然语言在描述空间坐标和视觉对象时效率极其低下。你花一句话描述的东西在屏幕上圈一下就能精确锁定。人类之间之所以能靠语言沟通文件是因为双方有大量的共享上下文但AI助手没有。它既看不见你的屏幕也不理解“你此刻正在做什么任务”。1.2 圈选交互的本质用空间坐标消解语义模糊圈一下这个动作从信息论角度讲是把“模糊的文字意图”变成了“精确的空间坐标视觉内容”。当用户在屏幕上选中一个区域时AI去识别这个区域里有什么——是表格里的一个数字是一个图表是一个文件名是一段聊天记录——这些视觉信息就是最强的线索。我实验下来发现圈选交互的准确率比纯对话高出一大截。原因很简单人类在屏幕上的视觉焦点往往就是当前任务的锚点。你圈住一个目录窗口里的文件名AI直接就能定位那个文件你圈住Excel里的一张图表AI至少能推断出你正在处理的数据主题你圈住网页上的一句话AI可以把这句话当作搜索词去全文检索。从交互链路来看圈一下比打字快了至少三倍而且不需要组织语言。对于我这种经常开着十几个窗口的人这个优势是压倒性的。1.3 这套方案能做什么、不能做什么把核心能力拆开看这套“圈一下拿文件”的工具其实由三个能力块组成屏幕感知截取用户圈选的区域内容识别出其中的文字或视觉主题意图推断理解圈选内容指向了什么样的文件检索需求本地文件定位基于文件名、路径、内容索引快速找到目标文件并回传它擅长的是屏幕上能看到某种线索你要顺着这个线索去本地文件系统找东西。比如你圈一个客户名称、一串订单编号、一张报表标题它就能帮你把相关文件定位出来。但它也有明确的边界。如果你的需求是“帮我找去年所有跟A项目相关的合同但要排除掉作废的那几版”——这种脱离屏幕、多重条件的逻辑光靠圈选解决不了。我在设计的时候也没有指望它一步到位解决所有文件查找问题而是把它定位成对话式检索之外的第二入口。两者配合使用AI助手才真正“长眼睛”了。2. 核心环节拆解从“圈一下”到“拿到文件”中间发生了什么2.1 第一步圈选区域截屏窗口与坐标采集无论你用的是Windows、macOS还是Linux第一步动作完全一样让用户用鼠标拖拽一个矩形区域然后对这个区域进行截屏。技术实现上有一个现成的组合拳。我用的是mss这个Python库来截屏因为它速度非常快实测可以跑到每秒30帧以上完全满足“拖拽完立刻截图”的低延迟需求。坐标采集则是通过监听鼠标按下和抬起来实现——记录按下时的(x1,y1)和抬起时的(x2,y2)然后交给mss的bbox参数去截。真正麻烦的不是代码而是缩放比的问题。现在很多笔记本是2K甚至3K分辨率Windows系统推荐缩放是150%或200%。如果你用系统API拿到的鼠标坐标和你用截图库捕获到的像素坐标很可能不在同一个坐标系里。最开始我没注意这个结果截出来的图总是向上偏了一大块。解决办法是在截屏前先获取当前屏幕的缩放系数。Windows上可以读windll.shcore.GetScaleFactorForDevice或者在代码里统一采用“逻辑坐标乘以缩放系数”再做截取macOS上则是用NSScreen.backingScaleFactor。这个细节在第3章的代码里会给出来。2.2 第二步屏幕内容识别OCR与多模态模型的取舍拿到圈选区域的截图之后怎么把它变成AI能理解的信息这里有两个技术路线可以走。第一条是传统OCR路线。用PaddleOCR或者RapidOCR把截图里的文字识别出来把结果作为检索词。优点是速度快、资源占用低在不联网的办公电脑上也能跑得很舒服缺点是你只能拿到“文字”拿不到“画面中的图表结构、颜色、布局”这些视觉信息。第二条是用多模态大模型直接看图。现在很多本地模型已经支持图像输入了直接给模型一张截图的局部区域它就能告诉你这个区域大概属于什么文件、描述了什么东西。这个路线理解能力强但缺点是更吃显存、响应时间也更长需要本地有个不错的显卡或者使用远端模型。我自己的实测经验是做文件检索场景用OCR就足够解决90%的问题。因为文件系统和ChatPDF这类知识库有一个本质不同——文件名本身就是最高质量的文本索引。OCR识别出圈选区域里出现的一个关键客户名称、一个项目代号就足以在文件系统里检索到目标文件。只有当圈选区域里没有明显文字、只有纯图表时才需要换多模态模型来兜底。2.3 第三步把“识别到的内容”翻译成“文件检索意图”OCR完成后你手上有了一段文本。接下来要做的是把它变成检索条件。极端简单的做法是把整段文本直接当关键词去做模糊匹配但效果很一般。更好的做法是先提取实体特征比如客户名、项目编号、日期、文件类型词合同、报表、设计图。为了让这个判断不依赖云端API我使用本地配置的规则集做了第一层过滤也就是把常见的日期格式、中文公司名后缀、文档类型词先抓出来随后接入一个本地大模型做语义整理让它把散落的OCR文本整理成结构化检索参数。这里我多提一句——现在热门的Agent开发工具如Trae这类产品它们做的是在IDE里让Agent自动改代码其本质是一个“能读懂上下文的代理”。而我们要做的本质上也类似让AI读懂屏幕上下文再做对应动作。两者对“上下文感知”的追求是相通的。我在实际设计中提取检索意图这一步会输出一个小JSON大概长这样{ keywords: [星河科技, 报价单], date_hint: 2025-06, file_type_hint: xlsx }有了这张结构化的检索卡片下一步去文件系统里搜精度和速度都会好很多。2.4 第四步在文件系统里找到目标文件检索实现我建议直接站在系统性工具的肩上不要自己遍历目录。个人开发者的代码再快也快不过为操作系统做了深度优化的索引工具。Windows平台我强烈推荐用Everything的HTTP服务。它是一个基于NTFS日志级的文件名索引工具千万级文件都能毫秒级返回搜索结果。开启它的HTTP服务器后你可以用一行请求完成检索curl http://localhost:8080/?search星河科技%20报价单macOS平台则更简单系统自带mdfind命令它是Spotlight的底层接口不仅索引文件名还索引文件内容。配合kMDItemTextContent参数就能做内容全文搜索mdfind 报价单 星河科技比较悲伤的消息是Linux桌面生态没有默认的全局索引所以一般需要先安装recoll或者自己写一个简单的索引库。我在主力开发机上用的是Windows配合Everything的方案整体体验最流畅。如果要做跨平台也建议在系统索引工具的上层做一层适配器而不是试图自己造轮子去遍历全盘。2.5 第五步把结果“拿”给用户检索完成之后怎么“拿”也有讲究。最开始我做的是把文件路径复制到剪贴板让用户自己去资源管理器里粘贴定位。但实测下来这个流程太反人类用户拿到路径还要手动操作。后来我改成了三个动作按需使用在文件管理器中定位并选中该文件Windows上用explorer /select,加路径生成文件预览浮窗直接显示文件属性、前几行内容或者直接把文件复制到当前正在使用的目录这里值得多花一点心思的是预览浮窗。因为有时候AI返回的结果并不对一个即时可见的预览卡片能让用户0.5秒内就判断出要不要打开这个文件比直接帮你把文件丢进命令里要安全得多。毕竟本地文件检索这种操作宁可多问一步做确认也不要强制覆盖用户正在看的文件。3. 实操搭一个最小可用的“圈一下拿文件”工具3.1 技术选型与环境准备一句话总结方案栈Python做胶水mss做截图PaddleOCR做文字识别Everything/mdfind做系统级检索本地模型做意图解析。为什么会选Python因为这类工具的难点根本不在运行时性能而在开发效率。整个链路的耗时大头在OCR大概几百毫秒和文件检索几十毫秒Python完全扛得住而且它的生态最完整任何一个环节都有现成库。我的基础依赖清单如下装在Python 3.10的虚拟环境里即可pip install mss paddleocr paddlepaddle pyperclip pyautogui pillow注意PaddleOCR的安装包比较大如果你的机器配置一般或者只是做功能验证也可以用rapidocr_onnxruntime替代它对中文识别的效果同样不错而且安装包只有几十MB。Windows下记得额外装一个Everything并开启HTTP服务。打开Everything→工具→选项→HTTP服务器勾选启用端口我默认设成了8087这个端口不太容易被占建议设置一个账号密码不要裸奔在内网里。3.2 第一步实现圈选与截图圈选交互UI本身是另一个小工程。我用PyQt5画一个全屏半透明遮罩鼠标拖拽绘制矩形松开后自动截取该区域。下面这串代码实现了核心的截图逻辑import mss import mss.tools from PyQt5.QtWidgets import QApplication, QWidget from PyQt5.QtCore import Qt, QRect, QPoint from PyQt5.QtGui import QPainter, QColor, QPen class ScreenRegionSelector(QWidget): def __init__(self): super().__init__() self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint) self.setAttribute(Qt.WA_TranslucentBackground) self.setCursor(Qt.CrossCursor) self.showFullScreen() self.start_point None self.end_point None def mousePressEvent(self, event): self.start_point event.globalPos() self.end_point self.start_point self.update() def mouseMoveEvent(self, event): self.end_point event.globalPos() self.update() def paintEvent(self, event): if self.start_point and self.end_point: painter QPainter(self) painter.setPen(QPen(QColor(0, 120, 255, 200), 2)) rect QRect(self.start_point, self.end_point) painter.fillRect(rect, QColor(0, 120, 255, 30)) painter.drawRect(rect) def mouseReleaseEvent(self, event): self.end_point event.globalPos() self.close() self.grab_region() def grab_region(self): if not self.start_point or not self.end_point: return x1 min(self.start_point.x(), self.end_point.x()) y1 min(self.start_point.y(), self.end_point.y()) x2 max(self.start_point.x(), self.end_point.x()) y2 max(self.start_point.y(), self.end_point.y()) # 关键一步做DPI缩放换算否则截屏位置会偏移 scale self.devicePixelRatio() monitor { left: int(x1 * scale), top: int(y1 * scale), width: int((x2 - x1) * scale), height: int((y2 - y1) * scale), } with mss.mss() as sct: raw sct.grab(monitor) mss.tools.to_png(raw.rgb, raw.size, outputselected_region.png) print(截图已保存: selected_region.png)提示devicePixelRatio()返回的是屏幕缩放倍数。在Windows的150%缩放下这个值是1.5在macOS的Retina屏下通常是2.0。忘了乘这个系数你截出来的区域会默认偏左上这是新手最容易踩的坑之一。3.3 第二步OCR识别与文字提取拿到截图文件之后就进入第二步。PaddleOCR的调用方式非常傻瓜化在Python里几行就能完成。但要注意OCR不是简单的“图片进去、文字出来”它对图片质量有要求在代码层面最好加上图像预处理特别是当用户圈选的是小字号的文件名时。from rapidocr_onnxruntime import RapidOCR from PIL import Image, ImageEnhance ocr RapidOCR() def recognize_text(image_path): img Image.open(image_path) # 放大两倍有助于识别小字号文字 w, h img.size img img.resize((w * 2, h * 2), Image.LANCZOS) # 提高对比度白底黑字的UI截图识别率会明显提升 enhancer ImageEnhance.Contrast(img) img enhancer.enhance(1.5) img.save(enhanced_region.png) result, _ ocr(enhanced_region.png) if not result: return [] texts [] for box, text, confidence in result: # 置信度过滤低于0.55的基本可判定为误识别 if confidence and confidence 0.55: texts.append(text) return texts这段代码里有两个细节值得注意。第一个是resize放大在Windows的150%缩放屏幕上用户圈选区域往往是个不足200像素高的目录框里面每行文字可能才十几个像素高直接丢给OCR会识别出大量错别字。放大两倍之后准确率肉眼可见地从七成提到了九成以上。第二个是置信度过滤。RapidOCR返回的第三项是置信度有些光影复杂的区域会出现信誓旦旦的错字比如把“报价”识别成“报仇”。把低于0.55的结果直接丢弃可以有效减少垃圾检索词进入下一步。3.4 第三步本地模型把OCR文本变成检索条件有不少OCR结果就是一行文件名那直接把文本传给Everything就能得到结果。但如果用户圈的是聊天记录中的一句话或者表格里的一个混合文本OCR输出的是多个混杂文本直接拿去全文搜索在速度上和准确率上都不理想。所以我加了一个轻量本地模型解析层。我用的方案是Ollama部署一个支持function calling的量化模型比如qwen2.5:7b的Q4版本。在8GB内存的机器上大概占用5GB内存响应在1-2秒左右这种耗时在“用户圈选后等结果”的场景里是可以接受的。模型负责的事听上去很简单就是判断OCR文本里哪些是客户名、哪些是日期、哪些是文件后缀词import ollama def parse_search_intent(texts: list[str]) - dict: prompt f 用户圈选了屏幕上的内容OCR识别出的文本是 {texts} 现在用户要从本地找相关文件。请提取JSON返回以下字段 - keywords: 一个字符串数组包含最可能是文件名的关键词语比如客户名、项目名、编号 - date_hint: 可能的日期格式YYYY-MM没有则为null - file_type_hint: 可能的文件扩展名或文件类型没有则为null 只返回JSON不要解释。 response ollama.chat( modelqwen2.5:7b, messages[{role: user, content: prompt}], ) # 这里简单粗暴地截取JSON部分工业级实现建议用正则严格解析 import json raw_text response[message][content] json_start raw_text.find({) json_end raw_text.rfind(}) 1 return json.loads(raw_text[json_start:json_end])你没有看错传统的AI文件搜索特别依赖用户把需求说完整而现在这个方案是反过来的——你的视线位置就是最大的提示词。用户把屏幕圈出来OCR文本相当于自动生成了一个结合当前上下文的Prompt传统方式需要连续追问、模糊猜意的过程被压缩成了一个动作。即使没有本地部署大模型的条件也可以退一步只做规则解析把OCR文本按空格、换行切片然后识别“合同”“报价”“发票”这类强类型词再加日期关键词组合出搜索串。效果会略差但胜在完全离线、零依赖。3.5 第四步Everything检索与结果确认意图解析完成之后进入检索环节。我从JSON里取出keywords拼成Everything查询串然后调HTTP接口import requests import urllib.parse import json def search_files(intent: dict, top_k: int 5): keywords intent.get(keywords, []) if not keywords: return [] # Everything搜索语法用空格分隔多个关键词默认是同时包含 query .join(keywords) params { search: query, count: top_k 10, # 多取一些方便按相关性排序 json: true, } resp requests.get(http://127.0.0.1:8087/, paramsparams, timeout5) resp.raise_for_status() data resp.json() results [] for item in data.get(results, []): results.append({ path: item.get(path), name: item.get(name), type: item.get(type), }) return results[:top_k]检索回来的是路径字符串列表。这个环节有个容易忽略的重要细节一定要同时把path和name都保存下来因为后续的预览或者打开动作系统要求你传绝对路径很多文章里的示例只给了文件名结果要用的时候还得去拼接路径白白踩坑。我在工具里还加了一个排序策略如果OCR识别的文本中包含了文件名本身那这个命中结果应该排第一若只是命中文件内部内容则说明终端用户可能要找的是某个目录或分类我们就降低权重。这步判断并不复杂大家在自己的实现里可以按需调节。检索结果出来之后我会用一个小浮窗把它们列出来。用户鼠标悬停时显示文件大小和时间单击就直接打开所在目录并选中文件。从“完成圈选”到“弹出结果”整体耗时在1.5秒左右属于体感非常流畅的范畴。3.6 让AI助手“真的拿”而不是“告诉你路径”再往前走一步就可以和现有的AI助手打通了。我们在浮窗上除了“打开位置”还可以给一个按钮叫“交给AI处理”。用户点击后这个文件的路径会作为上下文直接注入到当前正在运行的AI助手里。比如我常配合一个本地文件问答模板使用“现在请把C:\Projects\星河科技\2025-06-报价单.xlsx作为上下文读取帮我整理出这个月所有超过50万的条目。”听到这里你应该能感觉到这套方案越往后做越不是“文件检索工具”而是一个给AI助手补上“眼睛”和“手”的基础设施。传统AI助手像一个只知道读文档的书呆子哪怕文件就在眼前它也不知道要用而一旦有了圈选定位能力它就成了一个能看着屏幕干活的助理。这也是为什么我要在标题里说“别再瞎逛了”——在屏幕时代AI获得指令最自然的方式不是逐字输入而是圈选。4. 常见问题与排查技巧实录任何工具都是在踩坑中变好用的我在开发时整理了下面这些真实遇到的问题方便大家少走弯路。4.1 问题速查表问题现象根本原因解决方案截屏区域总是向左上偏移高DPI下没有乘屏幕缩放系数用devicePixelRatio()换算物理坐标中文文件名识别成一堆乱码截图区域过小或字体过细放大2倍截图提高对比度之后再OCREverything返回不了文件HTTP服务未开启或有认证检查服务端设置或在URL中带上账号密码参数检索结果多但排序不对命中词分散在文件名和路径中提高文件名中关键词的匹配权重PaddleOCR在无GPU机器上非常慢跑的不是CPU优化版改用rapidocr_onnxruntime或开启mkldnn加速后台进程无法监听全局快捷键没有注册系统级热键Windows用RegisterHotKeymacOS用全局事件监听4.2 高DPI截屏坐标偏移的根因与处理这是开发中最常见的问题值得单独拎出来讲。Windows系统有逻辑坐标和物理坐标的概念。如果你的笔记本是1920×1080分辨率系统设置为150%缩放那么屏幕对于应用来说只有1280×720的逻辑区域。鼠标API返回的往往是逻辑坐标而mss截图函数默认要求物理像素坐标。解决方案一般有两种一是读取缩放因子然后乘上去这是我在代码里用的方案二是让Qt的整个程序默认声明为DPI感知即在入口处调用import ctypes ctypes.windll.shcore.SetProcessDpiAwareness(2)设置之后系统会把所有坐标统一成物理坐标不再做逻辑到物理的换算这个小坑就能彻底消失。如果忘记设置用户用着150%缩放时你截图永远少截一小块。这也是很多刚做截屏工具的人最容易碰到的“鬼打墙”。4.3 检索不出结果Everything的索引盲区另一个高频问题是明明文件名完全一致但Everything搜不到。这往往是因为Everything默认只索引NTFS卷中带USN日志的分区如果你的文件放在网络驱动器、移动硬盘或者FAT32格式的U盘上默认是不被索引的。处理办法有两个。要么在Everything设置里取消“排除可移动磁盘”的勾选要么在桌面UI中手动添加FAT卷的索引。但注意移动硬盘拔掉再插上之后Everything需要重建索引项目后期如果需要高频使用建议直接把相关文件归入固定目录或NAS。还有一种情况Everything的HTTP服务默认绑定的是localhost如果你希望局域网内另一台机器上的客户端也能调用需要在服务设置里把绑定地址改为0.0.0.0。但由于Everything的HTTP服务没有较完善的鉴权机制这里只推荐在可信网络环境下开启或者加上密码。4.4 OCR识别的准确率上不去问题不在模型在图像尝试调高OCR准确率的朋友可能会先怀疑到底是换模型还是调参数。我的经验是日常UI截图场景PaddleOCR和RapidOCR的识别能力几乎打平差距不在模型端而在图像端。大原则是先让截图内容“干净”再谈识别。圈选区域如果混入了大量阴影、渐变背景OCR准确率会骤降。有次我测试圈选深色IDE界面里的文件名识别率低到让人绝望。后来我在OCR前加了一步反向处理先把图片转成灰度再做二值化白色文字反转成黑色文字识别率立刻恢复到了正常水平。补充一下更靠谱的做法是同时跑两个版本的图像原图和反相图看哪个文字的OCR平均置信度高就用哪一份结果能显著提升深色模式下的体验。4.5 本地模型解析卡顿降级与缓存设计加了本地大模型做意图解析之后整体响应速度会受到模型推理时长的影响。实测在普通办公电脑上7B模型一次推理大概需要2到5秒考虑到圈选后用户的心理预期是1秒以内响应就必须做降级设计。我的方案是设置两级解析先走快速规则匹配如果OCR文本清晰、已经包含明显的文件类型关键词且检索结果数少于5个就不进大模型只有在快速路径查不到结果时才启用大模型做语义解析兜底。这样典型场景的响应时间可以保持在800毫秒左右复杂的模糊场景则需要3秒上下符合交互预期。另外一个容易被人忽视的小技巧是OCR结果可以在当前会话内做缓存。如果用户连续圈选了同一目录下的几个文件识别出的前缀路径往往是相同的把它们缓存起来下一次圈选时优先搜索该目录不仅快而且准确率更高——好比你让一个助手连续看了同一片区域的屏幕它自然是越找越熟练。说实话做这个项目的过程让我对“AI助手如何融入人的工作流”有了不一样的理解。很多人折腾各种Agent框架、配置复杂的任务链但我越来越觉得智能程度再高的模型如果连用户此刻在看什么都无法感知那它很大程度上还是在盲人摸象。把屏幕变成AI的一个输入通道不需要AI接管整台电脑只需要一个圈选动作和一次本地检索AI就已经从“瞎逛”变成了“看哪儿找哪儿”。这个方向我后续还会继续做下去比如把语音、截屏和文件操作整合在一起把“圈选”扩展成一种更通用的指令输入方式。如果你也在尝试给自己的AI工具加一点“视觉”强烈建议把这个方案跑一遍应该会对“上下文感知”这件事产生新的灵感。
返回列表