Shape Detection API で テキストを読み取る

冒頭からタイトル詐欺であることを読者には謝罪したい。
Shape Detection API を使用して、テキストの読み取りは動作確認ができなかった。

代わりにtesseract.jsを使用して、テキストの読み取りを試みたので、そちらをレポートします。

目次

参考

実装 1(画像の中の文字を読み取る)

test1.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<html>
<head>
<script
src="https://code.jquery.com/jquery-3.5.1.slim.min.js"
integrity="sha256-4+XzXVhsDmqanXGHaHvgh1gMQKX40OUvDEBTu8JcmNs="
crossorigin="anonymous"
></script>
<script src="https://cdn.rawgit.com/naptha/tesseract.js/1.0.10/dist/tesseract.js"></script>
</head>
<body>
<img src="text1.png" id="source" />
<div id="result"></div>

<script type="text/javascript" src="app1.js"></script>
</body>
</html>
app1.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
const image = document.getElementById("source");

let texts = null;

if (!("TextDetector" in window)) {
alert("TextDetector is not available");
}

window.onload = async () => {
const result = await Tesseract.recognize(
image,
{ lang: "jpn" }, //eng
{ logger: (m) => console.log(m) }
);
console.log(result);

let resultText = "";
for (const block of result.blocks) {
let resultWord = "";
block.words.forEach((word, i) => {
resultWord += `<li>word${i} = ${word.text}</li>`;
});

resultText += `
<ul>
<li>text = ${block.text}</li>
${resultWord}
<li>左上 {x = ${block.bbox.x0},y = ${block.bbox.y0}}</li>
<li>右下 {x = ${block.bbox.x1},y = ${block.bbox.y1}}</li>
</ul>
`;
}
$("#result").html(resultText);
};

recognizeの第二引数には言語の指定を行います。
指定できる言語は、tessdocに紹介されています。

こちらを動かすと、次のようになります。

ひらがなの「う」をカタカナの「ラ」と誤判定しているようです。
たしかに似ているといえば、似ています。

tesseract.jsの判定結果のオブジェクトは、単語単位、行単位、さらに大きな塊の単位など階層構造になっています。
今回は抜粋し、大きな単位での、文字列全体。
単語単位での表示。そして全体の左上と右下座標を表示してみました。

実装 2 カメラから読み込む

前回同様に、カメラから取り込みし文字の認識を試みます。

test2.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<html>
<head>
<script
src="https://code.jquery.com/jquery-3.5.1.slim.min.js"
integrity="sha256-4+XzXVhsDmqanXGHaHvgh1gMQKX40OUvDEBTu8JcmNs="
crossorigin="anonymous"
></script>
<script src="https://cdn.rawgit.com/naptha/tesseract.js/1.0.10/dist/tesseract.js"></script>
</head>
<body>
<div id="canvas_area">
<canvas id="result" width="900" height="900"></canvas>
</div>
<div id="result_text"></div>
<script type="text/javascript" src="app2.js"></script>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
// streamを入力するvideoを作成する
const image = document.createElement("video");

// 画像を加工するcanvasを作成する
const offscreen_canvas = document.createElement("canvas");
const offscreen_context = offscreen_canvas.getContext("2d");

// 最終的に取得した画像を表示するcanvasを取得する
const canvas = document.querySelector("#result");
const context = canvas.getContext("2d");

//カメラと中間処理のキャンバスのサイズを最終的に表示するキャンバスを基準に設定
offscreen_canvas.width = canvas.width;
image.videoWidth = canvas.width;
offscreen_canvas.height = canvas.height;
image.videoHeight = canvas.height;

//取得結果は使いまわすので、外で定義する
let texts = null;

window.onload = async () => {
console.log("onload");
//カメラを取得
const stream = await navigator.mediaDevices.getUserMedia({
video: {
facingMode: { exact: "environment" },
},
});

//オブジェクトと関連付ける
image.srcObject = stream;
image.play();

//文字の解析処理自体の実行
analysis();

//解析結果をdomに書き出す処理の定期呼び出しを設定
setInterval(() => {
reflesh();
}, 800);
};

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

try {
//与える画像にimage<=カメラの入力を直接与えると安定しなかった。
//表示しない中間処理のoffscreen_contextを与える
texts = await Tesseract.recognize(offscreen_context, {
lang: "jpn",
}).progress(function (p) {
console.log(p);
});
} catch (e) {
console.log(e);
window.requestAnimationFrame(analysis);
return;
}

console.log(texts);

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 = 10;

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);
window.requestAnimationFrame(analysis);
};

//結果を文字で書き出す
const reflesh = () => {
$("#result_text").empty();
if (texts == null) {
$("#result_text").text("ERROR");
return;
}
if (texts.text.length == 0) {
$("#result_text").text("ERROR");
return;
}
let resultText = "";

texts.blocks.forEach((block) => {
console.log(block);
resultText += `
<ul>
<li>text = ${block.text}</li>
</ul>
`;
});

$("#result_text").html(resultText);
};

コメントでも記載していますが、recognizeの第一引数に video オブジェクトのimageを与えると動作が非常に不安定になりました。

動作させたときのは以下のようになります。

うまくいかないときは、文字のないところを誤検知もします。

カメラの入力を基に、画像上の文字の取得、位置の取得ができました。


今回は、Shape Detection API での文字認識がかなわず、tesseract.jsでの、文字認識を試みました。
recognizeの第一引数に最初はimageを渡していたわけですが、この場合の動作が不安定でした。
この点にはまって数時間かけてしまいました。

文字の認識処理は、バーコードと異なり数秒から十数秒かかるものでした。
結果としてバーコード読み取りの速さを再認識することになりました。
今回は日本語で検知を行ったことも処理が遅くなった要因の 1 つであるます。

Shape Detection API の記事 3 部作、これにて終了です。
実戦レベルなのは、バーコード読み取りだけかなーというのが一通り(最後のは動作確認できませんでしたが)触った所感でした。

ではでは。