iOS の家計簿アプリをつくる(レシート読み取り編)

advent_calendar アドベントカレンダー

はじめに

こちらは個人開発アプリができるまで by am10 Advent Calendar 2024の 11 日目の記事です。

11 日目は Vision を使ってレシート読み取り処理を作成します。
完成形はこんな感じです。

1

方針決め

このアプリで一番難しいのがレシート読み取り機能でしょう。レシートには様々な形式がありレシートの大量の文字から情報を抽出する必要があります。
すべてのレシートに対応するのは困難なので方針を決めます。

  1. 読み取る範囲を決める
  2. 取得する情報は品名と金額のみ
  3. 読み取るレシートの形式は下記 2 つに限定する

2

今回は私がよく使うスーパーとコーナンに限定しました(他も必要であればカスタマイズする)。

処理手順

実際のレシート読み取りの処理手順はこんな感じです。

  1. カメラでレシートを撮影する
  2. 読み取る範囲を決める
  3. 2 の範囲で画像をトリミングする
  4. Vision にトリミングした画像を渡す
  5. 文字列を読み取る
  6. 読み取ったデータを 1 行ごとにわける
  7. 文字列を解析し品名と金額を取得する
  8. 家計簿データに変換する

文字の読み取り

処理手順も決まったのでレシート読み取り機能を実装していきます。

カメラで撮影

カメラを使うために TARGETS > Info に「Privacy - Camera Usage Description」を追加します。

4

SwiftUI にはカメラを起動する機能がないようなので UIImagePickerController を使って View を作成する必要があります。
CmaeraView.swift ファイルを作成して下記のように実装します。

これで撮影処理は完了です。

読み取り範囲指定

次に撮影した写真の読み取り範囲を指定する処理を実装します。SwiftUI でもできると思うのですが難しかったので UIKit を使います。
ReceiptOCRViewController.swift ファイルを作成し下記のように実装します。

こんな感じです。

5

処理の流れは下記です。

  1. touchedPoint で赤枠のどこをタッチしたか判定
  2. touchesMoved でどの方向に拡大・縮小するか判定して赤枠をリサイズ

範囲指定はできたので次に指定の範囲で画像を切り抜きます。画面上(UIImageView 上)で指定した範囲は実際の画像(UIImage 上)の範囲とは異なるので下記のように変換する必要があります。
下記のようにすれば読み取るボタン押下で指定範囲の画像を切り抜けるようになります。

参考

CGImage.cropping()の注意点 - Qiita
はじめにこの前リリースしたAR Mini SketchというアプリでUIImageView上の画像をユーザーが指定した範囲で切り抜くという処理があるが、画像によっては指定通りに切り抜かれないという…

文字列の読み取り(1 行ごと)

指定範囲での画像のトリミングができたのでいよいよ Vision を使って文字列を読み取っていきます。
下記のようにすれば指定範囲の文字列を 1 行ごとに読み取ることができます。

ポイントは下記です。Vision へ渡す際によくわからないですが画像の向きがおかしくなるので向きを補正します。

formatData で取得した文字データを下記のように処理して 1 行ごとの文字列に整形しています。

  1. x 座標で昇順にソートする
  2. 一番左の文字データの矩形を画像の横幅いっぱいに伸ばしその範囲に中心座標がある文字データをグルーピングする
  3. グルーピングした文字データを半角スペースで連結する
  4. y 座標で昇順にソートする

View 作成

読み取り処理ができたので SwiftUI で使えるようにします。
下記のようにデリゲートで呼び出し元に通知できるようにします。

ReceiptOCRView.swift ファイルを作成し下記のように実装します。

このまま RegisterView に表示処理を書いてもいいのですが複雑になりそうなので別クラスにわけます。
ReadReceiptButton.swift ファイルを作成し下記のように実装します。

RegisterView を下記のように修正します。

文字列の解析

登録画面からレシート読み取り画面を表示できるようになったので次は読み取った文字列から品名と金額を取得します。
ReceiptDataFormatter.swift ファイルを作成し下記のように実装します。

これで万代とコーナン形式のレシートデータから品名と金額を取得できるようになりました。他のレシートにも対応したい場合は ReceiptDataFormatter に処理を追加していきます。

家計簿データに整形する

最後に取得した品名と金額から家計簿データに変換します。購入日、項目、サブ項目は登録画面で入力されている値を設定するようにします。
RegisterView のレシート読み取りの処理を下記のように修正します。

これでレシート読み取り機能は完成です(消費税に関しては別途手入力するか無視でいいかなと)。

おわりに

このアプリの一番の難所が終わりました。
サンプルのレシートは読み取れるようになりましたが他のレシートも読み取れるかはわからないので ReceiptDataFormatter の処理はアプリを使いつつ調整していければと思います。
必要であれば他の店のレシートも読み取れるように修正していきます。

明日はエラーハンドリングなど細かい調整をする予定です。

Amazon.co.jp

コメント

タイトルとURLをコピーしました