@frswataru (本石 渉)

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

Canvas四角(照準)を表示 JavaScript Canvas OCR

解決したいこと

カメラ映像に四角を表示したいです
また四角く囲ったテキストだけ読み取り精度も上げたいです
例)下図の通りに2か所に四角を表示したいです

スクリーンショット 2021-01-11 153055.jpg

コード

<html>
<head>
    <!-- ③ viewportを追加してスマホでも見やすくする -->
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, shrink-to-fit=no">
    <!-- ① CDNでCSSを読み込み -->
    <link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet">
</head>
<body>
    <div id="app">
        <div ref="canvas-container" style="width:100%;margin:0 auto;">
            <canvas ref="canvas"></canvas>
        </div>
        <div class="text-center pt-3">
            <!-- ② Status によって表示ブロックを切り替える -->
            <!-- play: カメラの映像をキャンバスにリアルタイムで表示 -->
            <div v-if="status=='play'">
                <button class="bg-blue-500 hover:bg-blue-700 text-white font-bold px-8 py-3 rounded" @click="takeSnapshot">
                    スナップショットを取る
                </button>
            </div>
            <!-- pause: スナップショットを撮ったので一次停止 -->
            <div v-if="status=='pause'">
                <button class="bg-blue-500 hover:bg-blue-700 text-white font-bold px-8 py-3 mr-1 rounded" @click="runOcr">
                    ISBNを読み取る
                </button>
                <button class="bg-transparent hover:bg-blue-500 text-blue-700 font-semibold hover:text-white py-2 px-4 ml-1 border border-blue-500 hover:border-transparent rounded" @click="playVideo">
                    戻る
                </button>
            </div>
            <!-- pause: スナップショットをOCRにかけてテキストを取得 -->
            <div v-if="status=='reading'">
                読み取り中です...
            </div>
        </div>
    </div>
    <!-- ① CDNでJavaScriptを読み込み -->
    <script src="https://unpkg.com/vue@3.0.0/dist/vue.global.prod.js"></script>
    <script src='https://unpkg.com/tesseract.js@v2.1.0/dist/tesseract.min.js'></script>
    <script>

        Vue.createApp({
            data() {
                return {
                    video: null,
                    canvas: null,
                    context: null,
                    dataUrl: '',
                    status: 'none'
                }
            },
            methods: {
                // ① カメラとキャンバスの準備
                initialize() {

                    this.status = 'initialize';

                    navigator.mediaDevices.getUserMedia({
                        video: {
                            facingMode: {
                                ideal: 'environment'
                            }
                        }
                    })
                    .then(stream => {

                        this.canvas = this.$refs.canvas;
                        this.context = this.canvas.getContext('2d');

                        this.video = document.createElement('video');
                        this.video.addEventListener('loadedmetadata', () => { // メタデータが取得できるようになったら実行

                            const canvasContainer = this.$refs['canvas-container'];
                            this.canvas.width = canvasContainer.offsetWidth;
                            this.canvas.height = parseInt(this.canvas.width * this.video.videoHeight / this.video.videoWidth);
                            this.render();

                        });
                        // iOSのために以下3つの設定が必要
                        this.video.setAttribute('autoplay', '');
                        this.video.setAttribute('muted', '');
                        this.video.setAttribute('playsinline', '');
                        this.video.srcObject = stream;
                        this.playVideo();

                    })
                    .catch(error => console.log(error));

                },
                render() {

                    if(this.video.readyState === this.video.HAVE_ENOUGH_DATA) {

                        this.context.drawImage(this.video, 0, 0, this.canvas.width, this.canvas.height);

                    }

                    requestAnimationFrame(this.render);

                },
                runOcr() { // ③ スナップショットからテキストを抽出

                    this.status = 'reading';

                    Tesseract.recognize(this.dataUrl, 'eng', {
                        logger: log => {

                            console.log(log);

                        }
                    })
                    .then(result => {

                        alert(result.data.text);

                    })
                    .catch(error => console.log(error))
                    .finally(() => {

                        this.playVideo();

                    });

                },
                playVideo() {

                    this.video.play();
                    this.status = 'play';

                },
                pauseVideo() {

                    this.video.pause();
                    this.status = 'pause';

                },
                takeSnapshot() { // ② スナップショットを取る(カメラは一時停止)

                    // this.makeSound(); // 音を鳴らす
                    this.pauseVideo();
                    this.dataUrl = this.canvas.toDataURL();

                },
                makeSound() { // ④ おまけ:スナップショットをとるときに音をならす

                    document.querySelectorAll('.notification-iframe').forEach(el => el.remove()); // 全ての通知用 iFrame を削除

                    // soundタグは使わず iFrame で直接音声ファイルへアクセスする
                    const iFrame = document.createElement('iframe');
                    iFrame.setAttribute('src', '/audios/insight.ogg');
                    iFrame.setAttribute('allow', 'autoplay');
                    iFrame.style.display = 'none';
                    iFrame.className = 'notification-iframe';
                    document.body.appendChild(iFrame);

                }
            },
            mounted() {

                this.initialize();

            }
        }).mount('#app');

    </script>
</body>
</html>
0 likes

1Answer

tesseract.jsの存在を今日知った超初心者が、わからんながら作ってみた。
精度はたぶん2値化すれば上がる(できるとは言っていない)。

こちらのサイトを参考にした(ほぼコピペ)。
https://ccbaxy.xyz/blog/2020/07/27/js4/

<html>
<head>
    <style>
    #container {
        overflow: hidden;
    }
    canvas {
        float: left;
    }
    #result-text {
        float: left;
        margin-left: 10px;
    }
    </style>
</head>
<body>
    <div id="container">
        <canvas width="500" height="400"></canvas>
        <div id="result-text"></div>
    </div>

<!-- <script src='https://unpkg.com/tesseract.js@v2.1.0/dist/tesseract.min.js'></script> -->
<script src="https://cdn.rawgit.com/naptha/tesseract.js/1.0.10/dist/tesseract.js"></script>
<script>
const canvas = document.querySelector("canvas");
const context = canvas.getContext("2d");

const offscreen_canvas = document.createElement("canvas");
const offscreen_context = offscreen_canvas.getContext("2d");

const image = document.createElement("video");

const resultText = document.getElementById("result-text");

offscreen_canvas.width = canvas.width;
offscreen_canvas.height = canvas.height;
image.videoWidth = canvas.width;
image.videoHeight = canvas.height;

let texts = null;

navigator.mediaDevices.getUserMedia({
    video: {
        facingMode: { ideal: "environment" },
    }
})
.then(stream => {
    image.srcObject = stream;
    image.play();

    analysis();

    setInterval(() => {
        reflesh();
    }, 800);
});

const analysis = () => {
    offscreen_context.drawImage(image, 0, 0);

    //与える画像にimage<=カメラの入力を直接与えると安定しなかった
    //表示しない中間処理のoffscreen_contextを与える
    Tesseract.recognize(offscreen_context, {
        lang: "eng"
    })
    .then(result => {
        texts = result;

        let state = true;

        if (texts == null) {
            state = false;
        }
        if (state == true && texts.text.length == 0) {
            state = false;
        }

        //読み取りできた単語単位で、赤い線で囲む
        if (state) {
            offscreen_context.strokeStyle = "rgb(255, 0, 0) ";
            offscreen_context.lineWidth = 1;
            
            texts.blocks.forEach((block) => {
                block.words.forEach((word) => {
                    offscreen_context.beginPath(word.bbox.x0, word.bbox.y0);
                    offscreen_context.lineTo(word.bbox.x1, word.bbox.y0);
                    offscreen_context.lineTo(word.bbox.x1, word.bbox.y1);
                    offscreen_context.lineTo(word.bbox.x0, word.bbox.y1);
                    offscreen_context.lineTo(word.bbox.x0, word.bbox.y0);
                    offscreen_context.closePath();
                    offscreen_context.stroke();
                });
            });
        }

        context.drawImage(offscreen_canvas, 0, 0, canvas.width, canvas.height);
    })
    .catch(error => console.log(error))
    .finally(() => {
        requestAnimationFrame(analysis);
    });
};

const reflesh = () => {
    resultText.innerHTML = "";
    if (texts === null || texts.text.length === 0) return;

    resultText.innerHTML = "<ul>";
    texts.blocks.forEach((block) => {
        block.words.forEach((word) => {
            resultText.innerHTML += `<li>${word.text}</li>`;
        });
    });
    resultText.innerHTML += "</ul>";
};

</script>
</body>
</html>
1Like

Comments

  1. @frswataru

    Questioner

    Excellent!!!

    予想を上回る出来!!!!単純に予め四角で囲った部分だけ読みたいのですが,,,,
    そういいたことは出来ますしょうか
  2. 公式ドキュメント読んで・・・

    https://github.com/naptha/tesseract.js/blob/master/docs/examples.md#with-only-part-of-the-image-201

    ```html
    <html>
    <head>
    <style>
    #container {
    overflow: hidden;
    }
    canvas {
    float: left;
    }
    #result-text {
    float: left;
    margin-left: 10px;
    }
    </style>
    </head>
    <body>
    <div id='container'>
    <canvas width='500' height='400'></canvas>
    <div id='result-text'></div>
    </div>

    <script src='https://cdnjs.cloudflare.com/ajax/libs/tesseract.js/2.1.4/tesseract.min.js'></script>
    <script>
    const canvas = document.querySelector('canvas');
    const context = canvas.getContext('2d');

    const image = document.createElement('video');

    const resultText = document.getElementById('result-text');
    let textBlocks = [];

    image.videoWidth = canvas.width;
    image.videoHeight = canvas.height;

    navigator.mediaDevices.getUserMedia({
    video: {
    facingMode: { ideal: 'environment' },
    }
    })
    .then(stream => {
    image.srcObject = stream;
    image.play();

    videoDisplay();

    setTimeout(analysis);
    });

    const videoDisplay = () => {
    context.drawImage(image, 0, 0, canvas.width, canvas.height);
    context.strokeStyle = "rgb(255, 0, 0) ";
    context.lineWidth = 3;
    context.beginPath(100, 150);
    context.lineTo(400, 150);
    context.lineTo(400, 250);
    context.lineTo(100, 250);
    context.lineTo(100, 150);
    context.closePath();
    context.stroke();

    context.strokeStyle = "rgb(0, 0, 255) ";
    textBlocks.forEach((block) => {
    context.beginPath(block.bbox.x0, block.bbox.y0);
    context.lineTo(block.bbox.x1, block.bbox.y0);
    context.lineTo(block.bbox.x1, block.bbox.y1);
    context.lineTo(block.bbox.x0, block.bbox.y1);
    context.lineTo(block.bbox.x0, block.bbox.y0);
    context.closePath();
    context.stroke();
    });
    console.log(textBlocks)

    requestAnimationFrame(videoDisplay);
    };

    const analysis = () => {
    (async () => {
    const worker = Tesseract.createWorker();
    await worker.load();
    await worker.loadLanguage('eng');
    await worker.initialize('eng');
    const { data: { text, blocks } } = await worker.recognize(canvas.toDataURL(), {
    rectangle: {
    left: 100, top: 150, width: 300, height: 100
    }
    });
    textBlocks = blocks;
    await worker.terminate();

    resultText.innerHTML = text.replace(/\r?\n/g, '<br>');
    setTimeout(analysis, 800);
    })();
    };

    </script>
    </body>
    </html>

    ```

    マークダウンが効いてない・・・
  3. @frswataru

    Questioner

    効きませんね
    rectangle_長方形なるほどです
  4. @frswataru

    Questioner

    ここの章の解説して頂きたく。チンプンカンプンですねん

    ```HTML
    const analysis = () => {
    (async () => {
    const worker = Tesseract.createWorker();
    await worker.load();
    await worker.loadLanguage('eng');
    await worker.initialize('eng');
    const { data: { text, blocks } } = await worker.recognize(canvas.toDataURL(), {
    rectangle: {
    left: 100, top: 150, width: 300, height: 100
    }
    });
    textBlocks = blocks;
    await worker.terminate();

    resultText.innerHTML = text.replace(/\r?\n/g, '<br>');
    setTimeout(analysis, 800);
    })();
    };
    ```
  5. ```html
    const analysis = () => {
      // async, awaitは非同期処理が終わるまで先に進まずに待ちたいときに使うやつ
      (async () => {
        // 文字を解析するための前準備
        // 具体的に何をしているのかは知らない。
        // 公式のサンプルをまねただけ
        // 詳しく知りたいならここを見て
        // https://github.com/naptha/tesseract.js/blob/master/docs/api.md#api
        const worker = Tesseract.createWorker();
        await worker.load();
        await worker.loadLanguage('eng');
        await worker.initialize('eng');
        // recognizeで画像から文字を検出する。
        // { data: { text, blocks } }は分割代入でググって
        const { data: { text, blocks } } = await worker.recognize(canvas.toDataURL(), {
          rectangle: {
            left: 100, top: 150, width: 300, height: 100
          }
        });
        // これはvideoDisplayで文字を認識した範囲を表示したいからグローバルな変数に代入している。
        textBlocks = blocks;
        // Tesseract.createWorkerで作ったworkerを終了させる。
        await worker.terminate();

        // recognizeで検出した文字を改行コードで区切ってをHTML上に表示させる。
        resultText.innerHTML = text.replace(/\r?\n/g, '<br>');
        // 0.8秒後にanalysisメソッドを実行する。
        // 要するにループ。0.8秒待たせるのは表示したテキストがすぐに消えてほしくないから。
        setTimeout(analysis, 800);
      })();
    };
    ```

    表示のために半角を全角にしてます。
  6. @frswataru

    Questioner

    ありがとうございます。
    いじって遊んでみます☆
  7. @frswataru

    Questioner

    Multiple Rectanglesに挑戦してます。
    どうもうまくいきません。コードをご教示願います
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="utf-8" >
    <title>ECSS3.0</title>
    <link rel="stylesheet" href="css/stylesheet.css">
    <link href="css/slick.css" rel="stylesheet" type="text/css">
    <link href="css/slick-theme.css" rel="stylesheet" type="text/css">
    <style>
    #container2 {
    overflow: hidden;
    }
    canvas {
    float: left;
    }
    #result-text {
    float: left;
    margin-left: 10px;
    }
    </style>
    </head>

    <body>
    <script src="script.js"></script>
    <div class="header">
    <div class="header-logo"font-weight:bold;>E<span style="font-size:0.3em;font-weight:normal;">lectronic</span>
    C<span style="font-size:0.3em;font-weight:normal;">heck</span>
    S<span style="font-size:0.3em;font-weight:normal;">heet</span>
    S<span style="font-size:0.3em;font-weight:normal;">ystem</span>

    </div>

    <div id="container">
    <div class="circle icon">
    <span class="bar top"></span>
    <span class="bar middle"></span>
    <span class="bar bottom"></span>
    </div>
    </div>

    <div id="containerLogo">
    <a href="index.html"><img src="images/TCD_B.jpg"></a>
    </div>




    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
    <script>
    $("#container").click(function () {
    $(".icon").toggleClass("close");
    })
    </script>
    </div>

    <!-- <div class="main"> -->
    <nav id="nav">
    <ul>
    <!-- <li><a href="#" onclick="window.close(); return false;">Close</a></li> -->
    <li><a href="index.html">Menu</a></li>
    </ul>

    </nav>

    <div id='container2'>
    <canvas width='500' height='400'></canvas>
    <div id='result-text'></div>
    </div>

    <script src='https://cdnjs.cloudflare.com/ajax/libs/tesseract.js/2.1.4/tesseract.min.js'></script>
    <script>
    // canvas(video)&text(result-text)宣言
    const canvas = document.querySelector('canvas');
    const context = canvas.getContext('2d');

    const image = document.createElement('video');

    const resultText = document.getElementById('result-text');
    let textBlocks = [];

    // videoのサイズ変更
    image.videoWidth = canvas.width;
    image.videoHeight = canvas.height;

    navigator.mediaDevices.getUserMedia({
    video: {
    facingMode: { ideal: 'environment' },
    }
    })
    .then(stream => {
    image.srcObject = stream;
    image.play();

    videoDisplay();

    setTimeout(analysis);
    });


    // 赤枠の発火条件_Textデータの個数が0以上
    const videoDisplay = () => {
    context.drawImage(image, 0, 0, canvas.width, canvas.height);
    context.strokeStyle = "rgb(255, 0, 0) ";
    context.lineWidth = 3;
    // Path描画(閉じたオブジェクト)_ED
    context.beginPath(100, 175);
    context.lineTo(300, 175);
    context.lineTo(300, 250);
    context.lineTo(100, 250);
    context.lineTo(100, 175);
    context.closePath();
    context.stroke(); 
    // Path描画(閉じたオブジェクト)_DS
    context.beginPath(305, 175);
    context.lineTo(390, 175);
    context.lineTo(390, 225);
    context.lineTo(305, 225);
    context.lineTo(305, 175);
    context.closePath();
    context.stroke(); 





    // 青枠の発火条件_Textデータの個数が1以上
    context.strokeStyle = "rgb(0, 0, 255) ";
    textBlocks.forEach((block) => {
    context.beginPath(block.bbox.x0, block.bbox.y0);
    context.lineTo(block.bbox.x1, block.bbox.y0);
    context.lineTo(block.bbox.x1, block.bbox.y1);
    context.lineTo(block.bbox.x0, block.bbox.y1);
    context.lineTo(block.bbox.x0, block.bbox.y0);
    context.closePath();
    context.stroke();
    });

    // textBlocks
    console.log(textBlocks)

    requestAnimationFrame(videoDisplay);
    };

    // 発火条件
    const analysis = () => {
    (async () => {
    const worker = Tesseract.createWorker();
    await worker.load();
    await worker.loadLanguage('eng');
    await worker.initialize('eng');
    const { data: { text, blocks } } = await worker.recognize(canvas.toDataURL(), {
    rectangle: {
    left: 100, top: 175, width: 200, height: 75
    },
    });


    // textBlocks = blocks;
    await worker.terminate();

    // resultText.innerHTML = text.replace(/\r?\n/g, '<br>');
    setTimeout(analysis, 1000);
    input1.value = text
    })();
    };

    </script>


    <!-- <div class="main">

    <div class="contents">

    <div class="contents-item">
    <a href="ocr.html"><img src="images/Camera.png"></a>
    <p>Camera</p>
    </div>
    <div class="contents-item">
    <img src="images/Create.png">
    <p>Create</p>
    </div>

    </div>
    </div> -->

    <!-- </div> -->

    <div class="SDkEP">
    <div id="inputWrapper">
    <div class="inputTextBox" >
    <input id="input1" type="search" autocomplete="off" aria-live="polite" placeholder="ED を入力">
    </div>
    <div class="inputTextBox">
    <input id="input" type="search" autocomplete="off" aria-live="polite" placeholder="DS を入力">
    </div>
    </div>
    </div>


    </body>
    </html>
  8. 見づらいしやり取りも長いから別で質問してこれはクローズして
  9. @frswataru

    Questioner

    なるほど承知しました
    別で上げました

Your answer might help someone💌