0
0

javascriptで行う画像処理

Last updated at Posted at 2024-07-15

前書き

画像処理について学んだ事を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
"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;

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

今後

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

参考文献

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0