NEONProtocol Sentinel expects 224x224 pixel input. Meridian cameras stream at 640x480. Every frame must be resized before inference. Get the dimensions wrong and the model outputs garbage. This is not negotiable.
NoorSame as responsive images —
object-fit: coveris center-crop,object-fit: containis resize-with-padding. I've been doing image transforms in CSS for years.
You set object-fit: cover to fill a container with a center-cropped image. You use CSS transform: rotate(45deg) to spin elements. ML image preprocessing uses the same operations: resize to a fixed input size, crop to a region of interest, flip and rotate for data augmentation.
object-fit: cover // center-crop to fill containertf.image.cropAndResize(image, boxes, [224, 224]) // crop and resize to model inputimport * as tf from '@tensorflow/tfjs';
// Resize: object-fit: contain (with padding)
function resizeContain(image: tf.Tensor3D, targetSize: [number, number]): tf.Tensor3D {
return tf.image.resizeBilinear(
image.expandDims(0) as tf.Tensor4D,
targetSize
).squeeze() as tf.Tensor3D;
}
// Resize a 640x480 camera frame to 224x224
const cameraFrame = tf.randomNormal([480, 640, 3]);
const resized = resizeContain(cameraFrame, [224, 224]);
console.log(resized.shape); // [224, 224, 3]
// Crop: extract a region of interest
function centerCrop(image: tf.Tensor3D, size: number): tf.Tensor3D {
const [h, w] = image.shape;
const startY = Math.floor((h - size) / 2);
const startX = Math.floor((w - size) / 2);
return image.slice([startY, startX, 0], [size, size, 3]);
}
// Flip: horizontal mirror (like transform: scaleX(-1))
const flipped = tf.reverse(image, 1); // reverse along width axis
// Normalize: 0-255 → 0-1 (models work better with small numbers)
const normalized = image.toFloat().div(255);
console.log('Range:', normalized.min().dataSync()[0], '-', normalized.max().dataSync()[0]);// Complete preprocessing pipeline for Protocol Sentinel
function preprocessFrame(frame: tf.Tensor3D): tf.Tensor4D {
return tf.tidy(() => {
// 1. Resize to model input size
const resized = tf.image.resizeBilinear(
frame.expandDims(0) as tf.Tensor4D,
[224, 224]
);
// 2. Normalize pixel values to [0, 1]
const normalized = resized.toFloat().div(255);
// 3. Return as batched tensor [1, 224, 224, 3]
return normalized as tf.Tensor4D;
});
}
// Batch multiple frames for efficient inference
function preprocessBatch(frames: tf.Tensor3D[]): tf.Tensor4D {
return tf.tidy(() => {
const processed = frames.map(frame => {
const resized = tf.image.resizeBilinear(
frame.expandDims(0) as tf.Tensor4D,
[224, 224]
);
return resized.toFloat().div(255);
});
return tf.concat(processed, 0) as tf.Tensor4D;
});
}
const frame = tf.randomNormal([480, 640, 3]);
const input = preprocessFrame(frame);
console.log('Model input:', input.shape); // [1, 224, 224, 3]Build a preprocessing function that resizes and normalizes an image for model input.
Write a function that normalizes pixel values from 0-255 to 0-1 and validates that the image has the correct dimensions for a model (targetHeight x targetWidth x 3 channels).
interface PreprocessResult { normalized: number[]; valid: boolean; shape: [number, number, number]; } function preprocessImage( pixels: number[], height: number, width: number, channels: number, targetHeight: number, targetWidth: number ): PreprocessResult { // 1. Normalize: divide each pixel value by 255 // 2. Check if dimensions match target (height === targetHeight, width === targetWidth, channels === 3) // 3. Return normalized array, validity flag, and shape return null; // your code here }
The transforms reveal something: Meridian's cameras all output images at an unusual resolution — one optimized for a specific model architecture.
Next: your first classifier — threshold-based image classification