前書き
画像処理について学んだ事をjavascriptのプログラムとともにまとめて行きます。
GitHubにもコードをあげているので興味があればご覧ください。
https://github.com/kengo2003/image-process-nextjs
目次
使用技術
・javascript
・Nextjs
グレースケール化
グレースケール化は、カラー画像を白黒画像に変換するプロセスです。RGB(赤、緑、青)の各の各値を一定の割合で組み合わせて、輝度情報を算出し、1つにまとめます。
このプログラムではBT.601はSDTVの規格の計算式を使用しています。
V = 0.299R + 0.587G + 0.114*B
"use client";
import React, { useEffect } from "react";
const Page = () => {
useEffect(() => {
const canvas1 = document.querySelector("#canvas1");
const ctx1 = canvas1.getContext("2d");
const canvas2 = document.querySelector("#canvas2");
const ctx2 = canvas2.getContext("2d");
const image = new Image();
const file = document.querySelector("#file");
file.onchange = () => {
if (file.files.length > 0) {
image.src = window.URL.createObjectURL(file.files[0]);
image.onload = () => {
canvas1.width = image.width;
canvas1.height = image.height;
canvas2.width = image.width;
canvas2.height = image.height;
ctx1.drawImage(image, 0, 0);
window.URL.revokeObjectURL(image.src);
render();
};
}
};
function render() {
const imageData = ctx1.getImageData(0, 0, image.width, image.height);
const pixel = imageData.data;
for (let i = 0; i < pixel.length; i += 4) {
const gray =
0.299 * pixel[i] + 0.587 * pixel[i + 1] + 0.114 * pixel[i + 2];
pixel[i] = gray;
pixel[i + 1] = gray;
pixel[i + 2] = gray;
}
imageData.data.set(pixel);
ctx2.putImageData(imageData, 0, 0);
}
}, []);
return (
<div className="text-center">
<h2 className="text-2xl pt-5">グレースケール化</h2>
<form className="py-10">
<input type="file" id="file" />
</form>
<p>処理前</p>
<canvas id="canvas1"></canvas>
<p>処理後</p>
<canvas id="canvas2"></canvas>
</div>
);
};
export default Page;
明るさ
明るさ調整は、画像全体のピクセルの輝度を増減させる操作です。各ピクセルの値に一定の値を加減することで、画像を明るくしたり暗くしたりします。例えば、各ピクセルのRGB値に10を加えると画像は明るくなります。
"use client";
import React, { useEffect } from "react";
const page = () => {
useEffect(() => {
const canvas1 = document.querySelector("#canvas1");
const ctx1 = canvas1.getContext("2d");
const canvas2 = document.querySelector("#canvas2");
const ctx2 = canvas2.getContext("2d");
const image = new Image();
const file = document.querySelector("#file");
file.onchange = () => {
if (file.files.length > 0) {
image.src = window.URL.createObjectURL(file.files[0]);
const menu = document.querySelector("#menu");
image.onload = () => {
canvas1.width = image.width;
canvas1.height = image.height;
canvas2.width = image.width;
canvas2.height = image.height;
ctx1.drawImage(image, 0, 0);
window.URL.revokeObjectURL(image.src);
render();
};
menu.onchange = () => {
if (image.src) render();
};
}
};
function render() {
const imageData = ctx1.getImageData(0, 0, image.width, image.height);
const pixel = imageData.data;
imageData.data.set(pixel);
ctx2.putImageData(imageData, 0, 0);
for (let i = 0; i < pixel.length; i += 4) {
const d = 50 - 100 * menu.selectedIndex;
pixel[i] = pixel[i] + d;
pixel[i + 1] = pixel[i + 1] + d;
pixel[i + 2] = pixel[i + 2] + d;
}
imageData.data.set(pixel);
ctx2.putImageData(imageData, 0, 0);
}
}, []);
return (
<div className="text-center">
<h2 className="text-2xl pt-5">明るさの変換</h2>
<form className="py-10">
<input type="file" id="file" />
<select id="menu">
<option>明るさ(+50)</option>
<option>明るさ(-50)</option>
</select>
</form>
<p>処理前</p>
<canvas id="canvas1"></canvas>
<p>処理後</p>
<canvas id="canvas2"></canvas>
</div>
);
};
export default page;
濃淡の反転
濃淡の反転は、各ピクセルの値を最大値から引くことで行います。
ここではどのような処理をするかというと、色を反転するということで、それぞれの数値は0~255までの256段階になっていますので、次のように各色の輝度を255から引いてあげると色が反転するという仕組みです。
R’=255-R
G’=255-G
B’=255-B
これにより、明るい部分が暗く、暗い部分が明るくなります。
ポスタリゼーション
ポスタリゼーションは、画像の色数を減らし、特定の数の色だけを使用するようにすることで、画像を単純化する技法です。RGBの各値(赤、緑、青)に対して行うことで、各ピクセルの色を0, 85, 170, 255の4つの値に制限してポスタリゼーションを実現しました。
"use client";
import React, { useEffect } from "react";
const page = () => {
useEffect(() => {
const canvas1 = document.querySelector("#canvas1");
const ctx1 = canvas1.getContext("2d");
const canvas2 = document.querySelector("#canvas2");
const ctx2 = canvas2.getContext("2d");
const image = new Image();
const file = document.querySelector("#file");
file.onchange = () => {
if (file.files.length > 0) {
image.src = window.URL.createObjectURL(file.files[0]);
const menu = document.querySelector("#menu");
image.onload = () => {
canvas1.width = image.width;
canvas1.height = image.height;
canvas2.width = image.width;
canvas2.height = image.height;
ctx1.drawImage(image, 0, 0);
window.URL.revokeObjectURL(image.src);
render();
};
}
};
menu.onchange = () => {
if (image.src) render();
};
function render() {
const imageData = ctx1.getImageData(0, 0, image.width, image.height);
const pixel = imageData.data;
for (let i = 0; i < pixel.length; i += 4) {
switch (menu.selectedIndex) {
case 0:
pixel[i] = 255 - pixel[i];
pixel[i + 1] = 255 - pixel[i + 1];
pixel[i + 2] = 255 - pixel[i + 2];
break;
case 1:
pixel[i] = Math.floor(pixel[i] / 64) * 85;
pixel[i + 1] = Math.floor(pixel[i + 1] / 64) * 85;
pixel[i + 1] = Math.floor(pixel[i + 2] / 64) * 85;
break;
}
}
imageData.data.set(pixel);
ctx2.putImageData(imageData, 0, 0);
}
}, []);
return (
<div className="text-center">
<h2 className="text-2xl pt-5">特殊効果</h2>
<form className="py-10">
<input type="file" id="file" />
<select id="menu">
<option>濃淡の反転</option>
<option>ポスタリゼーション</option>
</select>
</form>
<p>処理前</p>
<canvas id="canvas1"></canvas>
<p>処理後</p>
<canvas id="canvas2"></canvas>
</div>
);
};
export default page;
平均化フィルタ
平均化フィルタとは、画像のノイズを減らし、平滑化するためのフィルタリング手法です。各ピクセルの値をその周囲のピクセルの値の平均で置き換えます。
例えば、3x3のカーネルを使用して、各ピクセルとその周囲8ピクセルの平均を計算し、その値で中央のピクセルを置き換えます。
1/9,1/9,1/9
1/9,1/9,1/9
1/9,1/9,1/9
3x3の場合はこのようなフィルタを使用します。
ラプラシアンフィルタ
ラプラシアンフィルタは、画像のエッジを強調するためのフィルタです。これは、2次微分を用いて画像の変化の急な部分(エッジ)を強調します。
一般的な3x3ラプラシアンカーネルは以下のようになります。
0, -1, 0
-1, 4, -1
0, -1, 0
このカーネルで画像にフィルタをかけることで、エッジを抽出します。
エンボス処理
エンボス処理は、画像に浮き彫りのような効果を与えるフィルタリング手法です。エンボスフィルタを適用することで、画像の特徴が立体的に見えるようになります。
斜め方向のエンボスフィルタのカーネルは以下のようになります。
1, 0, 0
0, 0, 0
0, 0, -1
このカーネルでフィルタをかけることによって、ピクセル値の変化を強調し立体感を生み出し、画像に浮き彫りの効果を与えることができます。
"use client";
import React, { useEffect } from "react";
const page = () => {
useEffect(() => {
const canvas1 = document.querySelector("#canvas1");
const ctx1 = canvas1.getContext("2d");
const canvas2 = document.querySelector("#canvas2");
const ctx2 = canvas2.getContext("2d");
const image = new Image();
const file = document.querySelector("#file");
file.onchange = () => {
if (file.files.length > 0) {
image.src = window.URL.createObjectURL(file.files[0]);
const menu = document.querySelector("#menu");
image.onload = () => {
canvas1.width = image.width;
canvas1.height = image.height;
canvas2.width = image.width;
canvas2.height = image.height;
ctx1.drawImage(image, 0, 0);
window.URL.revokeObjectURL(image.src);
render();
};
}
};
menu.onchange = () => {
if (image.src) render();
};
function render() {
const imageData = ctx1.getImageData(0, 0, image.width, image.height);
const pixel = imageData.data;
const result = pixel.slice();
const width = image.width;
let j = new Uint32Array(9);
let w = new Int8Array(9);
for (let y = 1; y < image.height - 1; y++) {
for (let x = 1; x < width - 1; x++) {
const i = (x + y * width) * 4;
j[0] = (x - 1 + (y - 1) * width) * 4;
j[1] = (x + (y - 1) * width) * 4;
j[2] = (x + 1 + (y - 1) * width) * 4;
j[3] = (x - 1 + y * width) * 4;
j[4] = i;
j[5] = (x + 1 + y * width) * 4;
j[6] = (x - 1 + (y + 1) * width) * 4;
j[7] = (x + (y + 1) * width) * 4;
j[8] = (x + 1 + (y + 1) * width) * 4;
switch (menu.selectedIndex) {
case 0:
w = [1, 1, 1, 1, 1, 1, 1, 1, 1];
result[i] = mad(0) / 9;
result[i + 1] = mad(1) / 9;
result[i + 2] = mad(2) / 9;
break;
case 1:
w = [0, 1, 0, 1, -4, 1, 0, 1, 0];
result[i] = Math.abs(mad(0));
result[i + 1] = Math.abs(mad(1));
result[i + 2] = Math.abs(mad(2));
break;
case 2:
w = [1, 0, 0, 0, 0, 0, 0, 0, -1];
result[i] = mad(0) + 127;
result[i + 1] = mad(1) + 127;
result[i + 2] = mad(2) + 127;
break;
}
}
}
imageData.data.set(result);
ctx2.putImageData(imageData, 0, 0);
function mad(k) {
let s = 0;
for (let i = 0; i < 9; i++) {
s += w[i] * pixel[j[i] + k];
}
return s;
}
}
}, []);
return (
<div className="text-center">
<h2 className="text-2xl pt-5">空間フィルタリング</h2>
<form className="py-10">
<input type="file" id="file" />
<select id="menu">
<option>平均化フィルタ</option>
<option>ラプラシアンフィルタ</option>
<option>エンボス処理</option>
</select>
</form>
<p>処理前</p>
<canvas id="canvas1"></canvas>
<p>処理後</p>
<canvas id="canvas2"></canvas>
</div>
);
};
export default page;
これらの画像変換処理を適用することで、さまざまな視覚効果を得ることができます。
今後
他のエッジ処理や他のフィルタを試して実装してみたいです。
参考文献