【Tesseract.js】ブラウザだけで画像内の文字を解析

[Tesseract.js](https://tesseract.projectnaptha.com/)は、画像解析ができるJavaScriptライブラリです。画像に書かれた文章を文字列に起こしてくれます。
最近使う機会があってなかなか良かったので、布教も兼ねてすぐに試せるコードをいくつか紹介します。
変更してみて欲しい箇所にはコード内にコメントしています。

ライブラリの基本的な使用法については[公式ドキュメント](https://github.com/naptha/tesseract.js#documentation)や[他の記事](https://qiita.com/search?q=tesseract.js)で紹介されているので、そちらを参照ください。

# 画像例
この例ではLorem ipsumを解析します。
意味のない文章なので解析しづらいと思いますが、頑張ってもらいましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272677/48f95b30-72e8-4453-b074-3b39f41ab643.png)
# 1枚丸ごと解析
1枚の画像をブラウザに添付し、書いてある文字をすべて解析する例です。
以下のCDN使用例はhtmlファイルにコピペで動きます。
言語によって解析精度はかなり変わってくるので、色々な言語の画像で試してみてください。
…しかしLorem ipsumって何語で解析すれば良いんでしょうかね？元々意味がない文章とのことなので、ここでは日本語で進めます。

```html
<html lang="ja">
  <body>
    <input type="file" id="image_zone">
    <div id="output"></div>
    <script src='https://unpkg.com/tesseract.js@v2.0.2/dist/tesseract.min.js'></script>
    <script>
      const imageZone = document.getElementById('image_zone')
      imageZone.addEventListener('change', resizePinnedImage, false)
      function resizePinnedImage(e) {
        const file = e.target.files[0]
        if (!file.type.match('image.*')) { return }
        Tesseract.recognize(
          file,
          'jpn', // 言語設定
          { logger: m => console.log(m) }
        ).then(({ data: { text } }) => {
          const out = document.getElementById('output')
          out.innerHTML = text
        })
      }
    </script>
  </body>
</html>
```

結果は以下のようになりました。
文字が繋がってしまっているので、画像を拡大したほうがよさそうです。次でやってみましょう。

>Loem ipaundoorstamet coreeaeuradpscng eltseddoeuanod ierpornddguntutkkov etdoo magnaalqua_uienmad mimvenam qusrowud clatonulanco mboisneiuaiqdpexea commodocomsequat Dus aue ue coornrepeherdet Invoupaaeveltessoclun ooe eugdatnulaparauc cepeursntocuccatopdaatronpoden smtncupaquoWca desenntmottammigestkboum Cuablurpetuntndduniacus Nulagavdaociaodo_Nulanvaus upsqtcommodorhaevacaecsbbendunclrec MNcus magafal solgudn maus neoernmautseunbneuenodgevda Dusecteusotausvpuaevencua ponec kbots iusaelt lanterporUtulancopec leua cutenporcorgue wosesteuenoduupsidtnddunsapenrsusaquan Maccenasfermenun consequatn Doneciermenun Palencwaue maesuadanulaaml Dussanensen aiqetnec ommodo 0eb coreoquatqusyneque Aiquan eucbus gltutdcunaiguet elsnsiadpscngsapen sod naesuade danacuscoatem as molsscacisquenune Nlanacu_Alquam oonscqua Cusburaugueloemdapbusqus loeetat petunacnet Amneanmagnansl roNsqus nocsie cu_eugatinocLinhachabtasepaeadcuns、



# 画像を拡大して解析
以下はキャンバスを利用して画像を2倍に拡大して解析する例です。
Tesseractの対応形式は広く、Canvas要素をそのまま投入可能なのも便利なところです。

```html
<html lang="ja">
  <body>
    <input type="file" id="image_zone">
    <br>
    <canvas id="canvas"></canvas>
    <div id="output"></div>
    <script src='https://unpkg.com/tesseract.js@v2.0.2/dist/tesseract.min.js'></script>
    <script>
      const imageZone = document.getElementById('image_zone')
      imageZone.addEventListener('change', resizePinnedImage, false)
      function resizePinnedImage(e) {
          const file = e.target.files[0]
          if (!file.type.match('image.*')) { return }
          resize(file)
      }
      function resize(file) {
        imageToCanvas(file).then(function (canvas) {
          Tesseract.recognize(
            canvas, // Canvasを投げられる！
            'jpn',
            { logger: m => console.log(m) }
          ).then(({ data: { text } }) => {
            const out = document.getElementById('output')
            out.innerHTML = text
          })
        }).catch(function (error) {
          console.error(error)
        })
      }

      function imageToCanvas (imageFile) {
        return new Promise(function (resolve, reject) {
          readImage(imageFile).then(function (src) {
            loadImage(src).then(function (image) {
              const canvas = document.getElementById("canvas")
              const ctx = canvas.getContext('2d')
              // スケール2倍
              const scale = 2
              canvas.width = image.width * scale
              canvas.height = image.height * scale
              ctx.drawImage(
                image, 
                (image.width - (canvas.width / scale)) / 2, (image.height - (canvas.height / scale)) / 2,
                canvas.width / scale, canvas.height / scale,
                0, 0,
                canvas.width, canvas.height
              )
              resolve(canvas)
            }).catch(function (error) {
              reject(error)
            })
          }).catch(function (error) {
            reject(error)
          })
        })
      }

      function readImage(image) {
        return new Promise(function (resolve, reject) {
          const reader = new FileReader()
          reader.onload = function () { resolve(reader.result) }
          reader.onerror = function (e) { reject(e) }
          reader.readAsDataURL(image)
        })
      }
      function loadImage(src) {
        return new Promise(function (resolve, reject) {
          const img = new Image()
          img.onload = function () { resolve(img) }
          img.onerror = function (e) { reject(e) }
          img.src = src
        })
      }
    </script>
  </body>
</html>
```
この例では、拡大後の画像がキャンバスに表示されます。（大きすぎるのでトリミングしました）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272677/2c1127bf-adc1-cca5-1122-0cb4e36ff48b.png)
そして解析結果は以下のようになりました。
sやuの大文字・小文字違いを除けばほとんどが正確で、やはりちょうどよい文字の大きさがあるようです。
>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cUpidatat non proident, SUunt in culpa qui officia deserunt mollit anim id est laborum. Curabitur pretium tincidunt lacus. Nulla gravida orci a odio. Nullam varius, turpis et commodo pharetra, est eros bibendum elit, nec luctus magna felis sollicitudin mauris. Integer in mauris eu nibh euismod gravida. Duis ac tellus et risus vulputate vehicula. Donec lobortis risus a elit. Etiam tempor. Ut ullamcorper, ligula eu tempor congue, eros est eUuismod turpis, id tincidunt sapien risus a dquam. Maecenas fermentum consequat mi. Donec fermentum. Pellentesque malesuada nulla a mi. Duis sapien sem, aliquet nec, commodo edet, consedquat quis, neque. Aliquam faucibus, elit ut dictum aliquet, felis nisl adipiscing sapien, sed malesuada diam lacus eget erat. Cras mollis scelerisque nunc. Nullam arcu. Aliquam consedquat. Curabitur augue lorem, dapibus quis, laoreet et, pretium ac, nisi Aenean magna nisl, mollis quis, molestie eu, feugliat in, orci. In hac habitasse platea dictumst.



# 画像の一部を切り取って解析
先ほどとほぼ同じですが、画像の真ん中を（この例では`200px * 100px`）切り取って解析する例です。
画像の必要のない部分をカットすることで、高速な解析が望めます。

```html
<html lang="ja">
  <body>
    <input type="file" id="image_zone">
    <br>
    <canvas id="canvas"></canvas>
    <div id="output"></div>
    <script src='https://unpkg.com/tesseract.js@v2.0.2/dist/tesseract.min.js'></script>
    <script>
      const imageZone = document.getElementById('image_zone')
      imageZone.addEventListener('change', resizePinnedImage, false)
      function resizePinnedImage(e) {
          const file = e.target.files[0]
          if (!file.type.match('image.*')) { return }
          resize(file)
      }
      function resize(file) {
        imageToCanvas(file).then(function (canvas) {
          Tesseract.recognize(
            canvas,
            'jpn',
            { logger: m => console.log(m) }
          ).then(({ data: { text } }) => {
            const out = document.getElementById('output')
            out.innerHTML = text
          })
        }).catch(function (error) {
          console.error(error)
        })
      }

      function imageToCanvas (imageFile) {
        return new Promise(function (resolve, reject) {
          readImage(imageFile).then(function (src) {
            loadImage(src).then(function (image) {
              const canvas = document.getElementById("canvas")
              const ctx = canvas.getContext('2d')
              // 幅200、高さ100
              width = 200
              height = 100
              canvas.width = width
              canvas.height = height
              ctx.drawImage(image, (image.width / 2) - width / 2, (image.height / 2) + height / 2, width, height, 0, 0, width, height)
              resolve(canvas)
            }).catch(function (error) {
              reject(error)
            })
          }).catch(function (error) {
            reject(error)
          })
        })
      }

      function readImage(image) {
        return new Promise(function (resolve, reject) {
          const reader = new FileReader()
          reader.onload = function () { resolve(reader.result) }
          reader.onerror = function (e) { reject(e) }
          reader.readAsDataURL(image)
        })
      }
      function loadImage(src) {
        return new Promise(function (resolve, reject) {
          const img = new Image()
          img.onload = function () { resolve(img) }
          img.onerror = function (e) { reject(e) }
          img.src = src
        })
      }
    </script>
  </body>
</html>
```
雑に切り取ったら変なことになってしまいました。
個人的にはアプリ画面の必要な部分だけを切り取る用途に使う予定です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272677/31c4c3f0-16b6-5c2e-2fd8-d58448a22140.png)
以上です。ぜひ遊んでみてください。