📷
画像処理100本ノックに挑戦|Maxプーリング(008/100)
これはなに?
画像処理100本ノックを、TypeScriptとlibvipsで挑戦してみる記事の8本目です。
前回
実装
お題
ここでは平均値でなく最大値でプーリングせよ。
Coding
import sharp from 'sharp';
export async function maxPooling(
inputPath: string,
outputPath: string,
gridSize: number = 8
): Promise<void> {
try {
const image = await sharp(inputPath)
.raw()
.toBuffer({ resolveWithObject: true });
const { data, info } = image;
const { width, height, channels } = info;
// Grid数を計算
const Nh = Math.floor(height / gridSize);
const Nw = Math.floor(width / gridSize);
// 元画像をコピー
const newData = Buffer.from(data);
// 各グリッドに対して処理
for (let y = 0; y < Nh; y++) {
for (let x = 0; x < Nw; x++) {
for (let c = 0; c < channels; c++) {
// グリッド内の最大値を求める
let maxValue = 0;
for (let dy = 0; dy < gridSize; dy++) {
for (let dx = 0; dx < gridSize; dx++) {
const pos = ((y * gridSize + dy) * width + (x * gridSize + dx)) * channels + c;
maxValue = Math.max(maxValue, data[pos]);
}
}
// グリッド内の全ピクセルに最大値を設定
for (let dy = 0; dy < gridSize; dy++) {
for (let dx = 0; dx < gridSize; dx++) {
const pos = ((y * gridSize + dy) * width + (x * gridSize + dx)) * channels + c;
newData[pos] = maxValue;
}
}
}
}
}
// 結果を保存
await sharp(newData, {
raw: {
width,
height,
channels
}
})
.toFile(outputPath);
} catch (error) {
console.error('画像処理中にエラーが発生しました:', error);
throw error;
}
}
Test
import { existsSync, unlinkSync } from 'fs';
import { join } from 'path';
import sharp from 'sharp';
import { maxPooling } from './imageProcessor';
describe('Max Pooling Tests', () => {
const testInputPath = join(__dirname, '../test-images/test.jpeg');
const testOutputPath = join(__dirname, '../test-images/test-maxpooled.png');
afterEach(() => {
if (existsSync(testOutputPath)) {
unlinkSync(testOutputPath);
}
});
test('should successfully process image', async () => {
await expect(maxPooling(testInputPath, testOutputPath))
.resolves.not.toThrow();
expect(existsSync(testOutputPath)).toBe(true);
});
test('should maintain image dimensions', async () => {
await maxPooling(testInputPath, testOutputPath);
const inputMetadata = await sharp(testInputPath).metadata();
const outputMetadata = await sharp(testOutputPath).metadata();
expect(outputMetadata.width).toBe(inputMetadata.width);
expect(outputMetadata.height).toBe(inputMetadata.height);
});
test('should use maximum value in each grid', async () => {
const gridSize = 8;
await maxPooling(testInputPath, testOutputPath, gridSize);
const outputImage = await sharp(testOutputPath)
.raw()
.toBuffer({ resolveWithObject: true });
const { data, info } = outputImage;
const { width, channels } = info;
// テスト用のグリッドを選択
const startX = gridSize;
const startY = gridSize;
// グリッド内の最初のピクセル値を取得
const firstPixelPos = (startY * width + startX) * channels;
const referenceValues = [
data[firstPixelPos],
data[firstPixelPos + 1],
data[firstPixelPos + 2]
];
// グリッド内の全ピクセルが同じ値(最大値)を持つことを確認
for (let dy = 0; dy < gridSize; dy++) {
for (let dx = 0; dx < gridSize; dx++) {
const pos = ((startY + dy) * width + (startX + dx)) * channels;
for (let c = 0; c < channels; c++) {
expect(data[pos + c]).toBe(referenceValues[c]);
}
}
}
});
test('should use actual maximum values', async () => {
const gridSize = 8;
// 入力画像の値を取得
const inputImage = await sharp(testInputPath)
.raw()
.toBuffer({ resolveWithObject: true });
await maxPooling(testInputPath, testOutputPath, gridSize);
const outputImage = await sharp(testOutputPath)
.raw()
.toBuffer({ resolveWithObject: true });
// テスト用のグリッドを選択してチェック
const startX = gridSize;
const startY = gridSize;
const { width, channels } = inputImage.info;
// 入力画像のグリッド内の最大値を計算
const maxValues = new Array(channels).fill(0);
for (let dy = 0; dy < gridSize; dy++) {
for (let dx = 0; dx < gridSize; dx++) {
const pos = ((startY + dy) * width + (startX + dx)) * channels;
for (let c = 0; c < channels; c++) {
maxValues[c] = Math.max(maxValues[c], inputImage.data[pos + c]);
}
}
}
// 出力画像の値が最大値と一致することを確認
const outputPos = (startY * width + startX) * channels;
for (let c = 0; c < channels; c++) {
expect(outputImage.data[outputPos + c]).toBe(maxValues[c]);
}
});
});
結果
入力 | 結果 |
---|---|
Discussion