拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

Android 扫描识别车牌与身份证:TaoToken 统一 Key 接入配置与验证

Android 扫描识别车牌与身份证:TaoToken 统一 Key 接入配置与验证

1. Android 扫描识别车牌与身份证:从相机回调到统一 Key 接入

Android 端做车牌和身份证扫描识别,最麻烦的往往不是相机取景,而是识别能力怎么接、Key 怎么管、多个识别接口怎么统一。我最近在做一个案件信息录入类的 App,需要在同一个页面里既拍身份证又拍车牌,一开始每个识别能力单独申请一套凭证,代码里散落着好几处初始化逻辑,改一个环境要翻半天。后来把识别请求统一走 TaoToken 的 API 通道,用一套 Key 管理所有调用,配置集中到config.toml和settings.json里,Android 侧只负责把拍到的图片转成 Base64 或文件流发出去,识别结果解析逻辑基本不用动。

这篇就按这个思路写:先讲清楚 Android 扫描识别车牌、身份证这个场景里常见的接入痛点,再把 TaoToken 的前置准备、可复制的配置文件骨架、CC Switch 和 Cline 的配置片段给出来,最后用一次真实的扫描识别请求做验证,并列出我踩过的几个坑。适合已经在写 Android 相机回调、但被多套 Key 和识别接口配置搞烦的开发者。

TaoToken 在这里的角色是一个统一的 API 通道,把车牌识别、身份证识别这类能力收敛到同一个入口,你只需要维护一份 Key 和一份配置。官网地址是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 入口是 https://taotoken.net/api ,注意 API 地址不带 UTM 参数。

2. TaoToken 前置准备:Key、模型与通道确认

在写 Android 代码之前,先把服务端这侧的东西准备好,不然后面调试会分不清是 App 的问题还是 Key 的问题。

第一步是拿到 API Key。进入控制台的 API Keys 页面创建一个新的 Key,建议按项目命名,比如android-ocr-demo,方便后面区分。创建后立刻复制保存,页面刷新后就看不到完整值了。控制台入口:https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite ,API Keys 页面:https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite 。

第二步是确认你要用的识别能力对应的模型名。TaoToken 的模型列表里会区分通用对话模型和视觉/识别类模型,车牌和身份证属于图像理解范畴,选支持图片输入的模型。你可以在模型对话页面先手动传一张车牌图试试,确认返回结构长什么样,再去写 Android 代码。模型对话入口:https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=chat&utm_campaign=rewrite 。

第三步是确认请求格式。TaoToken 的 API 兼容常见的多模态消息结构,图片可以用 Base64 内联,也可以用可访问的 URL。Android 端拍完照拿到的是本地文件,最省事的做法是读成字节数组再 Base64 编码,拼进image_url字段。这里要注意 Base64 会让请求体变大,身份证正面照压缩到 200KB 以内比较稳。

如果你打算长期在 Android 项目里做识别和编码辅助,可以看一下 Coding Plan,它更适合持续性的开发调用场景:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite 。接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,遇到字段不确定的时候以文档为准。

3. 可复制配置:config.toml 与 settings.json 骨架

Android 项目本身不直接读 toml,但很多配套工具链(比如 CC Switch、Cline)用 toml 和 json 管理通道配置。我习惯把识别通道的配置抽出来,Android 侧只读一个BuildConfig里的 baseUrl 和 key,其余交给工具链。

先给config.toml骨架,放在项目根目录或者工具配置目录下:

# config.toml # TaoToken 统一通道配置,Android 识别项目示例 [provider] name = "taotoken" base_url = "https://taotoken.net/api" api_key = "sk-你的Key填这里" timeout_seconds = 60 [models] # 车牌识别用的视觉模型 plate = "gpt-4o" # 身份证识别用的视觉模型 idcard = "gpt-4o" [request] # 图片压缩质量,0-100 image_quality = 40 # 单次请求最大图片字节数 max_image_bytes = 512000 # 是否开启方向检测 detect_direction = true [logging] level = "info" save_raw_response = false

再给settings.json骨架,这个更适合放在 Android 的 assets 或者工具链的配置目录:

{ "taotoken": { "baseUrl": "https://taotoken.net/api", "apiKey": "sk-你的Key填这里", "endpoints": { "chat": "/v1/chat/completions", "models": "/v1/models" }, "ocr": { "plateModel": "gpt-4o", "idCardModel": "gpt-4o", "imageQuality": 40, "maxImageBytes": 512000 }, "retry": { "maxAttempts": 3, "backoffMs": 800 } } }

Android 侧读取的时候,把baseUrl和apiKey通过build.gradle的buildConfigField注入,避免硬编码在 Java 里。这样切换环境只改一处。

CC Switch 的配置片段,加到它的 provider 列表里:

[[providers]] name = "taotoken-ocr" base_url = "https://taotoken.net/api" api_key = "sk-你的Key填这里" models = ["gpt-4o"]

Cline 的配置片段,在它的设置里填:

{ "cline.apiProvider": "openai-compatible", "cline.openAiBaseUrl": "https://taotoken.net/api", "cline.openAiApiKey": "sk-你的Key填这里", "cline.openAiModelId": "gpt-4o" }

这几份配置的核心就一句话:baseUrl 统一指向https://taotoken.net/api,Key 只维护一份,模型名按识别类型区分。

4. Android 侧扫描识别请求:从相机回调到 API 调用

配置好了,接下来是 Android 里真正发请求的部分。我沿用原来相机回调的结构,但把识别方法换成走 TaoToken 的 HTTP 请求。

先看相机回调,这部分和你原来的写法基本一致,只是把识别方法名换掉:

@Override protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == REQUEST_CODE_CAMERA && resultCode == Activity.RESULT_OK) { if (data != null) { String contentType = data.getStringExtra(CameraActivity.KEY_CONTENT_TYPE); String filePath = FileUtils.getSaveFile(getApplicationContext()).getAbsolutePath(); if (!TextUtils.isEmpty(contentType)) { if (CameraActivity.CONTENT_TYPE_ID_CARD_FRONT.equals(contentType)) { recognizeByTaoToken("idcard", filePath); } if (CameraActivity.CONTENT_TYPE_GENERAL.equals(contentType)) { recognizeByTaoToken("plate", filePath); } } } } }

核心的识别方法,把图片转 Base64 后拼多模态消息:

private void recognizeByTaoToken(String type, String filePath) { new Thread(() -> { try { File file = new File(filePath); byte[] bytes = new byte[(int) file.length()]; FileInputStream fis = new FileInputStream(file); fis.read(bytes); fis.close(); String base64 = Base64.encodeToString(bytes, Base64.NO_WRAP); String prompt = "plate".equals(type) ? "识别这张图片中的车牌号码和车牌颜色,返回JSON:{\"number\":\"\",\"color\":\"\"}" : "识别这张身份证正面,返回JSON:{\"name\":\"\",\"gender\":\"\",\"ethnic\":\"\",\"idNumber\":\"\",\"address\":\"\"}"; JSONObject imageUrl = new JSONObject(); imageUrl.put("url", "data:image/jpeg;base64," + base64); JSONObject textPart = new JSONObject(); textPart.put("type", "text"); textPart.put("text", prompt); JSONObject imagePart = new JSONObject(); imagePart.put("type", "image_url"); imagePart.put("image_url", imageUrl); JSONArray content = new JSONArray(); content.put(textPart); content.put(imagePart); JSONObject message = new JSONObject(); message.put("role", "user"); message.put("content", content); JSONArray messages = new JSONArray(); messages.put(message); JSONObject body = new JSONObject(); body.put("model", "gpt-4o"); body.put("messages", messages); body.put("max_tokens", 512); URL url = new URL(BuildConfig.TAOTOKEN_BASE_URL + "/v1/chat/completions"); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("POST"); conn.setRequestProperty("Content-Type", "application/json"); conn.setRequestProperty("Authorization", "Bearer " + BuildConfig.TAOTOKEN_API_KEY); conn.setDoOutput(true); conn.setConnectTimeout(15000); conn.setReadTimeout(60000); OutputStream os = conn.getOutputStream(); os.write(body.toString().getBytes("UTF-8")); os.flush(); os.close(); int code = conn.getResponseCode(); InputStream is = code == 200 ? conn.getInputStream() : conn.getErrorStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(is, "UTF-8")); StringBuilder sb = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { sb.append(line); } reader.close(); conn.disconnect(); final String result = sb.toString(); runOnUiThread(() -> mContent.setText(result)); } catch (Exception e) { Log.e("TaoTokenOCR", "request failed", e); } }).start(); }

BuildConfig.TAOTOKEN_BASE_URL和BuildConfig.TAOTOKEN_API_KEY在build.gradle里注入:

android { defaultConfig { buildConfigField "String", "TAOTOKEN_BASE_URL", "\"https://taotoken.net/api\"" buildConfigField "String", "TAOTOKEN_API_KEY", "\"sk-你的Key填这里\"" } }

这样 Java 代码里不出现明文 Key,切换环境也只改 gradle 一处。

5. 验证请求与预期返回

配置和代码都就位后,做一次最小验证。我建议先用 curl 在电脑上跑一遍,确认 Key 和模型没问题,再去跑 Android。

curl -X POST "https://taotoken.net/api/v1/chat/completions" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer sk-你的Key" \ -d '{ "model": "gpt-4o", "messages": [ { "role": "user", "content": [ {"type": "text", "text": "识别这张图片中的车牌号码和车牌颜色,返回JSON"}, {"type": "image_url", "image_url": {"url": "data:image/jpeg;base64,/9j/4AAQ..."}} ] } ], "max_tokens": 512 }'

预期返回结构大致是这样:

{ "id": "chatcmpl-xxx", "object": "chat.completion", "choices": [ { "index": 0, "message": { "role": "assistant", "content": "{\"number\":\"京A12345\",\"color\":\"蓝色\"}" }, "finish_reason": "stop" } ], "usage": { "prompt_tokens": 820, "completion_tokens": 24, "total_tokens": 844 } }

拿到这个返回,说明通道是通的。Android 侧把choices[0].message.content解析出来,再按你原来的 JSON 解析逻辑取number和color就行。身份证识别同理,只是 prompt 换成身份证字段,返回的 content 里是姓名、性别、民族、身份证号、住址。

实测下来,一张压缩到 150KB 左右的车牌图,从发出请求到拿到结果大概 2 到 4 秒,取决于网络。身份证正面因为字段多,稍微慢一点,但也在可接受范围。

6. 本篇常见错排查

第一个坑是 Base64 前缀漏了。image_url的 url 字段必须是data:image/jpeg;base64,开头,少了这个前缀,服务端会当成普通 URL 去请求,直接报错。我一开始就是只传了 Base64 字符串,排查了半天。

第二个坑是图片太大。Android 相机默认拍出来的图可能好几 MB,直接 Base64 编码后请求体会超过限制,返回 413 或者超时。解决办法是在拍照后先压缩,或者用BitmapFactory采样后再编码。我一般把长边压到 1280 像素,质量 40,出来的图 100 到 200KB。

第三个坑是 Key 权限。如果你在控制台创建 Key 的时候限制了模型范围,而请求里用的模型不在范围内,会返回 403。去 API Keys 页面确认一下这个 Key 允许的模型列表。

第四个坑是并发。Android 里如果在主线程发请求会直接崩,必须放到子线程。上面代码里用了new Thread,如果你用 OkHttp 或者 Retrofit,记得回调里切回主线程更新 UI。

第五个坑是返回内容不是纯 JSON。模型有时候会在 JSON 外面包一层说明文字,比如「识别结果如下:{...}」。解析前先做一次字符串截取,找到第一个{和最后一个}之间的内容再解析,能省很多事。

排障相关的入口放在这里:API Keys 页面 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite ,接入文档 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite 。如果你在 Android 里做长期编码和识别混合的场景,Coding Plan 会更合适:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite 。想先手动验证模型对车牌和身份证的识别效果,直接去模型对话页面传图试:https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=chat&utm_campaign=rewrite 。

最后补一个实用技巧:把车牌和身份证的 prompt 模板抽成常量放在settings.json里,Android 侧读配置拼 prompt,这样以后换识别字段或者调整返回格式,不用改 Java 代码,改配置就行。

返回列表