📷
画像処理100本ノックに挑戦|ヒストグラム表示(020/100)
これはなに?
画像処理100本ノックを、TypeScriptとlibvipsで挑戦してみる記事の20本目です。
前回
実装
お題
matplotlibを用いてimori_dark.jpgのヒストグラムを表示せよ。
ヒストグラムとは画素の出現回数をグラフにしたものである。 matplotlibではhist()という関数がすでにあるので、それを利用する。
Coding
import sharp from 'sharp';
export async function generateHistogramData(inputPath: string): Promise<number[]> {
try {
// 画像を読み込む
const image = await sharp(inputPath)
.raw()
.toBuffer({ resolveWithObject: true });
const { data } = image;
// ヒストグラムデータの初期化(0-255の範囲)
const histogram = new Array(256).fill(0);
// 各ピクセル値の出現回数をカウント
for (const pixel of data) {
histogram[pixel]++;
}
return histogram;
} catch (error) {
console.error('ヒストグラム生成中にエラーが発生しました:', error);
throw error;
}
}
matplotlibではなくて、コンソールでサクッと確認できるように、ASCIIアートでヒストグラムを視覚化するようにしてみました。
import * as path from 'path';
import { generateHistogramData } from './imageProcessor';
function displayHistogramAscii(histogram: number[]) {
const maxCount = Math.max(...histogram);
const height = 20; // グラフの高さ
const width = 80; // グラフの幅
const binSize = Math.ceil(256 / width); // ビンの大きさ
// データを圧縮(ビニング)
const compressedData: number[] = [];
for (let i = 0; i < width; i++) {
const start = i * binSize;
const end = Math.min(start + binSize, 256);
const sum = histogram.slice(start, end).reduce((a, b) => a + b, 0);
compressedData.push(sum);
}
const maxCompressed = Math.max(...compressedData);
// ヒストグラムを描画
console.log('\nヒストグラム表示 (横軸: ピクセル値 0-255, 縦軸: 出現頻度)');
console.log('=' . repeat(width + 2));
for (let h = height - 1; h >= 0; h--) {
process.stdout.write('|');
for (let w = 0; w < width; w++) {
const normalizedValue = compressedData[w] / maxCompressed;
const threshold = h / height;
process.stdout.write(normalizedValue > threshold ? '█' : ' ');
}
process.stdout.write('|\n');
}
console.log('=' . repeat(width + 2));
console.log(`0${' '.repeat(width-2)}255`);
// 統計情報を表示
const total = histogram.reduce((a, b) => a + b, 0);
const mean = histogram.reduce((acc, count, value) => acc + value * count, 0) / total;
console.log(`\n統計情報:`);
console.log(`総ピクセル数: ${total}`);
console.log(`平均値: ${mean.toFixed(2)}`);
console.log(`最大出現回数: ${maxCount} (値: ${histogram.indexOf(maxCount)})`);
}
async function main() {
// __dirnameを使用して相対パスを解決
const inputPath = path.join(__dirname, '../main.jpeg');
try {
console.log('画像処理を開始します...');
const histogram = await generateHistogramData(inputPath);
// ASCII アートでヒストグラムを表示
displayHistogramAscii(histogram);
// 詳細な数値も表示
console.log('\n詳細な出現回数:');
histogram.forEach((count, value) => {
if (count > 0) {
console.log(`値 ${value.toString().padStart(3, ' ')}: ${count.toString().padStart(6, ' ')}回`);
}
});
console.log('処理が完了しました');
} catch (error) {
console.error('プログラムの実行に失敗しました:', error);
}
}
main();
結果
入力 | 出力 |
---|---|
![]() |
![]() |
Discussion