- RGB normalization में अगर आप किसी अपरिचित image file को प्रोसेस करके फिर 8-bit में सेव कर रहे हैं, तो 255 से divide करने वाला standard तरीका उपयुक्त है
- 255 वाला तरीका 0 को 0.0 और 255 को 1.0 पर map करता है, इसलिए काले और सफेद को सीधे संभालना आसान होता है, और यह GPU के UNORM-to-float conversion तरीके से भी मेल खाता है
- 256 वाला तरीका
(img + 0.5) / 256.0के जरिए हर value को interval के center में रखता है, जिससे dithering जैसी processing में boundary handling सरल हो सकती है, लेकिन 0, 0.0 नहीं रहता, इसलिए processing logic 8-bit input से बंध जाता है - 255 वाले तरीके में दोनों सिरों के interval आधी चौड़ाई के होते हैं, इसलिए uniform
[0, 1]random numbers को वापस 8-bit में round करने पर 0 और 255, बाकी मानों की तुलना में आधी बार आते हैं, लेकिन वास्तविक image round-trip conversion lossless काम करता है - 256 वाला तरीका सिद्धांततः mean absolute error
1 / 1024देता है, जो 255 वाले तरीके के1 / 1020से थोड़ा कम है, लेकिन अगर पहले से 255 तरीके से quantize की गई image को गलत scale से पढ़ा जाए, तो यह उल्टा error बढ़ा देता है
समस्या की रूपरेखा
Image processing programs 8-bit images को floating-point में बदलते हैं, processing करते हैं, और फिर उन्हें वापस 8-bit colors के रूप में सेव करते हैं
दो conversion तरीके इस प्रकार हैं
# Standard: 255 से divide करना
pixels = img / 255.0
result = process(pixels)
output = np.trunc(result * 255 + 0.5)
# Alternative: 0.5 जोड़कर 256 से divide करना
pixels = (img + 0.5) / 256.0
result = process(pixels)
output = np.trunc(result * 256)
दोनों तरीकों में final conversion से पहले values को 0~255 तक सीमित किया जाता है
output_8bit = output.clip(0, 255).astype(np.uint8)
Standard तरीका integer 0 को 0.0 और 255 को 1.0 पर map करता है, और GPU के UNORM-to-float conversion तरीके जैसा है
Alternative तरीका 0 को 0.5 / 256 = 0.001953125 पर map करता है, इसलिए काले pixel को detect करने के लिए इस constant को जानना पड़ता है
255 से divide करने वाले standard तरीके की विशेषताएँ
Standard तरीके में [0, 1] range के भीतर दोनों सिरों के values के interval, बाकी interval की तुलना में प्रभावी रूप से आधी चौड़ाई के होते हैं
अगर uniform [0, 1] random numbers बनाए जाएँ और trunc(result * 255 + 0.5) से round किया जाए, तो 0 और 255 बाकी integers की तुलना में आधी frequency से दिखाई देंगे
लेकिन मूल 8-bit images uint8 → float → uint8 round-trip conversion में बिना किसी loss के वापस आ जाती हैं
इसके अलावा, अगर processing result 0.0 या 1.0 से थोड़ा बाहर भी चला जाए, तो clamp और rounding के बाद वह सही integer range में आ सकता है
उदाहरण के लिए, अगर floating-point color से 0.005 घटाया जाए, तो standard तरीके में black negative हो जाएगा, लेकिन final result फिर भी integer 0 ही रहेगा
trunc(255 * (-0.005) + 0.5) = 0
Floating-point precision और interval center placement
255 वाले तरीके की कुछ values exactly represent नहीं होतीं
उदाहरण के लिए 128 / 255.0 ≈ 0.501961 है, जबकि 128 / 256.0 = 0.5 है
यह अंतर 32-bit floating-point के 23-bit mantissa में सबसे निचले bit स्तर की rounding error है, और इसका आकार 2^-23 से छोटा है
इसलिए यह inaccuracy व्यावहारिक तकनीकी समस्या से ज़्यादा एक सौंदर्यगत मुद्दे के करीब है
256 वाला तरीका हर floating-point value को दो integers के बीच के सटीक center पर रखता है
जब यह पता न हो कि मूल quantized value वास्तव में क्या थी, तब इस गुण को दो लगातार integers के बीच के average point का एक समझौता माना जा सकता है
Andrew Kesler की 2015 की पोस्ट “Converting Color Depth” के अनुसार, यह तरीका dithering में noise जोड़ते समय boundary handling को कम चिंताजनक बना सकता है
इसके उलट, standard तरीके में दोनों सिरों के interval के लिए noise distribution को consistent रखने हेतु सावधानीपूर्वक handling की ज़रूरत होती है
Quantization के नज़रिए से
दोनों तरीकों को uniform scalar quantizer के रूप में देखा जा सकता है
Wikipedia की quantization व्याख्या) signed input data के uniform quantizer को मुख्यतः mid-riser और mid-tread में बाँटती है
mid-tread में 0 value reconstruction level होता है, जबकि mid-riser में 0 value classification threshold होता है
संबंधित formulas इस प्रकार हैं
| तरीका | Encoding | Decoding |
|---|---|---|
| mid-tread | k = trunc(x L + 0.5) |
y_k = k / L |
| mid-riser | k = trunc(x L) |
y_k = (k + 0.5) / L |
Standard तरीका L=255 का उपयोग करने वाला mid-tread रूप है, और alternative तरीका L=256 का उपयोग करने वाला mid-riser रूप है
Standard तरीका programming convenience के लिए 0.0 और 1.0 पर endpoints को align करता है, लेकिन यह 8-bit input के लिए सबसे उपयुक्त interval placement नहीं है
Reconstruction error और वास्तविक image processing
अगर आप uniform distribution वाली real values x ∈ [0, 1] को 8-bit integers में encode करके फिर real values में reconstruct करने वाली system को सीधे design कर रहे हैं, तो 256 वाला तरीका सिद्धांततः अधिक precise है
Standard तरीके की representable range [-0.5 / 255, 255.5 / 255] बनती है, जिससे [0, 1] के लिए ज़रूरी सीमा की तुलना में interval spacing थोड़ी चौड़ी हो जाती है
StackOverflow उपयोगकर्ता Peter Mudrievskij की गणना के अनुसार mean absolute error, 255 से divide करने पर 1 / 1020 और 256 से divide करने पर 1 / 1024 है
लेकिन पहले से सेव की गई 8-bit RGB images को पढ़कर प्रोसेस करने की स्थिति में, सेव करते समय खोई हुई जानकारी वापस नहीं आती
अगर image को 255 से multiply और round करने वाले तरीके से quantize किया गया था, तो load करते समय 256 से divide करने पर precision वापस नहीं मिलेगी
दूसरों द्वारा बनाई गई images अधिकतर standard तरीके से quantize की गई होने की संभावना है, इसलिए alternative formula से पढ़ने पर सैद्धांतिक रूप से गलत scale factor इस्तेमाल होगा
व्यवहार में इसका मतलब यह है कि colors को थोड़ा छोटे range और छोटे offset के साथ process किया जाएगा
दो quantizer के encoding और decoding चरणों को मिलाना टूटा हुआ code बन जाता है
निष्कर्ष
अगर आप किसी अपरिचित स्रोत से मिली image को प्रोसेस कर रहे हैं, तो RGB values को 255 से normalize करना चाहिए
सिर्फ इस वजह से कि floating-point values exact नहीं हैं, या abstract reconstruction error थोड़ा बड़ा लगता है, 256 वाले तरीके को चुनने का आधार कमजोर है
अगर आप image saving और loading दोनों को नियंत्रित करते हैं, 0 का 0 पर map होना ज़रूरी नहीं है, और processing code का 8-bit dynamic range से बँधा होना स्वीकार्य है, तो 256 से divide करके थोड़ी अधिक सैद्धांतिक precision हासिल की जा सकती है
1 टिप्पणियां
Lobste.rs की राय
अगर यह सहज न लगे, तो इसे 2-बिट के साधारण उदाहरण से देख सकते हैं। जब संभव integer values सिर्फ 0, 1, 2, 3 हों, तो integer→floating-point रूपांतरण के सभी मामलों को गिनने पर पता चलता है कि काला/सफेद को सचमुच काला/सफेद बनाए रखने और intervals को बराबर रखने के लिए मान 0.0, 0.33..., 0.66..., 1.0 होने चाहिए
इसलिए reverse conversion में 4(2^2) नहीं, बल्कि 3 से गुणा करना चाहिए
reverse conversion में quantization (rounding) चाहिए, और symmetry यहीं टूटती है
अगर 0..=1 रेंज में एक uniform real gradient बनाकर उसे 0, 1, 2, 3 में quantize करें, तो दिखेगा कि 3 से गुणा करने पर नतीजा uniform नहीं रहता। ×3 के बाद
round()करने पर 1 और 2 ज़्यादा represent होते हैं, और ×3 के बादfloorयाceilकरने पर 0 या 3 ऐसे fold हो जाते हैं कि gradient मानो 4 रंगों में से सिर्फ 3 ही इस्तेमाल कर रहा हो/3और×3वाली logic exact numbers के round-trip conversion के लिए ठीक लग सकती है, लेकिन बीच के मान rounding के चुनाव से बहुत प्रभावित होते हैं, और डेटा processing शुरू करते ही यही महत्वपूर्ण हो जाता हैintegers का uniform proportion सिर्फ (4-ε) से गुणा करके floor लेने पर मिलता है, जो ×4,
floor(),clamp()के बराबर है। यह 1 के अजीब अंतर या ε-error जैसा लग सकता है, लेकिन सहज रूप से यही सबसे अच्छा दिखने वाला हल हैमेरे लिए जवाब हमेशा “बिलकुल” [0.0..255.0] रहा है, लेकिन शायद यह सबके लिए उतना स्पष्ट नहीं है
लेख में कहा गया है कि “extreme” intervals की क्षमता बाकी intervals की आधी है, लेकिन मुझे यह framing भी सही नहीं लगती
अगर [0..1] के बाहर कोई values हैं ही नहीं, तो उनका संकरा interval दिखना rendering का प्रभाव है। आपने यह जानते हुए कि range के बाहर कोई value नहीं है, buckets को काट दिया है, इसलिए वे संकरे render हुए हैं
उलटे, अगर [0..1] के बाहर values मौजूद हैं, तो वह range अनंत है। लेख बाद वाली बात मानता है, लेकिन पहली नहीं
जैसे ही पहली बात मान लें, सही behavior काफ़ी स्पष्ट लगता है, लेकिन इस तरह का लेख आया ही है, यह बताता है कि यह वस्तुनिष्ठ रूप से इतना “स्पष्ट” सवाल नहीं है :D
अगर 0..<1 integer 0 पर जाए, और 254>..255.0 integer 255 पर, तो 128 बीच में गायब हो जाता है। शायद आप चाहेंगे कि 127.5..128.5, 128 पर जाए, लेकिन फिर ये आधे हिस्से कहाँ जाएंगे?
अगर 128 को सही बैठाने के लिए पूरी range को थोड़ा shift करें, तो 0..0.99609375 integer 0 पर map होता है
round()बुला देते हैंलोगों को वह तरीका काफ़ी natural लगता है, इसलिए शायद सादगी की वजह से वही standard बन गया
pngcrushसे compress किया था। या आपका मतलब है कि image की सामग्री में कुछ गड़बड़ है?