ひょんなことからOCRについて気になったので簡易的なものを実装してみた。
オープンソースのtesseractというOCRエンジンがあり、今回はgo向けのラッパーgosseractを利用させてもらいました。
otiai10/gosseract
構成
- go
- echo
- react
- typescript
- docker
- tesseract
- gosseract
Go
main.go
package main
import (
"fmt"
"io"
"net/http"
"os"
"strings"
"github.com/labstack/echo"
"github.com/labstack/echo/middleware"
"github.com/otiai10/gosseract/v2"
)
func main() {
e := echo.New()
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
AllowOrigins: []string{"http://localhost:3000"},
AllowHeaders: []string{echo.HeaderOrigin, echo.HeaderContentType, echo.HeaderAccept, echo.HeaderAccessControlAllowHeaders, echo.HeaderXCSRFToken},
AllowMethods: []string{"GET", "POST", "PUT", "DELETE"},
AllowCredentials: true,
}))
e.POST("/", analysisMedia)
e.POST("/movie", analysisMovie)
e.Logger.Fatal(e.Start(":8080"))
}
func analysisMedia(c echo.Context) error {
file, err := c.FormFile("file")
if err != nil {
return nil
}
src, err := file.Open()
if err != nil {
return nil
}
defer src.Close()
fileModel := strings.Split(file.Filename, ".")
fileName := fileModel[0]
extension := fileModel[1]
f, err := os.Create(fmt.Sprintf("%s_out.%s", fileName, extension))
if err != nil {
panic(err)
}
if _, err = io.Copy(f, src); err != nil {
return err
}
defer os.Remove(f.Name())
client := gosseract.NewClient()
defer client.Close()
client.SetLanguage("eng", "jpn")
client.SetImage(f.Name())
text, _ := client.Text()
return c.String(http.StatusOK, text)
}
結果
スキャンされたデータはそこそこの精度で解析できた。
スマホで撮影したレシート画像では、お話にならないデータが返却された。
感想
結論、実験程度であればかなり良いのではと思った。
出力される結果を使いたいなら整形が必要でchatGPTなどのAIで整形して使うのが一連の流れになりそう。
他のocrエンジンとしては、Google Cloud Vision APIやAzure Computer Visionなどがあり、調べたところGoogle Cloud Vision APIが一番精度良さそう。
おまけ
chatGPT-4oで画像アップロードできるようになり、画像を解析してJSONを出力できるようになった。
先ほどの画像と同じものをアップロードしJSONで出力をお願いすると、以下の内容が返っていきた。
chatGPT4o
{
"store": {
"name": "FamilyMart",
"branch": "一の橋店",
"address": "東京都港区麻布十番1-2-10",
"phone": "03-3560-3895"
},
"receipt": {
"date": "2016年9月29日",
"day_of_week": "木",
"time": "12:45",
"items": [
{
"name": "十六茶",
"price": 151,
"discount": -22
},
{
"name": "吊るしベーコン",
"price": 198,
"discount": 0
},
{
"name": "茶碗蒸し",
"price": 133,
"discount": 0
}
],
"totals": {
"subtotal": 460,
"discount": -22,
"total": 460,
"tax": 34
},
"payment": {
"given": 510,
"change": 50
}
},
"transaction": {
"register_id": "2-4986",
"clerk_id": "No. 022"
}
}
完璧ですね。。。
APIでこの実装ができるか試していないが、選択の一つになり得るかもしれない。