javascriptで行う画像処理

## 前書き
画像処理について学んだ事をjavascriptのプログラムとともにまとめて行きます。
GitHubにもコードをあげているので興味があればご覧ください。
https://github.com/kengo2003/image-process-nextjs

## 目次
1. [使用技術](#使用技術)
2. [グレースケール化](#グレースケール化)
3. [明るさ](#明るさ)
4. [濃淡の反転](#濃淡の反転)
5. [ポスタリゼーション](#ポスタリゼーション)
6. [平均化フィルタ](#平均化フィルタ)
7. [ラプラシアンフィルタ](#ラプラシアンフィルタ)
8. [エンボス処理](#エンボス処理)
9. [今後](#今後)
10. [参考文献](#参考文献)

## 使用技術
・javascript
・Nextjs

## グレースケール化
グレースケール化は、カラー画像を白黒画像に変換するプロセスです。RGB（赤、緑、青）の各の各値を一定の割合で組み合わせて、輝度情報を算出し、1つにまとめます。  
このプログラムではBT.601はSDTVの規格の計算式を使用しています。
```
V = 0.299R + 0.587G + 0.114*B
```

```javascript
"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を加えると画像は明るくなります。

```javascript
"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つの値に制限してポスタリゼーションを実現しました。
```javascript
"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

このカーネルでフィルタをかけることによって、ピクセル値の変化を強調し立体感を生み出し、画像に浮き彫りの効果を与えることができます。

```javascript
"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;
```

これらの画像変換処理を適用することで、さまざまな視覚効果を得ることができます。

## 今後
他のエッジ処理や他のフィルタを試して実装してみたいです。

## 参考文献
https://qiita.com/deneimonmaru/items/4b68220f0b7ed8e3f320