TesseractとSympyを使って画像から方程式を認識して解を求める

# 概要

この記事では、OCRエンジン「Tesseract」を使って、画像から数式を認識し、数式計算ライブラリであるSympyを使って方程式の解を解く方法について紹介します。Pythonを使った手順も含め、数式認識から計算までの流れを詳しく説明します。
# 動作環境
Ubuntu 22.04 LTS
Python 3.10
pytesseract 0.3.13


## 事前準備
### 1. Tesseractのインストール
Tesseractとは、Googleが開発したオープンソースの光学文字認識OCR（Optical Character Recognition）ライブラリです。
参考サイト
https://pypi.org/project/pytesseract/

```bash
pip install pytesseract 
```

### 2. 関連のライブラリのインストール
Pythonで画像処理を行うためのパッケージであるOpenCVとPillowをインストールします。
```bash
sudo apt-get install python3-opencv
pip install pillow
```
数式計算を行うライブラリSympyもインストールします。画像から数字と演算子を認識したあとに、Sympyを使って方程式を計算します。
```bash
pip install sympy 
```

### 3. 認識する画像
以下のサイトにある画像を使って、方程式の解を求めます。
![equation1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925972/85488afb-08a4-bcc3-9356-ca44dd463c35.png)

https://www.bbc.co.uk/bitesize/articles/zrg6pbk#z38vbqt



## コード
用意した画像を読みこんで、方程式の解を求めるPythonプログラムです。
全体コードのあとに、それぞれのコードの役割について解説します。

```python
import pytesseract
from PIL import Image
import cv2
import sympy as sp
import re

# 画像を読み込む
image_path = '/home/folder/equation1.png'  # ここを画像のパスに変更
image = cv2.imread(image_path)

# 画像をRGBに変換（OpenCVはBGRを使用するため）
image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)

# Tesseractを使用して画像内の数式をOCRで読み取る（数式設定を使用）
text = pytesseract.image_to_string(image_rgb, config='--psm 7', lang='eng+equ')

# 認識したテキストを整形
equation = text.strip().replace(' ', '')  # スペースを除去
print(f"認識された数式: {equation}")

# 必要に応じて掛け算記号（*）を挿入（例: '2x'を '2*x'に変更）
equation = re.sub(r'(\d)([a-zA-Z])', r'\1*\2', equation)
print(f"修正された数式: {equation}")

# 解の変数を初期化
solution_text = ""

# 数式に'x'が含まれているかチェック（含まれている場合は1次方程式とみなす）
if 'x' in equation:
    # SymPyで使用するシンボル'x'を定義
    x = sp.symbols('x')
    
    # SymPyは数式を '左辺 = 右辺' の形式で扱うため、数式を分割
    lhs, rhs = equation.split('=')
    
    # 左辺と右辺をSymPy式に変換
    lhs_expr = sp.sympify(lhs)
    rhs_expr = sp.sympify(rhs)
    
    # 方程式を解き、'x'の解を得る
    solution = sp.solve(lhs_expr - rhs_expr, x)
    
    # 画像に重ねるための解のテキストを準備
    solution_text = f"x = {solution[0]}"
    print(f"xの解: {solution}")
else:
    print("方程式に変数'x'が見つかりません。")

# 認識した数式と解を画像に重ねる
output_image = image.copy()

# フォント、サイズ、色、位置の設定
font = cv2.FONT_HERSHEY_SIMPLEX
font_scale = 0.7  # 小さめのフォントサイズ
font_color = (255, 0, 0)  # 青色（BGR形式）
font_thickness = 1  # 細めのフォント太さ
line_type = cv2.LINE_AA

# テキストの位置（座標を調整可能）
equation_position = (50, 50)
solution_position = (50, 100)

# 認識した数式と解を画像に重ねる
cv2.putText(output_image, f"認識された数式: {equation}",
            equation_position, font, font_scale, font_color, font_thickness, line_type)
cv2.putText(output_image, f"解: {solution_text}",
            solution_position, font, font_scale, font_color, font_thickness, line_type)

# 出力画像を保存
output_path = 'output_linearequation1.png'
cv2.imwrite(output_path, output_image)
print(f"出力画像が {output_path} として保存されました")

```

## コードの解説


#### ライブラリのインポート
``` python
import pytesseract
from PIL import Image
import cv2
import sympy as sp
import re
``` 
#### 画像の読み込み

``` python
image = cv2.imread(image_path)
``` 
指定したディレクトリから画像を読み込みます

#### OCRによる数字と演算子の認識

``` python
text = pytesseract.image_to_string(image_rgb, config='--psm 7', lang='eng+equ')
``` 
`pytesseract ` で画像から文字（数字や記号等）抽出し、英語（`eng` ）と数字（`equ` ）を認識します。

#### 数式の整形

``` python
equation = text.strip().replace(' ', '')
``` 
`strip()`を用いて 文字列の前後にある空白や改行文字を取り除き、`.replace(' ', '')`を用いて文字列内のすべてのスペース（半角スペース）を取り除きます。
例えば、OCRで読み取った数式が` 3 x + 2 = 5`だった場合、余計なスペースが除去され、数式は `3x+2=5` という形式に変換されます。
### 演算子（掛け算記号）の挿入
``` python
equation = re.sub(r'(\d)([a-zA-Z])', r'\1*\2', equation)
``` 

OCRで読み取った`3x+2=5` をSympyを用いて方程式の解を求めるとエラーが発生します。そこで3とxの間に掛け算を明示することで、計算可能な形式に変換します。
### 方程式の解の算出
``` python
if 'x' in equation: # 読みこんだ数式に変数 `x` が含まれているかをチェック
    x = sp.symbols('x') #変数ｘとして定義
    lhs, rhs = equation.split('=') #左辺、右辺の定義
    lhs_expr = sp.sympify(lhs)
    rhs_expr = sp.sympify(rhs)
    solution = sp.solve(lhs_expr - rhs_expr, x)
    solution_text = f"x = {solution[0]}"
 ``` 
各行で何を行っているか解説します。
- 変数ｘの定義
数式に が含まれていれば、SymPy の `symbols` 関数を使って、変数を x として定義します。
``` python
2x = sp.symbols('x')
``` 

- 左辺、右辺の分割
equationの数式を比較演算子 `=` で分割し、左右の部分（`lhs` と `rhs`）に分けます。
`lhs` は左辺（left-hand side）、`rhs` は右辺（right-hand side）を表しています。

``` python
lhs, rhs = equation.split('=')
``` 
- 文字列から数式オブジェクトの変換
`sympify` 関数を使って、文字列 `lhs` を SymPy の数式（式）オブジェクトに変換します。
これにより、数式をPythonの文字列ではなく、数式としての操作が可能な SymPy のオブジェクトとして扱えるようになります。rhs（右辺）も同様に SymPy の数式オブジェクトに変換します。
これで、左右の辺が数式として扱えるようになり、演算や計算が可能になります。
``` python
lhs_expr = sp.sympify(lhs)
rhs_expr = sp.sympify(rhs)
``` 

- 方程式の計算
``` python
solution = sp.solve(lhs_expr - rhs_expr, x)
``` 
`lhs_expr - rhs_expr` は、等式を `lhs = rhs` から `lhs - rhs = 0` の形に変形しています。例えば、`3*x + 2 = 5` は `3*x + 2 - 5 = 0` と変形できます。
`sp.solve` 関数を使用して、この方程式を `x` について解きます。
`solution` には、x の解がリスト形式で返されます。たとえば、解が x = 1 なら、solution には [1] が格納されます。
``` python
solution_text = f"x = {solution[0]}"
``` 
solution の最初の解を `solution[0]` で取得し、solution_text という文字列にフォーマットして代入します。
`f"x = {solution[0]}"` では、解が見やすい形式で出力されるように、文字列として x = 解 の形式に整形しています。


### 実行結果

![output_linearequation1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925972/2593c540-cd81-b947-4786-ef168fa0dec8.png)
画像から一次方程式を認識し、方程式の解を求めることができました。


## 終わりに
今回、TesseractとPythonを使って数式を認識し、計算する方法について解説しました。WEBカメラで手書きの数式を読みこみや2次方程式の二乗も認識されるのか試してみたいと思います。