Computer Vision / Vision Foundations
How a picture becomes numbers a model can read.
Reviewed by Yuvaraj
A vision model never sees a "picture." It sees a grid of numbers, and every operation it performs, convolution, attention, a dot product, is arithmetic on that grid. Before any of the interesting parts of computer vision make sense, you have to be fluent in the one representation they all share: a digital image is a tensor, a multi-dimensional array of numbers with a specific shape, data type, and memory layout. Get the representation right and the rest of the field is transformations on it; get it wrong and your model silently trains on garbage.
A grayscale image is a 2D grid: one intensity value per pixel, arranged in rows and columns. That is a tensor of shape , or equivalently when we want the channel axis to be explicit.
Ask about this lesson, or about anything in AI. Answers cite the lessons they draw on.
Finished this lesson?
Mark it complete to earn XP, keep your streak, and schedule a review.
Make that concrete. Here is a tiny grayscale image written out as the numbers a model actually receives, 0 is black, 255 is white, and values between are shades of gray:
12 34 200
45 178 255
9 87 120
Nine pixels, nine numbers, nothing more. A real photo is the same object at scale, and a color version simply stacks three such grids, one for red, one for green, one for blue.
A color image adds a channel axis. An RGB image stores three numbers per pixel, one each for the red, green, and blue components, so it is a tensor of shape . The three channels are three stacked grayscale images that combine to produce color. Other channel counts exist: a single channel for grayscale or depth maps, four for RGBA (adding an alpha/transparency channel), and many more for satellite or medical imagery.
| Image kind | Channels | Tensor shape | Value per pixel |
|---|---|---|---|
| Grayscale | 1 | one intensity | |
| RGB color | 3 | (red, green, blue) | |
| RGBA | 4 | color plus transparency | |
| Multispectral | many | one reading per spectral band |
A pixel's value needs a numeric type. The near-universal capture format is unsigned 8-bit integer (uint8): each channel value is a whole number in , giving distinct intensity levels per channel. That is why an RGB color is often written as three numbers from 0 to 255, and why million colors is the familiar figure.
Neural networks, however, do not want raw integers in . Large-magnitude inputs make gradients unstable and slow to converge, so the first step of nearly every vision pipeline is to convert to floating point and rescale into a small range, typically by dividing by 255, and then standardizing further (below). The image passes from uint8 capture format to float32 model-input format.
Rescaling to is only half the story. The stronger, standard practice is standardization: subtract a per-channel mean and divide by a per-channel standard deviation so each channel is centered near zero with roughly unit variance. Models pretrained on ImageNet use fixed statistics computed once over that dataset, mean and standard deviation for the red, green, and blue channels. The full transform for one channel value in is:
Take a mid-gray red value . Dividing by 255 gives ; subtracting the red mean and dividing by the red standard deviation gives . The pixel is now a small, centered number the optimizer can work with.
For a curious beginner
Photos vary wildly in overall brightness, a sunlit shot versus a dim indoor one. Normalizing recenters every image to a common scale, so the network is not thrown off by exposure and can concentrate on shapes and patterns. It is like adjusting every photo to the same baseline exposure before comparing them.
How it is actually used
Divide by 255 to reach , then subtract the per-channel dataset mean and divide by the per-channel standard deviation. Apply the exact same statistics at training and inference time, a mismatch here is a classic silent bug. With a pretrained backbone, reuse that model's published mean and standard deviation, not statistics from your own data.
The underlying mechanism
Standardization maps each channel to approximately zero mean and unit variance. This conditions the optimization: the first layer's gradients are better scaled across input dimensions, so gradient descent takes more uniform, stable steps and converges faster. It is the input-layer analogue of what batch normalization does between hidden layers.
A standard model input is a RGB image. Its element count is:
Memory depends entirely on the data type. In uint8, each value is 1 byte, so the image is bytes, exactly KiB. Converted to float32, each value takes 4 bytes:
The float version costs 4x the memory of the integer version for identical pixels, which is why images are stored and moved as uint8 and cast to float only inside the pipeline. It also compounds fast: a training batch of 32 such images in float32 is about MiB before a single feature map is computed.
| Data type | Bytes per value | One image | Typical role |
|---|---|---|---|
uint8 | 1 | 147 KiB | capture and storage, |
float16 | 2 | 294 KiB | half precision, common on GPUs |
float32 | 4 | 588 KiB | default model input |
The tensor is conceptually multi-dimensional, but RAM is a flat one-dimensional line of bytes. Row-major order (the C and NumPy default) stores the last axis contiguously: consecutive bytes walk across a row before stepping down to the next. The framework tracks a stride per axis to translate an index like (row, col, channel) into a flat offset.
Two conventions decide where the channel axis sits, and mixing them up is a frequent source of shape errors:
The leading is the batch dimension: models process many images at once, so a real input tensor is 4D, in PyTorch, in TensorFlow. Converting between the two layouts is a cheap axis permutation (transpose/permute), but forgetting to do it feeds a channel-first tensor to a channel-last op and produces either a crash or, worse, a model that trains on scrambled data.
Common mistakes
uint8 pixels in straight into a network instead of
casting to float and rescaling, large inputs destabilize training. - Using
different normalization statistics at training and inference, or inventing
your own mean and standard deviation when fine-tuning a pretrained backbone
that expects ImageNet values. - Confusing channel-first (NCHW) and
channel-last (NHWC) layouts, so a permute is missed and the model silently
learns from scrambled axes. - Forgetting the batch dimension and passing a 3D
tensor to a layer that expects 4D. - Assuming RGB order when a library
(notably OpenCV) loads images as BGR, swapping the red and blue channels. -
Storing or transferring images as float32 when uint8 would do, quadrupling
memory and bandwidth for no benefit.