یکی از خستهکنندهترین خطاهایی که در توسعهی سیستمهای بینایی ماشین تجربه میکنید، باگهای بیصدایی هستند که هیچ خطایی چاپ نمیکنند، اما دقت مدل را ناگهان به قهقرا میبرند. کافی است تصویری با فرمت BGR را به مدلی بدهید که روی RGB آموزش دیده، یا ترتیب کانالها را از CHW به HWC اشتباه بفرستید؛ شبکه بدون کرش کردن خروجی اشتباه تولید میکند و روزها وقت صرف دیباگ کردن ساختار شبکه میشود، در حالی که ریشهی مشکل در همان مرحلهی ورودی و لود تصویر بوده است.
درک عمیق این که یک تصویر در حافظه چگونه نگهداری میشود، مفهوم دقیق پیکسل به عنوان یک نمونه نوری نه یک مربع، و سازوکار فضاهای رنگی مختلف مثل RGB، HSV و مقادیر خاکستری، سنگ بنای تسلط بر یادگیری عمیق در بینایی ماشین است. در این درس یاد میگیرید که چطور پایپلاین پیشپردازش ورودی مدلهای آمادهی PyTorch را از پایه با NumPy و ابزارهای استاندارد بسازید و تضمین کنید دادهها دقیقاً با همان فرمت مورد انتظار وارد شبکه میشوند.
تصویر، تنسوری از نمونههای نور است. هر مدل بینایی ماشینی که تا به حال استفاده کردهاید یا بعداً به کار خواهید برد، کارش را دقیقاً از همین واقعیت شروع میکند.
اهداف یادگیری
- توضیح اینکه چطور یک صحنهی پیوسته به پیکسلها گسستهسازی میشود و چرا تصمیمهای مربوط به نمونهبرداری و کوانتیزاسیون، سقف کارایی تمام مدلهای بعدی را تعیین میکنند
- خواندن، برش زدن و بررسی تصاویر در قالب آرایههای NumPy و جابهجایی مسلط بین چینشهای HWC و CHW
- تبدیل فضاهای رنگی RGB ،grayscale ،HSV و YCbCr به یکدیگر و توجیه چرایی وجود هر یک
- اعمال پیشپردازشهای سطح پیکسل (نرمالسازی، استانداردسازی، تغییر اندازه و کانال-اول) دقیقاً به همان شکلی که مدلهای بینایی ماشین ازپیشآموزشدیدهی PyTorch انتظار دارند
صورت مسئله
هر مقالهای که بخوانید، هر وزن ازپیشآموزشدیدهای که دانلود کنید و هر API بینایی ماشینی که فراخوانی کنید، نوع خاصی از کدگذاری را برای ورودی فرض میگیرد. اگر تصویری از نوع uint8 را به مدلی بدهید که ورودی float32 میخواهد، کد همچنان اجرا میشود — و در سکوت کامل، خروجی پرتوپلا تولید میکند. اگر تصویر BGR را به شبکهای بدهید که روی RGB آموزش دیده است، دقت مدل 10 درصد افت میکند. اگر ورودی با چینش کانال-آخر (channels-last) را به مدلی بدهید که کانال-اول (channels-first) میخواهد، نخستین لایهی کانولوشن بعد ارتفاع را به چشم یک کانال ویژگی در نظر میگیرد. هیچکدام از اینها خطایی نمیدهند؛ فقط معیارهای ارزیابیتان خراب میشوند و باید یک هفته تمام وقت بگذارید تا باگی را پیدا کنید که ریشهاش فقط در نحوهی بارگذاری فایل بوده است.
کانولوشن اصلاً عملیات پیچیدهای نیست، به شرطی که بدانید قرار است روی چه چیزی بلغزد. بخش دشوار ماجرا اینجاست که مفهوم «تصویر» برای دوربین، دیکودر JPEG، کتابخانهی PIL، کتابخانهی OpenCV، کتابخانهی torchvision و کرنل CUDA تفاوت دارد. هر بخش از این پشته ترتیب محورها، بازهی بایتی و قراردادهای کانالیِ خاص خودش را دارد. مهندس بینایی ماشینی که نتواند این تفاوتها را درست مدیریت کند، پایپلاینهای معیوب روانهی محیط عملیاتی خواهد کرد.
این درس زیربنای کار را محکم میکند تا ادامهی این فاز روی پایههای درستی بنا شود. در پایان این درس میدانید پیکسل چیست، چرا هر پیکسل بهجای یک عدد با سه عدد توصیف میشود، «نرمالسازی با آمارههای ImageNet» در عمل چه کار میکند و چطور بین دو یا سه چینشی جابهجا شوید که در تمام درسهای بعدی این فاز فرض گرفته خواهند شد.
مفاهیم پایه
نگاهی گذرا به کل پایپلاین پیشپردازش
تمام سیستمهای بینایی ماشین در محیط عملیاتی، توالی یکسانی از تبدیلهای برگشتپذیر هستند. اگر یک مرحله را اشتباه بروید، مدل ورودی متفاوتی نسبت به آنچه رویش آموزش دیده میبیند.
دو جعبهی قرمز و آبی بخشهایی هستند که 80 درصد خطاهای پنهان در آنها رخ میدهد: جا انداختن استانداردسازی و چینش اشتباه.
پیکسل یک نمونه است، نه یک مربع
حسگر دوربین، تعداد فوتونهایی را میشمارد که روی شبکهای از آشکارسازهای کوچک فرود میآیند. هر آشکارساز نور را در کسری از ثانیه تجمیع میکند و ولتاژی متناسب با تعداد فوتونهای برخوردی تولید مینماید. حسگر سپس این ولتاژ را به یک عدد صحیح گسستهسازی میکند. به این ترتیب، هر آشکارساز به یک پیکسل تبدیل میشود.
Continuous scene Sensor grid Digital image
(infinite detail) (H x W detectors) (H x W integers)
~~~~~ +--+--+--+--+--+ 210 198 180 155 120
~ ~ ~ | | | | | | 205 195 178 152 118
~ light ~ ----> +--+--+--+--+--+ ----> 200 190 175 150 115
~~~~~ | | | | | | 195 185 170 148 112
+--+--+--+--+--+ 188 180 165 145 108
در این مرحله دو تصمیم گرفته میشود که سقف کیفیت تمام مراحل بعدی را مشخص میکنند:
- نمونهبرداری مکانی (Spatial sampling) تعیین میکند در هر درجه از صحنه چه تعداد آشکارساز قرار گیرد. اگر تعداد کم باشد، لبهها دندانهدار میشوند (الیاسینگ (تداخل فرکانسی) یا همان aliasing). اگر زیاد باشد، فضای ذخیرهسازی و حجم محاسبات سر به فلک میکشد.
- کوانتیزاسیون شدت نور (Intensity quantization) تعیین میکند ولتاژ با چه دقتی در دستههای عددی قرار گیرد. کدگذاری 8 بیتی 256 سطح شدت ارائه میدهد و برای نمایشگرها استاندارد است. دقتهای 10، 12 و 16 بیتی گرادیانهای نرمتری ایجاد میکنند و در تصویربرداری پزشکی، تصاویر HDR و پایپلاینهای حسگر خام (raw) اهمیت دارند.
پیکسل یک مربع رنگیِ دارای مساحت نیست؛ بلکه یک مقدار اندازهگیریشده است. وقتی ابعاد تصویری را تغییر میدهید یا آن را میچرخانید، در واقع دارید همان شبکهی اندازهگیری را بازنمونهبرداری (resample) میکنید.
چرا سه کانال؟
یک آشکارساز، فوتونها را در کل طیف مرئی ثبت میکند — که حاصل آن همان تصویر خاکستری (grayscale) است. حسگر برای ثبت رنگ، این شبکه را با موزاییکی از فیلترهای قرمز، سبز و آبی میپوشاند. پس از مرحلهی درونیابی موزاییکی (demosaicing)، هر موقعیت مکانی دارای سه عدد صحیح میشود: پاسخ آشکارسازهای مجاور با فیلترهای قرمز، سبز و آبی. این سه عدد صحیح، سهگانهی RGB یک پیکسل را میسازند.
One pixel in memory:
(R, G, B) = (210, 140, 30) <- reddish-orange
An H x W RGB image:
shape (H, W, 3) stored as H rows of W pixels of 3 values
each in [0, 255] for uint8
عدد سه هیچ ویژگی جادویی خاصی ندارد. دوربینهای عمقسنج یک کانال Z هم اضافه میکنند. ماهوارهها باندهای فروسرخ و فرابنفش دارند. اسکنهای پزشکی اغلب یک کانال دارند (مثل رادیوگرافی و CT) یا چند کاناله هستند (تصویربرداری ابرطیفی). تعداد کانالها آخرین محور تنسور را تشکیل میدهد؛ لایههای کانولوشن یاد میگیرند که در امتداد این کانالها ترکیب انجام دهند.
دو قرارداد برای چینش ابعاد: HWC و CHW
یک تنسور واحد، اما با دو شیوهی مرتبسازی. هر کتابخانهای یکی را انتخاب میکند.
HWC (height, width, channels) CHW (channels, height, width)
W -> H ->
+-----+-----+-----+ +-----+-----+
H |R G B|R G B|R G B| C |R R R R R R|
| +-----+-----+-----+ | +-----+-----+
v |R G B|R G B|R G B| v |G G G G G G|
+-----+-----+-----+ +-----+-----+
|B B B B B B|
+-----+-----+
PIL, OpenCV, matplotlib, PyTorch, most deep learning
almost every image file on disk frameworks, cuDNN kernels
چینش CHW به این دلیل به وجود آمد که کرنلهای کانولوشن در امتداد H و W میلغزند. با قرار دادن محور کانال در ابتدا، هر کرنل در هر کانال یک صفحهی دوبعدی پیوسته در حافظه (contiguous) میبیند؛ این ویژگی کار برداریسازی را تمیزتر و بهینهتر میکند. فرمتهای ذخیرهسازی روی دیسک ساختار HWC را حفظ میکنند؛ چون این حالت با نحوهی خروج خطوط روبش (scanlines) از حسگر همخوانی دارد.
تبدیل تکخطی زیر را هزاران بار تایپ خواهید کرد:
img_chw = img_hwc.transpose(2, 0, 1) # NumPy
img_chw = img_hwc.permute(2, 0, 1) # PyTorch tensor
نمایش بصری چیدمان در حافظه:
بازههای بایتی و نوع داده (dtype)
سه قرارداد رایج داریم:
| قرارداد | نوع داده (dtype) | بازه | کجا دیده میشود |
|---|---|---|---|
| خام (Raw) | uint8 |
[0, 255] | فایلهای روی دیسک، خروجی PIL و OpenCV |
| نرمالشده (Normalized) | float32 |
[0.0, 1.0] | پس از اجرای img.astype('float32') / 255 |
| استانداردشده (Standardized) | float32 |
تقریباً بین [-2, +2] | پس از کسر میانگین و تقسیم بر انحراف معیار |
شبکههای عصبی کانولوشنی با ورودیهای استانداردشده آموزش دیدهاند. آمارههای ImageNet یعنی mean=[0.485, 0.456, 0.406] و std=[0.229, 0.224, 0.225]، میانگین حسابی و انحراف معیار سه کانال روی کل مجموعهی آموزشی ImageNet هستند که با پیکسلهای نرمالشده در بازهی [0, 1] محاسبه شدهاند. فرستادن دادهی خام uint8 به مدلی که منتظر مقادیر اعشاری استانداردشده است، شایعترین خطای پنهان در پروژههای کاربردی بینایی ماشین محسوب میشود.
فضاهای رنگی و چرایی وجود آنها
RGB فرمت ثبت تصاویر است، اما لزوماً مفیدترین نمایش داده برای یک مدل نیست.
RGB HSV YCbCr / YUV
R red H hue (angle 0-360) Y luminance (brightness)
G green S saturation (0-1) Cb chroma blue-yellow
B blue V value/brightness (0-1) Cr chroma red-green
Linear to Separates color from Separates brightness from
sensor output brightness. Useful for color. JPEG and most video
color thresholding, UI codecs compress the chroma
sliders, simple filters channels harder because the
human eye is less sensitive
to chroma detail than to Y.
برای بیشتر CNNهای مدرن ورودی RGB میفرستید. در این شرایط با سایر فضاها سروکار خواهید داشت:
- HSV — کدهای کلاسیک بینایی ماشین، قطعهبندی بر پایهی رنگ، تنظیم تعادل سفیدی.
- YCbCr — خواندن ساختار داخلی JPEG، پایپلاینهای ویدیو، مدلهای وضوحافزایی (super-resolution) که فقط روی مؤلفهی Y کار میکنند.
- Grayscale — در OCR، مدلهای تحلیل اسناد و هر جایی که رنگ بهجای سیگنال، یک متغیر مزاحم باشد.
تبدیل RGB به تصویر خاکستری یک جمع وزندار است نه یک میانگین ساده؛ چون چشم انسان به رنگ سبز حساستر از قرمز یا آبی است:
نسبت تصویر، تغییر اندازه و درونیابی
هر مدلی ورودی با ابعاد ثابتی میگیرد (224x224 در اکثر مدلهای دستهبندی ImageNet و 384x384 یا 512x512 در مدلهای جدید تشخیص اشیا). تصاویر شما بهندرت با این ابعاد هماندازهاند. سه گزینهی مهم برای تغییر اندازه وجود دارد:
- تغییر اندازهی ضلع کوچکتر و سپس برش از مرکز (Center crop) — دستورالعمل استاندارد ImageNet. نسبت ابعاد حفظ میشود، اما نواری از پیکسلهای لبه دور ریخته خواهد شد.
- تغییر اندازه همراه با پدینگ (Resize and pad) — نسبت ابعاد و تمام پیکسلها را حفظ میکند، ولی نوارهای سیاهی در حاشیه به جا میگذارد. گزینهی استاندارد در سیستمهای تشخیص شیء و OCR.
- تغییر اندازه مستقیم به ابعاد هدف — تصویر را میکشد. ارزان است، ساختار هندسی را تغییر میدهد، ولی برای بسیاری از وظایف دستهبندی جواب میدهد.
روش درونیابی مشخص میکند در زمان جابهجایی شبکهی پیکسلهای قدیم و جدید، مقادیر پیکسلهای میانی چگونه محاسبه شوند:
Nearest neighbour fastest, blocky, only choice for masks/labels
Bilinear fast, smooth, default for most image resizing
Bicubic slower, sharper on upscaling
Lanczos slowest, best quality, used for final display
یک قاعدهی تجربی: Bilinear برای آموزش، Bicubic یا Lanczos برای فایلهایی که قرار است انسان به آنها نگاه کند، و Nearest برای هر دادهای که شامل شناسههای عددی صحیح کلاسها باشد.
پیادهسازی
مرحلهی 1: ساخت تنسور تصویر و بررسی ابعاد آن
کار را با یک تصویر مصنوعی قطعی شروع میکنیم تا این بخش اول بدون نیاز به اینترنت و صرفاً با NumPy اجرا شود. دیکود کردن فایل یک مرز مجزاست: بهمحض اینکه دیکودر JPEG یا PNG بایتهای RGB را برگرداند، تمامی عملیاتهای تنسوری بعدی یکسان خواهند بود.
import numpy as np
def synthetic_rgb(h=128, w=192, seed=0):
rng = np.random.default_rng(seed)
yy, xx = np.meshgrid(np.linspace(0, 1, h), np.linspace(0, 1, w), indexing="ij")
r = (np.sin(xx * 6) * 0.5 + 0.5) * 255
g = yy * 255
b = (1 - yy) * xx * 255
rgb = np.stack([r, g, b], axis=-1) + rng.normal(0, 6, (h, w, 3))
return np.clip(rgb, 0, 255).astype(np.uint8)
arr = synthetic_rgb()
print(f"type: {type(arr).__name__}")
print(f"dtype: {arr.dtype}")
print(f"shape: {arr.shape} # (H, W, C)")
print(f"min: {arr.min()}")
print(f"max: {arr.max()}")
print(f"pixel at (0, 0): {arr[0, 0]}")
خروجی مورد انتظار: shape: (H, W, 3)، نوع دادهی dtype: uint8، بازهی [0, 255]. این همان نمایش متعارف و دیکودشده است؛ فرقی ندارد که این بایتها از دوربین خوانده شده باشند، از یک فایل تصویر یا همین تولیدکنندهی مصنوعی.
مرحلهی 2: جداسازی کانالها و بازچینی ابعاد
کانالهای R ،G و B را جداگانه بیرون میکشیم و سپس چینش HWC را برای استفاده در PyTorch به CHW تغییر میدهیم.
R = arr[:, :, 0]
G = arr[:, :, 1]
B = arr[:, :, 2]
print(f"R shape: {R.shape}, mean: {R.mean():.1f}")
print(f"G shape: {G.shape}, mean: {G.mean():.1f}")
print(f"B shape: {B.shape}, mean: {B.mean():.1f}")
arr_chw = arr.transpose(2, 0, 1)
print(f"\nHWC shape: {arr.shape}")
print(f"CHW shape: {arr_chw.shape}")
نتیجه سه صفحهی خاکستری است، یکی برای هر کانال. چینش CHW فقط ترتیب محورها را عوض میکند؛ تا وقتی ساختار حافظه اجازه دهد، اصلاً نیازی به کپی کردن دادهها نیست.
مرحلهی 3: تبدیل به تصویر خاکستری و HSV
ابتدا تبدیل به تصویر خاکستری با جمع وزندار و سپس پیادهسازی دستی RGB به HSV.
def rgb_to_grayscale(rgb):
weights = np.array([0.299, 0.587, 0.114], dtype=np.float32)
return (rgb.astype(np.float32) @ weights).astype(np.uint8)
def rgb_to_hsv(rgb):
rgb_f = rgb.astype(np.float32) / 255.0
r, g, b = rgb_f[..., 0], rgb_f[..., 1], rgb_f[..., 2]
cmax = np.max(rgb_f, axis=-1)
cmin = np.min(rgb_f, axis=-1)
delta = cmax - cmin
h = np.zeros_like(cmax)
mask = delta > 0
argmax = np.argmax(rgb_f, axis=-1)
rmax = mask & (argmax == 0)
gmax = mask & (argmax == 1)
bmax = mask & (argmax == 2)
h[rmax] = ((g[rmax] - b[rmax]) / delta[rmax]) % 6
h[gmax] = ((b[gmax] - r[gmax]) / delta[gmax]) + 2
h[bmax] = ((r[bmax] - g[bmax]) / delta[bmax]) + 4
h = h * 60.0
s = np.divide(delta, cmax, out=np.zeros_like(delta), where=cmax > 0)
v = cmax
return np.stack([h, s, v], axis=-1)
gray = rgb_to_grayscale(arr)
hsv = rgb_to_hsv(arr)
print(f"gray shape: {gray.shape}, range: [{gray.min()}, {gray.max()}]")
print(f"hsv shape: {hsv.shape}")
print(f"hue range: [{hsv[..., 0].min():.1f}, {hsv[..., 0].max():.1f}] degrees")
print(f"sat range: [{hsv[..., 1].min():.2f}, {hsv[..., 1].max():.2f}]")
print(f"val range: [{hsv[..., 2].min():.2f}, {hsv[..., 2].max():.2f}]")
فام (Hue) بر حسب درجه و اشباع (Saturation) و روشنایی (Value) در بازهی [0, 1] به دست میآیند. این خروجی با قرارداد hsv_full در OpenCV هماهنگ است.
مرحلهی 4: نرمالسازی، استانداردسازی و بازگشت به حالت اول
تبدیل بایتهای خام به همان تنسوری که یک مدل ImageNet ازپیشآموزشدیده نیاز دارد، و سپس معکوس کردن آن فرایند.
mean = np.array([0.485, 0.456, 0.406], dtype=np.float32)
std = np.array([0.229, 0.224, 0.225], dtype=np.float32)
def preprocess_imagenet(rgb_uint8):
x = rgb_uint8.astype(np.float32) / 255.0
x = (x - mean) / std
x = x.transpose(2, 0, 1)
return x
def deprocess_imagenet(chw_float32):
x = chw_float32.transpose(1, 2, 0)
x = x * std + mean
x = np.clip(x * 255.0, 0, 255).astype(np.uint8)
return x
x = preprocess_imagenet(arr)
print(f"preprocessed shape: {x.shape} # (C, H, W)")
print(f"preprocessed dtype: {x.dtype}")
print(f"preprocessed mean per channel: {x.mean(axis=(1, 2)).round(3)}")
print(f"preprocessed std per channel: {x.std(axis=(1, 2)).round(3)}")
roundtrip = deprocess_imagenet(x)
max_diff = np.abs(roundtrip.astype(int) - arr.astype(int)).max()
print(f"roundtrip max pixel diff: {max_diff} # should be 0 or 1")
میانگین هر کانال باید نزدیک به صفر و انحراف معیار آن نزدیک به یک باشد. جفتتوابع پیشپردازش و بازگشت دقیقاً همان کاری را انجام میدهند که در پسزمینهی هر فراخوانی transforms.Normalize در torchvision رخ میدهد.
مرحلهی 5: پیادهسازی تغییر اندازه از صفر
روش نزدیکترین همسایه (Nearest neighbor) هر مختصات خروجی را به یک پیکسل مبدأ نسبت میدهد. درونیابی Bilinear چهار پیکسل اطراف را پیدا میکند و آنها را بر اساس فاصله با هم ترکیب مینماید. هر دو پیادهسازی زیر از مختصات تراز با اندپوینت (endpoint-aligned) استفاده میکنند تا اولین و آخرین پیکسل مبدأ ثابت بمانند.
def resize_coordinates(source_length, target_length):
if target_length == 1:
return np.zeros(1, dtype=np.float32)
return np.linspace(0, source_length - 1, target_length, dtype=np.float32)
def nearest_resize(image, target_height, target_width):
y = np.rint(resize_coordinates(image.shape[0], target_height)).astype(int)
x = np.rint(resize_coordinates(image.shape[1], target_width)).astype(int)
return image[y[:, None], x[None, :]]
def bilinear_resize(image, target_height, target_width):
y = resize_coordinates(image.shape[0], target_height)
x = resize_coordinates(image.shape[1], target_width)
y0 = np.floor(y).astype(int)
x0 = np.floor(x).astype(int)
y1 = np.minimum(y0 + 1, image.shape[0] - 1)
x1 = np.minimum(x0 + 1, image.shape[1] - 1)
wy = (y - y0)[:, None, None]
wx = (x - x0)[None, :, None]
source = image.astype(np.float32)
top = source[y0[:, None], x0[None, :]] * (1 - wx)
top += source[y0[:, None], x1[None, :]] * wx
bottom = source[y1[:, None], x0[None, :]] * (1 - wx)
bottom += source[y1[:, None], x1[None, :]] * wx
result = top * (1 - wy) + bottom * wy
return np.clip(np.rint(result), 0, 255).astype(image.dtype)
target_height = arr.shape[0] * 3
target_width = arr.shape[1] * 3
nearest = nearest_resize(arr, target_height, target_width)
bilinear = bilinear_resize(arr, target_height, target_width)
def local_roughness(x):
gy = np.diff(x.astype(float), axis=0)
gx = np.diff(x.astype(float), axis=1)
return float(np.abs(gy).mean() + np.abs(gx).mean())
for name, out in [("nearest", nearest), ("bilinear", bilinear)]:
print(f"{name:>8} shape={out.shape} roughness={local_roughness(out):6.2f}")
روش Nearest بیشترین زبری را دارد، چون لبههای تیز را حفظ میکند. روش Bilinear نرمتر است، چون هر پیکسل جدید دو موقعیت را روی هر محور با هم ترکیب میکند. کد تکمیلی قابل اجرا همین ایدهی تفکیکپذیر را با کرنل مکعبی Catmull-Rom به چهار همسایه در هر بعد تعمیم میدهد و هر سه نتیجه را بدون نیاز به هیچ کتابخانهی تصویری چاپ میکند.
استفادهی عملی
فریمورک PyTorch همین عملیاتها را روی تنسورهای دستهای (batched) و با آگاهی از دستگاه پردازشی انجام میدهد. کد زیر اندازهی ضلع کوچکتر را تغییر میدهد، یک برش از مرکز میگیرد، هر کانال را استانداردسازی میکند و همان تنسور NCHW مد نظر مدلهای ازپیشآموزشدیده را میسازد.
import torch
import torch.nn.functional as F
image_hwc = torch.from_numpy(synthetic_rgb(256, 320))
batch = image_hwc.permute(2, 0, 1).unsqueeze(0).float() / 255.0
height, width = batch.shape[-2:]
scale = 256 / min(height, width)
resized_height = round(height * scale)
resized_width = round(width * scale)
batch = F.interpolate(
batch,
size=(resized_height, resized_width),
mode="bilinear",
align_corners=False,
antialias=True,
)
top = (resized_height - 224) // 2
left = (resized_width - 224) // 2
batch = batch[:, :, top:top + 224, left:left + 224]
mean = torch.tensor([0.485, 0.456, 0.406]).view(1, 3, 1, 1)
std = torch.tensor([0.229, 0.224, 0.225]).view(1, 3, 1, 1)
batch = (batch - mean) / std
print(f"tensor dtype: {batch.dtype}")
print(f"batched shape: {tuple(batch.shape)}")
print(f"per-channel mean: {batch.mean(dim=(0, 2, 3)).tolist()}")
print(f"per-channel std: {batch.std(dim=(0, 2, 3)).tolist()}")
چهار مرحله، با همین ترتیب دقیق: تبدیل بایتها به مقادیر اعشاری و جابهجایی HWC به NCHW، تغییر اندازهی ضلع کوچکتر به 256، گرفتن برش 224x224 از مرکز، و در نهایت کسر میانگین ImageNet و تقسیم بر انحراف معیار آن. جابهجا کردن این ترتیب، خروجی نهایی مدل را در سکوت تغییر خواهد داد.
تحویل پروژه
این درس خروجیهای زیر را به همراه دارد:
outputs/prompt-vision-preprocessing-audit.md— پرامپتی که شناسنامهی هر مدل یا دیتاست را به یک چکلیست دقیق از شروط تغییرناپذیر پیشپردازش تبدیل میکند تا تیم به آنها پایبند بماند.outputs/skill-image-tensor-inspector.md— مهارتی که با دریافت هر تنسور یا آرایهی تصویری، نوع داده، چینش، بازهی عددی و وضعیت آن را از نظر خام، نرمال یا استاندارد بودن گزارش میکند.
تمرینها
- (ساده) یک آرایهی
uint8با ابعاد 2x2 به فرمت RGB با چهار رنگ متمایز بسازید. چینش HWC را به CHW و سپس برعکس تبدیل کنید، ابعاد هر دو را چاپ کنید و نشان دهید دادهها در مسیر رفت و برگشت کاملاً حفظ شدهاند. - (متوسط) تابع
standardize(img, mean, std)و معکوس آن را طوری بنویسید که هر دو در کنار هم تستroundtrip_max_diff <= 1را روی هر تصویر uint8 پاس کنند. این توابع باید با فراخوانی یکسان، هم روی یک تصویر منفرد در HWC و هم روی یک دسته در NCHW کار کنند. - (سخت) یک تنسور 3 کانالهی استانداردشده با ImageNet بردارید و آن را از یک کانولوشن 1x1 عبور دهید که ترکیب وزندار RGB را به یک کانال تصویر خاکستری تبدیل کند. وزنها را روی مقادیر
[0.299, 0.587, 0.114]مقداردهی اولیه کرده و قفل (freeze) کنید، و بررسی نمایید که خروجی تا حد خطای محاسبات ممیز شناور با خروجی تابع دستیrgb_to_grayscaleشما تطابق دارد. چه تبدیلات فضای رنگی کلاسیک دیگری را میتوان به شکل کانولوشن 1x1 نوشت؟
اصطلاحات کلیدی
| اصطلاح | در گفتار عامیانه | معنای واقعی آن |
|---|---|---|
| پیکسل (Pixel) | «یک مربع رنگی» | یک نمونه از شدت نور در یک نقطه از شبکه — سه عدد برای تصویر رنگی، یک عدد برای تصویر خاکستری |
| کانال (Channel) | «همان رنگ» | یکی از شبکههای مکانی موازی که در یک تنسور تصویر روی هم چیده شدهاند؛ آخرین بعد در HWC، اولین بعد در CHW |
| HWC / CHW | «شکل یا ابعاد» | ترتیب محورها در تنسور تصویر؛ دیسک و PIL از HWC استفاده میکنند، PyTorch و cuDNN از CHW |
| نرمالسازی (Normalize) | «تنظیم مقیاس تصویر» | تقسیم بر 255 برای قرار گرفتن مقادیر در بازهی [0, 1] — لازم است اما کافی نیست |
| استانداردسازی (Standardize) | «صفر کردن میانگین» | کسر میانگین و تقسیم بر انحراف معیار برای هر کانال، بهگونهای که توزیع ورودی با توزیع دادههای آموزش مدل مطابقت داشته باشد |
| تبدیل به تصویر خاکستری (Grayscale conversion) | «میانگینگیری از کانالها» | یک جمع وزندار با ضرایب 0.299/0.587/0.114 که با ادراک چشم انسان از شدت نور همخوانی دارد |
| درونیابی (Interpolation) | «نحوهی انتخاب پیکسلها در تغییر اندازه» | قاعدهای که مقادیر خروجی را در شرایطی که شبکهی جدید با شبکهی قدیم همتراز نیست مشخص میکند — Nearest برای برچسبها، Bilinear برای آموزش و Bicubic برای نمایش |
| نسبت تصویر (Aspect ratio) | «عرض به ارتفاع» | نسبتی که «تغییر اندازه و پدینگ» را از «تغییر اندازه و کشیدگی» متمایز میکند |
برای مطالعهی بیشتر
- Charles Poynton — A Guided Tour of Color Space — شفافترین بررسی فنی دربارهی دلایل تنوع فضاهای رنگی و زمان کاربرد هر یک
- PyTorch Vision Transforms Docs — مستندات کامل پایپلاین تبدیلهایی که عملاً در محیط عملیاتی به کار خواهید برد
- How JPEG Works (Colt McAnlis) — یک آموزش بصری دقیق دربارهی Chroma Subsampling ،DCT و دلایل استفادهی JPEG از فرمت YCbCr بهجای RGB
- ImageNet Preprocessing Conventions (torchvision models) — مرجع اصلی برای مقادیر
mean=[0.485, 0.456, 0.406]و اینکه چرا تمام مدلهای این مجموعه داده به آنها نیاز دارند
