はじめに
前前回、家計簿アプリの設計を行いました(AIがやりました)
前回はOCRの実装を試みましたがうまく動作しません。
今回もOCRの実装をしていきます。
プロンプト
商品の名称、金額は読み込めているが日付、合計金額の読み込みが出来ていない、商品一覧に不要な情報が多く入っている。
こちらを改善するためのコードを出力してください。
回答
現在の状態を見る限り、OCR自体はかなり成功しています。
問題は:
OCR後のレシート構造解析
です。
必要なのは:
| 改善内容 | 対応 |
|---|---|
| 日付取得 | 専用Regex |
| 合計取得 | 商品価格と分離 |
| 不要行除外 | 商品判定強化 |
です。
以下コードへ置き換えてください。
Execute全体
OCR解析メイン処理。
public OcrResult ParseReceipt(string text)
{
OcrResult result = new OcrResult();
string[] lines =
text.Split(
new[] { "\r\n", "\n" },
StringSplitOptions.RemoveEmptyEntries);
foreach (string rawLine in lines)
{
string line =
NormalizeText(rawLine);
line =
NormalizeSpace(line);
if (string.IsNullOrWhiteSpace(line))
{
continue;
}
// 日付解析
ParseDate(line, result);
// 合計解析
ParseTotal(line, result);
// 商品解析
ParseItem(line, result);
}
return result;
}
日付解析
ParseDate
private void ParseDate(
string line,
OcrResult result)
{
if (result.Date != null)
{
return;
}
Match match = Regex.Match(
line,
@"(\d{4})年\s*(\d{1,2})月\s*(\d{1,2})日");
if (!match.Success)
{
return;
}
int year =
int.Parse(match.Groups[1].Value);
int month =
int.Parse(match.Groups[2].Value);
int day =
int.Parse(match.Groups[3].Value);
result.Date =
new DateTime(
year,
month,
day);
}
合計取得
重要。
商品価格と区別します。
ParseTotal
private void ParseTotal(
string line,
OcrResult result)
{
if (!line.Contains("合計"))
{
return;
}
MatchCollection matches =
Regex.Matches(
line,
@"([0-9]{1,5})");
if (matches.Count == 0)
{
return;
}
string amountText =
matches[matches.Count - 1].Value;
if (decimal.TryParse(
amountText,
out decimal amount))
{
result.TotalAmount = amount;
}
}
商品判定
これがかなり重要。
IsProductLine
private bool IsProductLine(string line)
{
if (string.IsNullOrWhiteSpace(line))
{
return false;
}
// 日付
if (Regex.IsMatch(
line,
@"\d{4}年"))
{
return false;
}
// 店舗情報
string[] excludeWords =
{
"合計",
"小計",
"税込",
"税額",
"対象額",
"お預り",
"お釣り",
"現計",
"登録番号",
"TEL",
"営業時間",
"ポイント",
"カード",
"税抜",
"内税",
"外税",
"No",
"店",
"セルフ"
};
foreach (string word in excludeWords)
{
if (line.Contains(word))
{
return false;
}
}
// 数値が無い
MatchCollection matches =
Regex.Matches(
line,
@"([0-9]{1,5})");
if (matches.Count == 0)
{
return false;
}
// 商品名部分
Match lastMatch =
matches[matches.Count - 1];
string productName =
line.Substring(
0,
lastMatch.Index);
productName =
NormalizeProductName(productName);
// 商品名短すぎ
if (productName.Length <= 2)
{
return false;
}
return true;
}
商品解析
ParseItem
private void ParseItem(
string line,
OcrResult result)
{
if (!IsProductLine(line))
{
return;
}
MatchCollection matches =
Regex.Matches(
line,
@"([0-9]{1,5})");
if (matches.Count == 0)
{
return;
}
// 最後の数値を価格扱い
Match lastMatch =
matches[matches.Count - 1];
string amountText =
lastMatch.Value;
decimal amount =
NormalizePrice(amountText);
if (amount <= 0)
{
return;
}
string productName =
line.Substring(
0,
lastMatch.Index);
productName =
NormalizeProductName(productName);
amount =
FixAmountByProductName(
productName,
amount);
result.Items.Add(new OcrItem
{
Name = productName,
Amount = amount,
Quantity = 1,
TaxRate = 10
});
}
商品名補正
NormalizeProductName
private string NormalizeProductName(string text)
{
text =
Regex.Replace(
text,
@"\s+",
"");
text = text.Replace("-", "ー");
text =
Regex.Replace(
text,
@"[※★■◆\|]+",
"");
text =
Regex.Replace(
text,
@"[。\.,]+$",
"");
return text.Trim();
}
OCR価格補正
NormalizePrice
private decimal NormalizePrice(string value)
{
value = value.Replace("o", "0");
value = value.Replace("O", "0");
value = value.Replace("Q", "0");
value =
Regex.Replace(
value,
@"[^0-9]",
"");
if (decimal.TryParse(
value,
out decimal amount))
{
return amount;
}
return 0;
}
飲料価格補正
FixAmountByProductName
private decimal FixAmountByProductName(
string productName,
decimal amount)
{
// 飲料なのに異常高額
if (productName.Contains("mL"))
{
if (amount >= 900)
{
string text = amount.ToString();
// 921 → 321
if (text.StartsWith("9"))
{
text = "3" + text.Substring(1);
}
if (decimal.TryParse(
text,
out decimal fixedAmount))
{
return fixedAmount;
}
}
}
return amount;
}
このコードで改善される内容
日付
2026年05月10日
取得可能。
合計
2363
取得可能。
商品一覧
残るもの:
| 商品名 | 金額 |
|---|---|
| バファリンPMDXクイック+40錠 | 1958 |
| 爽健美茶600mL | 84 |
| モンスターエナジー500mL | 321 |
除外されるもの
- 日付
- 小計
- 税額
- 登録番号
- 店舗情報
- レジ番号
など。
起動、確認
起動してみます。
最後に
次回は支出登録全般を開発します。
