ArtikelLokal AI

OCR lokalt med AI: läs skannade dokument offline och kontrollera texten

Skicka en skannad sida till en visionsmodell i Ollama, läs samma sida med Tesseract och jämför båda mot tio rader du har skrivit av själv. Då ser du både vad som saknas och vad modellen har hittat på.

Publicerad · Senast granskad

En mörk metallkub med ett grått pappersark i en springa på ovansidan. Genom kubens öppna sida syns en lodrät pelare med en stapel tunna, vitgröna ljusränder i grönt sken mot svart bakgrund.
Sidan går in och text kommer ut. Om texten verkligen står på papperet avgör ditt eget facit.

Kvitton, gamla brev och inskannade avtal blir sökbara först när bilden har blivit text. Du kan göra det på den egna datorn. En visionsmodell i Ollama tar emot bilden och returnerar text, och den klassiska OCR-motorn Tesseract gör samma sak utan språkmodell. Skillnaden som spelar roll är hur de felar: Tesseract tappar eller förvränger tecken, medan en språkmodell kan skriva en fullt läsbar mening som inte stod på papperet. Den här artikeln visar hur du skickar sidan till modellen och hur du fångar den sortens fel med ett eget facit.

Välj en modell vars kort faktiskt talar om text

Vid kontrollen den 19 oktober 2026 var 51 av de 256 modellerna i Ollamas katalog märkta med Vision. Att en modell kan beskriva en bild betyder inte att den kan läsa av en sida ordagrant, så utgå från vad modellkortet själv påstår. Kortet för glm-ocr beskriver en OCR-modell för dokumentförståelse med 0,9 miljarder parametrar och lyfter fram tabeller, formler och svåra layouter. Taggen latest är 2,7 GB att ladda ned och q8_0 1,8 GB. Hur mycket minne modellen sedan tar i drift beror på din maskin; räkna på det med hårdvaruguiden.

Två saker står inte på kortet: vilka språk modellen läser och om den klarar handskrift. Det officiella modellkortet på Hugging Face listar åtta språk i sina metadata: kinesiska, engelska, franska, spanska, ryska, tyska, japanska och koreanska. Svenska finns inte med, och handskrift nämns inte där heller. Svenska tecken som å, ä och ö är alltså något ditt prov ska visa, inte något du kan förutsätta. Samma kort anger MIT-licens för modellen och Apache 2.0 för en medföljande layoutkomponent; kontrollera licensen igen när du laddar ned, eftersom kort kan ändras.

Ollamas egen visionsdokumentation använder gemma4 i sina exempel. Gemma 4-kortet nämner OCR och liten text som uppgifter där en högre bildbudget behövs, med budgetar från 70 till 1 120 bildtoken. Observera att modellen också har taggarna gemma4:cloud och gemma4:31b-cloud. En molntagg körs inte på din dator.

Gör lokalt till något du kontrollerar

Enligt Ollamas FAQ ser Ollama inte dina prompter eller data när modellen körs lokalt, medan molnmodeller bearbetas på Ollamas servrar. Servern binder som standard till 127.0.0.1 på port 11434. Vill du utesluta molnet helt sätter du OLLAMA_NO_CLOUD=1 och startar om Ollama; loggen ska då visa Ollama cloud disabled: true. Ladda ned modellen först, koppla sedan bort nätverket och kör provet nedan. Fungerar det offline har du ett eget belägg, inte bara ett löfte i dokumentationen. Bredare kontroller av loggar och synkade mappar finns i guiden till den tekniska datagränsen.

Tre sätt att skicka sidan

Skanna en sida med tryckt text till en PNG-fil, kalla den sida.png och lägg den i en tom mapp. Det snabbaste provet är terminalen, med den prompt modellkortet anger:

ollama pull glm-ocr
ollama run glm-ocr Text Recognition: ./sida.png

För skript går du via /api/chat. API-referensen beskriver fältet images i ett meddelande som en lista med inbäddade bilder, och visionsdokumentationen anger att REST-API:t förväntar sig base64-kodad bilddata. Ollamas Python-bibliotek tar även en filsökväg direkt, men exemplet nedan använder bara standardbiblioteket så att du ser exakt vad som skickas. Standardvärdet för stream är true; vi sätter det till false för att få hela svaret i ett stycke, och temperaturen till 0 för att göra omkörningar jämförbara.

import base64, json, urllib.request

with open("sida.png", "rb") as f:
    bild = base64.b64encode(f.read()).decode("ascii")

body = {
    "model": "glm-ocr",
    "messages": [{"role": "user", "content": "Text Recognition:", "images": [bild]}],
    "stream": False,
    "options": {"temperature": 0},
}
req = urllib.request.Request(
    "http://localhost:11434/api/chat",
    json.dumps(body).encode("utf-8"),
    {"Content-Type": "application/json"})
with urllib.request.urlopen(req, timeout=300) as svar:
    text = json.load(svar)["message"]["content"]

with open("modell.txt", "w", encoding="utf-8") as f:
    f.write(text)
print(text)

Adressen är localhost, så anropet går till din egen server. Timeouten på 300 sekunder är en vald stoppgräns, inget mått på hur snabbt din dator svarar.

Läs samma sida med Tesseract

Tesseract är en öppen OCR-motor under Apache 2.0 som körs från kommandoraden. Enligt den officiella dokumentationen kommer Windows-installeraren från UB Mannheim, Debian och Ubuntu har paketet tesseract-ocr med språkpaket enligt mönstret tesseract-ocr- plus en trebokstavskod, och på macOS installerar du med brew install tesseract. Svenska heter swe i språkdatan. Dokumentationen beskriver också en avvägning: tessdata_fast är snabbast men minst exakt, tessdata_best mest exakt men långsammast.

tesseract sida.png tesseract -l swe

Kommandot skriver resultatet till tesseract.txt. Använder du - i stället för filnamnet hamnar texten i terminalen.

Facit på tio rader: hitta inlagd och saknad text

Välj tio rader på sidan, gärna med siffror, namn på orter och ord med å, ä och ö. Skriv av dem för hand i facit.txt, en rad per rad på papperet. Det tar några minuter, och det är den del av provet som inte kommer från en maskin. Kör sedan jämförelsen:

import difflib

def rader(fil):
    with open(fil, encoding="utf-8") as f:
        return [r.strip() for r in f if r.strip()]

facit = rader("facit.txt")
for namn in ["modell.txt", "tesseract.txt"]:
    utdata = rader(namn)
    print("==", namn)
    diff = difflib.unified_diff(facit, utdata, "facit", namn, lineterm="", n=0)
    for r in list(diff)[2:]:  # hoppa över filhuvudet
        if r[:1] in "+-":
            print(("INLAGD  " if r[0] == "+" else "SAKNAS  ") + r[1:])

Utdata innehåller hela sidan medan facit bara har tio rader, så de övriga raderna visas också som INLAGD. Gå därför igenom listan för hand. En rad som stämmer nästan exakt med en facitrad är ett läsfel. En rad som inte motsvarar något på papperet är det som ska oroa dig: modellen har skrivit text som inte finns. Titta extra noga på rader som är avbrutna, suddiga eller vid en vikning i papperet: står där en hel, välformulerad mening i utdata har något fyllts i.

Räkna sedan för varje motor hur många av de tio raderna som återges exakt, hur många som har läsfel och hur många som saknas. Skriv upp modelltagg, Ollamas version och skanningens upplösning bredvid siffrorna. Byter du modell eller tagg gör du om provet; resultatet gäller bara den kombination du faktiskt har kört.

Från text till sökbart arkiv

När texten håller kan den gå vidare till ett lokalt dokumentindex. Hur du mäter att sökningen hittar rätt beskriver artikeln om lokal dokumentsökning med facit på tio frågor. Har du ljudinspelningar i stället för papper hör de hemma i artikeln om Whisper lokalt.

Källor och metod

Källorna kontrollerades 19 oktober 2026. Modellval, kodexempel och facitmetod är artikelns egna. Inga körningar med Ollama eller Tesseract har gjorts av redaktionen, och inga felandelar anges; jämförelsen mot facit är ditt prov.