[React] OpenCV で画像を2値化してみる | 心を無にして始める React
準備
React で OpenCV.js を使えるようにしておきます。
2値化する
簡単なしきい値処理
使うものはこちら。
https://docs.opencv.org/5.x/d7/d1b/group__imgproc__misc.html#gae8a4a146d1ca78c626a53577199e9c57
簡単なしきい値処理では、しきい値を決めての2値化ができますが、画像処理では画像に合わせて 自動で しきい値を決めたいことが多いので、ここでは 大津の2値化 をします。
cv.threshold(grayMat, thresholdMat, 0, 255, cv.THRESH_OTSU);
いつものように App.js を編集していきます。
(グレースケールにしてみる App.js をベースに少しだけ変更しています。)
import React, { useState } from 'react';
import './App.css';
import Button from './components/Button';
import Dropzone from "./components/DropZone";
const cv = window.cv;
function App() {
const [urls, setUrls] = useState([]);
const onDrop = (acceptedFiles) => {
if (acceptedFiles) {
setUrls(acceptedFiles.map(acceptedFile => URL.createObjectURL(acceptedFile)))
}
}
const handleClick = (cb) => {
if (urls.length <= 0) {
cb();
return;
}
urls.forEach((url, i) => {
const img = new Image();
img.src = url;
img.onload = () => {
img.height = 240;
img.width = img.naturalWidth * (240 / img.naturalHeight);
const imageMat = cv.imread(img);
const grayMat = new cv.Mat();
cv.cvtColor(imageMat, grayMat, cv.COLOR_RGBA2GRAY, 0);
const thresholdMat = new cv.Mat();
cv.threshold(grayMat, thresholdMat, 0, 255, cv.THRESH_OTSU);
cv.imshow(`canvas-${i}`, thresholdMat);
thresholdMat.delete();
imageMat.delete();
grayMat.delete();
cb();
}
})
}
return (
<>
<div className="bg-dark p-5" style={{ minHeight: '100vh', height: '100%' }}>
<Dropzone className="w-100" onDrop={onDrop}>
<div className="d-flex justify-content-center align-items-center p-5">
{
urls.length === 0 && (
<span>ファイルをドロップしてください</span>
)
}
{
urls.map(url => (
<div key={url?.toString()} className="border border-dark m-2">
<img src={url} alt="" style={{ maxWidth: 320, maxHeight: 240 }} />
</div>
))
}
</div>
</Dropzone>
<div className="d-flex justify-content-center align-items-center p-5">
<Button valiant="info" onClick={handleClick}>大津の二値化をするボタン</Button>
</div>
{
urls.length && (
<div className="d-flex justify-content-center align-items-center p-5 bg-light">
{
urls.map((url, i) => (
<div className="border border-dark m-2">
<canvas className="bg-light" key={url?.toString()} id={`canvas-${i}`} />
</div>
))
}
</div>
)
}
</div>
</>
);
}
export default App;
確認してみます。
はい、できました。
適応的なしきい値処理
簡単なしきい値処理の threshold と比べて、適応的なしきい値処理ができる関数が用意されています。
https://docs.opencv.org/5.x/d7/d1b/group__imgproc__misc.html#ga72b913f352e4a1b1b397736707afcde3
今回は 近傍領域の中央値 をしきい値にしてみます。
画素ごとに、当該画素を中心とした1辺の長さが BlockSize の正方形の範囲の中央値がしきい値になります。
cv.adaptiveThreshold(grayMat, thresholdMat, 255, cv.ADAPTIVE_THRESH_MEAN_C, cv.THRESH_BINARY, 3, 1)
App.js 全体ではこのようになります。
import React, { useState } from 'react';
import './App.css';
import Button from './components/Button';
import Dropzone from "./components/DropZone";
const cv = window.cv;
function App() {
const [urls, setUrls] = useState([]);
const onDrop = (acceptedFiles) => {
if (acceptedFiles) {
setUrls(acceptedFiles.map(acceptedFile => URL.createObjectURL(acceptedFile)))
}
}
const handleClick = (cb) => {
if (urls.length <= 0) {
cb();
return;
}
urls.forEach((url, i) => {
const img = new Image();
img.src = url;
img.onload = () => {
img.height = 240;
img.width = img.naturalWidth * (240 / img.naturalHeight);
const imageMat = cv.imread(img);
const grayMat = new cv.Mat();
cv.cvtColor(imageMat, grayMat, cv.COLOR_RGBA2GRAY, 0);
const thresholdMat = new cv.Mat();
cv.adaptiveThreshold(grayMat, thresholdMat, 255, cv.ADAPTIVE_THRESH_MEAN_C, cv.THRESH_BINARY, 3, 1)
cv.imshow(`canvas-${i}`, thresholdMat);
thresholdMat.delete();
imageMat.delete();
grayMat.delete();
cb();
}
})
}
return (
<>
<div className="bg-dark p-5" style={{ minHeight: '100vh', height: '100%' }}>
<Dropzone className="w-100" onDrop={onDrop}>
<div className="d-flex justify-content-center align-items-center p-5">
{
urls.length === 0 && (
<span>ファイルをドロップしてください</span>
)
}
{
urls.map(url => (
<div key={url?.toString()} className="border border-dark m-2">
<img src={url} alt="" style={{ maxWidth: 320, maxHeight: 240 }} />
</div>
))
}
</div>
</Dropzone>
<div className="d-flex justify-content-center align-items-center p-5">
<Button valiant="info" onClick={handleClick}>適応的なしきい値で二値化をするボタン</Button>
</div>
{
urls.length && (
<div className="d-flex justify-content-center align-items-center p-5 bg-light">
{
urls.map((url, i) => (
<div className="border border-dark m-2">
<canvas className="bg-light" key={url?.toString()} id={`canvas-${i}`} />
</div>
))
}
</div>
)
}
</div>
</>
);
}
export default App;
確認してみます。
はい、できました。
手先や足先の判断に変化がでました (ΦωΦ)
白黒反転(おまけ)
2値化した結果を白黒反転させておきたいときは、cv.THRESH_BINARY_INV を使います。
書き方は関数で少し変わります。
cv.threshold
cv.threshold(grayMat, thresholdMat, 0, 255, cv.THRESH_OTSU + cv.THRESH_BINARY_INV);
cv.adaptiveThreshold
cv.adaptiveThreshold(grayMat, thresholdMat, 255, cv.ADAPTIVE_THRESH_MEAN_C, cv.THRESH_BINARY_INV, 3, 1)
ここにコードがないコンポーネントは、過去の記事にあります ('◇’)ゞ