You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unity 3D中Tesseract导入异常致OCR调用报错的解决求助

Unity中Tesseract OCR调用异常的解决思路

问题概述

通过Visual Studio NuGet安装Tesseract后,将相关文件复制到Unity项目Assets/Plugins目录,并放置eng.traineddata资源,运行拍照识别代码时抛出异常:

OCR Error: Exception has been thrown by the target of an invocation.

原示例代码

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;
using Newtonsoft.Json.Linq;
using Tesseract;

public class CaptureCards : MonoBehaviour
{
    public RawImage cameraFeed;
    public Button captureButton;
    public Text cardDetailsText;
    private WebCamTexture webCamTexture;
    private HttpClient httpClient;

    void Start()
    {
        StartCameraFeed();
        httpClient = new HttpClient();
        captureButton.onClick.AddListener(CaptureAndProcess);
    }

    void StartCameraFeed()
    {
        WebCamDevice[] devices = WebCamTexture.devices;
        if (devices.Length == 0)
        {
            Debug.LogError("No camera devices found.");
            return;
        }

        Debug.Log("Available cameras:");
        foreach (var device in devices)
        {
            Debug.Log(device.name);
        }

        string selectedCamera = devices[0].name;
        Debug.Log("Using camera: " + selectedCamera);
        webCamTexture = new WebCamTexture(selectedCamera);
        cameraFeed.texture = webCamTexture;
        cameraFeed.material.mainTexture = webCamTexture;
        webCamTexture.Play();
    }

    async void CaptureAndProcess()
    {
        if (webCamTexture == null || !webCamTexture.isPlaying)
        {
            cardDetailsText.text = "Camera feed not available.";
            return;
        }

        Texture2D captureImage = new Texture2D(webCamTexture.width, webCamTexture.height);
        captureImage.SetPixels(webCamTexture.GetPixels());
        captureImage.Apply();

        string tempPath = Path.Combine(Application.persistentDataPath, "captureCard.png");
        Texture2D preprocessedImage = PreprocessImage(captureImage);
        File.WriteAllBytes(tempPath, preprocessedImage.EncodeToPNG());
        Debug.Log("Image saved at: " + tempPath);

        string cardName = await RecognizeText(tempPath);
        if (!string.IsNullOrEmpty(cardName))
        {
            JObject cardData = await FetchCardDetails(cardName);
            if (cardData != null)
            {
                DisplayCardDetails(cardData);
                SaveCardDetails(cardData);
            }
            else
            {
                cardDetailsText.text = "Card not Found";
            }
        }
        else
        {
            cardDetailsText.text = "Failed to recognize card name";
        }
    }

    Texture2D PreprocessImage(Texture2D original)
    {
        int targetWidth = 800;
        int targetHeight = (int)(original.height * (800.0f / original.width));
        Texture2D resizedImage = new Texture2D(targetWidth, targetHeight);

        for (int y = 0; y < targetHeight; y++)
        {
            for (int x = 0; x < targetWidth; x++)
            {
                Color pixel = original.GetPixelBilinear((float)x / targetWidth, (float)y / targetHeight);
                resizedImage.SetPixel(x, y, pixel);
            }
        }
        resizedImage.Apply();

        Color[] pixels = resizedImage.GetPixels();
        for (int i = 0; i < pixels.Length; i++)
        {
            float gray = pixels[i].grayscale;
            pixels[i] = new Color(gray, gray, gray);
        }
        resizedImage.SetPixels(pixels);
        resizedImage.Apply();

        return resizedImage;
    }

    async Task<string> RecognizeText(string imagePath)
    {
        string resultText = string.Empty;
        string tessDataPath = Path.Combine(Application.streamingAssetsPath, "tessdata");
        Debug.Log($"StreamingAssets Path: {Application.streamingAssetsPath}");
        Debug.Log($"File Exists: {File.Exists(Path.Combine(Application.streamingAssetsPath, "tessdata", "eng.traineddata"))}");

        await Task.Run(() =>
        {
            try
            {
                if (!Directory.Exists(tessDataPath))
                    throw new DirectoryNotFoundException($"Tesseract data directory not found: {tessDataPath}");
                if (!File.Exists(Path.Combine(tessDataPath, "eng.traineddata")))
                    throw new FileNotFoundException("Tesseract trained data file not found (eng.traineddata).");
                if (!File.Exists(imagePath))
                    throw new FileNotFoundException($"Image file not found: {imagePath}");

                using (var engine = new TesseractEngine(tessDataPath, "eng", EngineMode.Default))
                {
                    using (var img = Pix.LoadFromFile(imagePath))
                    {
                        using (var page = engine.Process(img, PageSegMode.SingleBlock))
                        {
                            resultText = page.GetText().Trim();
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Debug.LogError($"OCR Error: {ex.Message}\n{ex.StackTrace}");
            }
        });

        return resultText;
    }

    async Task<JObject> FetchCardDetails(string cardName)
    {
        try
        {
            string apiUrl = $"https://api.scryfall.com/cards/named?exact={UnityWebRequest.EscapeURL(cardName)}";
            string response = await httpClient.GetStringAsync(apiUrl);
            return JObject.Parse(response);
        }
        catch
        {
            return null;
        }
    }

    void DisplayCardDetails(JObject cardData)
    {
        string name = cardData["name"].ToString();
        string type = cardData["type_line"].ToString();
        string manaCost = cardData["mana_cost"].ToString();
        string description = cardData["oracle_text"].ToString();
        cardDetailsText.text = $"Name: {name}\nType: {type}\nMana Cost: {manaCost}\nDescription: {description}";
    }

    void SaveCardDetails(JObject cardData)
    {
        string savePath = Path.Combine(Application.persistentDataPath, "savedCards.json");
        JArray savedCards = File.Exists(savePath)
            ? JArray.Parse(File.ReadAllText(savePath))
            : new JArray();
        savedCards.Add(cardData);
        File.WriteAllText(savePath, savedCards.ToString());
    }

    void OnDestroy()
    {
        webCamTexture.Stop();
        httpClient.Dispose();
    }
}

解决步骤

1. 修正Tesseract库导入方式

  • 避免直接从NuGet复制文件,Unity对.NET运行时环境有特定要求,建议使用Unity兼容的预编译Tesseract版本,或者通过Unity Package Manager安装适配包
  • 手动导入时,需将库文件放置到对应平台目录(如Plugins/x86_64、Plugins/Android),并在Inspector面板设置正确的平台兼容选项(取消勾选无关平台)
  • 删除NuGet导入的冗余依赖文件,避免版本冲突

2. 修复tessdata路径与访问权限

  • 移动端无法直接通过File类访问StreamingAssets目录,需先将eng.traineddata复制到persistentDataPath再使用,修改RecognizeText方法:
    async Task<string> RecognizeText(string imagePath)
    {
        string resultText = string.Empty;
        string targetTessPath = Path.Combine(Application.persistentDataPath, "tessdata");
        string trainedDataPath = Path.Combine(targetTessPath, "eng.traineddata");
    
        // 复制tessdata到持久化目录
        if (!Directory.Exists(targetTessPath))
        {
            Directory.CreateDirectory(targetTessPath);
            string sourcePath = Path.Combine(Application.streamingAssetsPath, "tessdata/eng.traineddata");
    
            #if UNITY_EDITOR || UNITY_STANDALONE
            File.Copy(sourcePath, trainedDataPath, overwrite: true);
            #else
            UnityWebRequest request = UnityWebRequest.Get(sourcePath);
            await request.SendWebRequest();
            File.WriteAllBytes(trainedDataPath, request.downloadHandler.data);
            #endif
        }
    
        await Task.Run(() =>
        {
            try
            {
                using (var engine = new TesseractEngine(targetTessPath, "eng", EngineMode.Default))
                {
                    using (var img = Pix.LoadFromFile(imagePath))
                    {
                        using (var page = engine.Process(img, PageSegMode.SingleBlock))
                        {
                            resultText = page.GetText().Trim();
                        }
                    }
                }
            }
            catch (TargetInvocationException ex)
            {
                Debug.LogError($"OCR Inner Error: {ex.InnerException.Message}\n{ex.InnerException.StackTrace}");
            }
            catch (Exception ex)
            {
                Debug.LogError($"OCR Error: {ex.Message}\n{ex.StackTrace}");
            }
        });
    
        return resultText;
    }
    

3. 捕获深层异常信息

  • 原异常是包装后的TargetInvocationException,需捕获内部异常才能看到真实错误原因(如库文件缺失、架构不兼容),在catch块中添加专门处理逻辑

4. 优化图像预处理

  • 在灰度化基础上添加二值化处理,强化文本与背景对比度,提升OCR准确率:
    // 替换原灰度化代码
    float threshold = 0.6f;
    for (int i = 0; i < pixels.Length; i++)
    {
        float gray = pixels[i].grayscale;
        pixels[i] = gray >= threshold ? Color.white : Color.black;
    }
    

内容的提问来源于stack exchange,提问作者Kayle Brown

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 18:07:33