📷

画像処理100本ノックに挑戦|ヒストグラム操作(022/100)

2025/01/24に公開

これはなに?

画像処理100本ノックを、TypeScriptとlibvipsで挑戦してみる記事の22本目です。

前回

https://zenn.dev/nyagato_00/articles/37c98e95fee7ed

実装

お題

ヒストグラムの平均値をm0=128、標準偏差をs0=52になるように操作せよ。

これはヒストグラムのダイナミックレンジを変更するのではなく、ヒストグラムを平坦に変更する操作である。

平均値m、標準偏差s、のヒストグラムを平均値m0, 標準偏差s0に変更するには、次式によって変換する。

https://github.com/minido/Gasyori100knock-1/tree/master/Question_21_30#q22-ヒストグラム操作

Coding

import sharp from 'sharp';

export async function histogramManipulation(
  inputPath: string, 
  outputPath: string,
  targetMean: number = 128,
  targetStdDev: number = 52
): Promise<void> {
  try {
    const image = await sharp(inputPath)
      .raw()
      .toBuffer({ resolveWithObject: true });

    const { data, info } = image;
    const { width, height, channels } = info;

    // 現在の平均値と標準偏差を計算
    let sum = 0;
    let sumSquared = 0;
    const n = data.length;

    for (let i = 0; i < n; i++) {
      sum += data[i];
      sumSquared += data[i] * data[i];
    }

    const mean = sum / n;
    const variance = (sumSquared / n) - (mean * mean);
    const stdDev = Math.sqrt(variance);

    // 新しい画像データ用のバッファを作成
    const normalizedData = Buffer.alloc(data.length);

    // ヒストグラム操作を実行
    const scale = targetStdDev / stdDev;
    for (let i = 0; i < data.length; i++) {
      const normalized = scale * (data[i] - mean) + targetMean;
      normalizedData[i] = Math.min(255, Math.max(0, Math.round(normalized)));
    }

    // 結果を保存
    await sharp(normalizedData, {
      raw: {
        width,
        height,
        channels
      }
    })
    .toFile(outputPath);

    // 結果の表示
    console.log('ヒストグラム操作の結果:');
    console.log('元の統計値:', { 
      平均値: mean.toFixed(2), 
      標準偏差: stdDev.toFixed(2) 
    });

    // 操作後の統計値を計算
    let newSum = 0;
    let newSumSquared = 0;
    for (let i = 0; i < normalizedData.length; i++) {
      newSum += normalizedData[i];
      newSumSquared += normalizedData[i] * normalizedData[i];
    }
    const newMean = newSum / n;
    const newVariance = (newSumSquared / n) - (newMean * newMean);
    const newStdDev = Math.sqrt(newVariance);

    console.log('操作後の統計値:', { 
      平均値: newMean.toFixed(2), 
      標準偏差: newStdDev.toFixed(2) 
    });

  } catch (error) {
    console.error('画像処理中にエラーが発生しました:', error);
    throw error;
  }
}

結果

入力 出力

Discussion