မိတ္ေဆြ၊ ညီအစ္ကို၊ ေမာင္ႏွမမ်ား အားလံုးပဲ မဂၤလာပါဗ်ာ။ ဒီေန႔ေတာ့ က်ေနာ္တို႔ Visual Studio C# ကို အသံုးျပဳၿပီး Image Processing အတြက္ pixel နဲ႔ Color တန္ဖိုးေတြကို လ်င္လ်င္ျမန္ျမန္ Detect လုပ္ႏိုင္မရ္႕ Lockbits( ), Unsafe context and Parallel.For Mehtod အေၾကာင္းေလးကို ေလ့လာၾကည့္ပါ့မရ္။ Processing အတြက္ကိုေတာ့ ေရွ႕သင္ခန္းစာမွာ ေလ့လာခဲ့ၿပီးျဖစ္တဲ့ Image Color Channel ခြဲထုတ္တာကိုပဲ သံုးထားပါတရ္။ ဒီေကာင္ကေတာ့ က်ေနာ္တို႔ ေလ့လာၾကည့္မရ္႕ Fast Image Porcessing Method ေတြက ေလးခုေျမာက္ Method ျဖစ္ပါတရ္။ ေရွ႕မွာ Method ႏွစ္ခု ေလ့လာခဲ့ၿပီးျဖစ္လို႔ ဒီသင္ခန္းစာက နားလည္ရ လြယ္သြားမရ္လို႔ ထင္ပါတရ္။ ေရွ႕မွာ ေလ့လာခဲ့ၿပီးျဖစ္တဲ့...
  • (၁) Processing using GetPixel and SetPixel
  • (၂) Processing using Bitmap.Lockbits and Marshal.Copy
  • (၃) Processing using Bitmap.Lockbits and Direct Memory Accress in Unsafe context Method
Method သံုးခုထက္စာရင္ Processing Time ပိုျမန္ပါတရ္ဗ်။ က်ေနာ့္ ေလ့က်င့္ခန္းေတြကေတာ့ စတင္ ေလ့လာသူမ်ားအတြက္သာ ရည္ရြယ္ပါတရ္။ ဒါေၾကာင့္ သိေနၿပီးသား သူမ်ားအေနနဲ႔ က်ေနာ္ရဲ႕ တင္ျပပံုမွားယြင္းတာမ်ား ရွိခဲ့ရင္ နားလည္ေပးၾကဖို႔နဲ႔ ျပန္လည္ေထာက္ျပေပးႏိုင္ဖို႔ ေမွ်ာ္လင့္ပါတရ္ဗ်။
ဒီသင္ခန္းစာကေတာ့ ေရွ႕မွာ ေလ့လာခဲ့ၿပီးျဖစ္တဲ့ Lockbits() and Unsafe context Method ကို Paraller.For() Method နဲ႔ တြဲဘက္အသံုးျပဳၿပီး ေလ့လာမွာျဖစ္ပါတရ္။ Unsafe နဲ႔ တြဲသံုးမွာျဖစ္လို႔ အရင္သင္ခန္းစာမွာ ေလ့လာခဲ့ၿပီးျဖစ္တဲ့ Unsafe Configuration ကို လုပ္ဖို႔ေတာ့ လိုပါလိမ့္မရ္ဗ်။ ဒီမွာ Processing Method ကို မေလ့လာမွီ က်ေနာ္အေနနဲ႔ Parallel.For() Method နဲ႔ ပါတ္သတ္ၿပီး အနည္းငရ္ ေဆြးေႏြးေပးစရာေလး ရွိေနပါတရ္။ Parallel.For ဆိုတာကေတာ့ For Loop အစားအသံုးျပဳႏိုင္မရ္႕ Threading Library ေအာက္က Parallel Method တစ္ခုျဖစ္ပါတရ္။ သူ႔ကို အသံုးျပဳမရ္ဆိုရင္ေတာ့ using System.Threading.Tasks; ဆိုတဲ့ Library File ကို ေၾကျငာေပးရပါလိမ့္မရ္။ Parallel.For အတြက္ အသံုးျပဳႏိုင္မရ္႕ Function Overload ပံုစံေတြအမ်ားႀကီးပါ။ ဒီထဲကမွ က်ေနာ္တို႔ ေလ့လာၾကည့္မရ္႕ ပံုစံကေတာ့...
Parallel.For(Int32, Int32, Action)
အခုလိုပံုစံ ျဖစ္ပါတရ္။ ပထမ Parameter ကေတာ့ Loop အတြက္ စမွတ္(First Index)ပါ။ ဒုတိယကေတာ့ ဆံုးမွတ္(Last Index)ျဖစ္ပါတရ္။ ေနာက္ဆံုး parameter ကေတာ့ Parallel.For အတြက္ အေရးအႀကီးဆံုး Processing ေတြလုပ္မရ္႕ Action ျဖစ္ပါတရ္။ Action ကို Lambda Expressions ပံုစံနဲ႔ သံုးပါတရ္။ ဥပမာ က်ေနာ္တို႔ ကိန္းစဥ္တန္းတစ္ခုကို ေပါင္းတာနဲ႔ ေလ့လာၾကည့္ပါ့မရ္။ ေအာက္ကေတာ့ က်ေနာ္တို႔ သမာရိုးက် For Loop အသံုးပါ။ ဒါကို က်ေနာ္တို႔ Parallel.For() ကို အသံုးျပဳၿပီး ေရးမရ္ဆိုရင္ေတာ့... Parallel.For ရဲ႕ Loop Step ကလည္း For မွာ i++ ေပးသလို 1 step ပါပဲ။ ရရွိလာတဲ့ Result တူေနေပမဲ့ Processing Time ကိုၾကည့္မရ္ဆိုရင္ေတာ့ အရမ္းႀကီး ကြာဟေနမွာ ေတြ႔ရပါလိမ့္မရ္။ ဒါက အလြယ္ကူဆံုး ဥပမာေပးတာပါ။ ဒီေလာက္ Processing အတြက္ကိုေတာ့ Parallel ကို သံုးစရာမလိုပါဘူး။ မလိုအပ္ပဲ Parallel သံုးျဖစ္အားျဖစ္ CPU instruction ကိုရႈပ္ေထြးသြားေစႏိုင္ပါတရ္။ သိန္း၊ သန္း မက်ေသာ Items Processing ေတြအတြက္ ရည္ရြယ္တဲ့အတြက္ Big Data Processing ေတြမွာ အရမ္းကို အသံုး၀င္ပါတရ္ဗ်။ ကဲ... ဒီေလာက္ဆိုရင္ေတာ့ Parallel.For() Method ရဲ႕ အလုပ္လုပ္ပံုကို နားလည္သြားၿပီလို႔ ထင္ပါတရ္။
ဒီေန႔ ေလ့က်င့္ခန္းအတြက္ GUI နဲ႔ Tools ေတြရဲ႕ Properties ေတြကိုေတာ့ အရင္အတိုင္းပဲ ျပန္သံုးထားပါတရ္။ cbProcessingMethod မွာ Lockbits(), Unsafe and Parallel.For Method အတြက္ Item တိုးေပးဖို႔နဲ႔ Calling Method က Index No ကို ျပန္ေခၚယံုပါပဲဗ်ာ။ အခုမွ စတင္ေလ့လာမိသူမ်ားအေနနဲ႔ ေရွ႕က သင္ခန္းစာေတြကို ျပန္ဖတ္ေစခ်င္ပါတရ္။ ဒါကေတာ့ က်ေနာ္တို႔ Project မွာ အသံုးျပဳမရ္႕ Library File ေတြပါ။ ကဲ... ေအာက္မွာ Lockbits(), Unsafe and Parallel.For Method ေလးကို ေလ့လာၾကည့္လိုက္ၾကပါအံုးဗ်ာ။ ရွင္းျပရမရ္ဆိုရင္ေတာ့... Coding စစခ်င္းမွာ Unsafe keyword ကို “{....}” နဲ႔ စထားပါတရ္။ Processing လုပ္မရ္႕ Coding ေတြထဲမွာ Unsafe context ေတြကို သံုးပါ့မရ္ဆိုတဲ့ သေဘာပါ။
1. Load Image to Bitmap
Bitmap cloneImg = new Bitmap(inputImg);
ပထမအဆင့္မွာ Image တစ္ခုကို Bitmap ထဲသို႔ ဆြဲတင္တာျဖစ္ပါတရ္။

2. Lock Bitmap in Rectangle by BitmapData
Rectangle ret = new Rectangle(0, 0, cloneImg.Width, cloneImg.Height);
BitmapData bmpData = cloneImg.LockBits(ret, ImageLockMode.ReadWrite, cloneImg.PixelFormat);
ဒီအဆင့္မွာေတာ့ LockBits( ) Method ကိုအသံုးျပဳၿပီး Bitmap Image တစ္ခုကို Lock ခ်တာျဖစ္ပါတရ္။ စစခ်င္း inputImg ရဲ႕ Width and Height အတိုင္း Rectangle တစ္ခုကို တည္ေဆာက္ပါတရ္။ ေနာက္တစ္ဆင့္မွာေတာ့ ၄င္း Rectangle ထဲသုိ႔ Image ကို Lock ခ်ပါတရ္။ ဒီမွာ LockBits( ) Method ကို Parameter(၃)ခုသံုးထားပါတရ္။ ပထမ Parameter က Destination Rectangle ျဖစ္ပါတရ္။ ဒုတိယ parameter ကေတာ့ LockMode ပါ။ ဒီမွာ မိတ္ေဆြတို႔အတြက္ ေရြးခ်ယ္စရာ properties ေတြ အမ်ားႀကီးရွိပါတရ္။ ဥပမာအေနနဲ႔ read အတြက္ bitmap တစ္ခု၊ write အတြက္ bitmap တစ္ခုအေနနဲ႔ ခြဲသံုးႏိုင္ပါတရ္။ ဒီေလ့က်င့္ခန္းမွာ က်ေနာ္ကေတာ့ Bitmap တစ္ခုတည္းကိုပဲ Read, Write လုပ္ၿပီး Return ျပန္ေပးမွာျဖစ္လို႔ LockMode ကို ReadWrite သံုးထားတာပါ။ ေနာက္ဆံုး တတိယ parameter ကေတာ့ pixel format သတ္မွတ္ေပးတာပါ။ တနည္းအားျဖင့္ Image တစ္ခုကို 8bpp, 16bpp, 24bpp, 32pbb,... အစရွိတဲ့ Bits Per Pixel သတ္မွတ္တာပါ။ PixelFormat.Format24bppRgb အခုလို ပုံစံမ်ိဳးနဲ႔လည္း မိမိႏွစ္သတ္ရာ Format ကုိ ေရြးခ်ယ္ၿပီး lock လုပ္ႏိုင္ပါတရ္။ ဘာမွ မေရြးခ်ယ္ပဲ inputImg ရဲ႕ Pixel Format အတိုင္း lock လုပ္ခ်င္တရ္ဆိုရင္ေတာ့ inputImg.PixelFormat ဆိုၿပီး ေပးႏိုင္ပါတရ္။

3. Calculate PixelFormat(BytesPerPixel) and TotleByte
int bytePP = Bitmap.GetPixelFormatSize(cloneImg.PixelFormat) /8;
ဒါကေတာ့ pixel တစ္ခုစီတိုင္းမွာ အသံုးျပဳမရ္ Byte အေရအတြက္ကို တြက္ယူပါတရ္။ 8 နဲ႔စားထားတာကေတာ့ 8bits = 1byte ျဖစ္လို႔ပါ။ ဥပမာ- က်ေနာ္တို႔က 24bppRgb PixelFormat ကိုေရြးမိရင္ 24/8=3 ျဖစ္တဲ့အတြက္ 1 pixel မွာ 3 byte ေနရာယူ အလုပ္ လုပ္ေနမွာပါ။ Blue = 1byte(8bits), Green = 1byte(8bits) and Red = 1byte(8bits) ကိုယ္စီနဲ႔ အလုပ္ လုပ္မွာပါ။ ဒါမွမဟုတ္ 32bppArgb PixelFormat ကုိ ေရြးမိရင္ေတာ့ 32/8=4 ျဖစ္တဲ့အတြက္ pixel တစ္ကြက္မွာ 4 byte ေနရာယူ ေနမွာပါ။ ဒါဆိုရင္ေတာ့ Alpha = 1byte(8bits), Blue = 1byte(8bits), Green = 1byte(8bits) and Red = 1byte(8bits) ကိုယ္စီနဲ႔ အလုပ္ လုပ္သြားမွာျဖစ္ပါတရ္။

4. Store Scaning Data in Pointer Direct Memory
byte* ptrFirstPixel = (byte*)bmpData.Scan0;
ဒီေကာင္ကေတာ့ lock ခ်ထားတဲ့ Image ႀကီးတစ္ခုလံုးကို Scan ဖတ္လိုက္တာပါ။ Scan0 ရဲ႕ သေဘာကေတာ့ Target Image ရဲ႕ left-top corner ကေနၿပီး right-buttom အထိ တစ္ဆင့္ခ်င္းစီ Scan ဖတ္သြားမွာပါ။ ေရွ႕သင္ခန္းစာမွာတုန္းက byte[]array ကိုအသံုးျပဳၿပီး 2-Dimension ကို 1-Dimension ေျပာင္းယူပါတရ္။ ဒီသင္ခန္းစာမွာေတာ့ byte* Pointer ကို အသံုးျပဳၿပီးေတာ့ Memory Address ထဲကို တိုက္ရိုက္သြားသိမ္းပါတရ္ဗ်။ ေနာက္တစ္ဆင့္မွာေတာ့ မိမိလိုခ်င္တဲ့ Memory Address ကုိ တိုက္ရိုက္ညႊန္းၿပီး Processing လုပ္ယူမွာ ျဖစ္ပါတရ္ဗ်ာ။

5. Pre-Processing Get Pixel Processing လုပ္သြားတဲ့ ပံုစံကေတာ့ Stride by Stride အလုပ္ လုပ္သြားတာပါ။ ပထမ loop က Stride (or) Row ကို မွတ္သားခ်င္တဲ့အတြက္ပါ။ ဒီမွာ Parallel.For() Loop ကို အသံုးျပဳထားပါတရ္။ အလုပ္လုပ္သြားတဲ့ ပံုစံကိုေတာ့ အေပၚမွာ ဥပမာနဲ႔ ရွင္းျပခဲ့ၿပီးျဖစ္လို႔ နားလည္ၾကလိမ့္မရ္လို႔ ထင္ပါတရ္။ ဒီမွာ ptrFirstPixel + (y * bmpData.Stride)လုပ္ၿပီး row ဖတ္ထားတာကေတာ့ Stride တစ္ေၾကာင္း Processing လုပ္ၿပီးတိုင္း ေနာက္တစ္ၾကာင္းကို ကူးႏိုင္ရန္ Memory Address ေနရာကို ညႊန္းယူတာပါ။ Pointer နဲ႔ Memory Address ေတြျဖစ္လို႔ Trace လိုက္ၿပီး ရွင္းဖိ႔ုမလြယ္ပါဘူး။ ပံုေသနည္းလို႔ မွန္ထားရင္လည္း အဆင္ေျပပါတရ္။ အၾကမ္းဖ်ဥ္း ေျပာရရင္ေတာ့ Stride တစ္ေၾကာင္းလံုးမွာရွိတဲ့ Pixel and Color Value ေတြကို Address တစ္ခုစီမွာ သိမ္းပါတရ္။ ဥပမာ- Stride 5 ေၾကာင္းရွိရင္ Address ငါးခုရွိတရ္ဆိုတဲ့ သေဘာပါ။ Stride ဆိုတာကေတာ့ row တစ္ row စီမွာရွိတဲ့ byte အေရအတြက္ပါ။ Stride = bytesPerPixel * inputImg.Height; လို႔ ေရွ႕သင္ခန္းစာမွာ ရွင္းျပခဲ့ၿပီးပါၿပီ။ ဒုတိယ loop ကေတာ့ Stride ရဲ႕ byte Index ေတြထဲမွာရွိတဲ့ BGRA တန္ဖိုးေတြကို ဆြဲထုတ္ႏိုင္ေအာင္ပါ။ currentAddress ဆိုတာကေတာ့ Memory Address ပါ။ ဒါေၾကာင့္ loop တခါပါတ္တိုင္း Memory Address ထဲကို တိုက္ရိုက္သြားဖတ္ၿပီး BGRA တန္ဖိုးေတြကို ဆြဲထုတ္ယူႏိုင္တာပဲ ျဖစ္ပါတရ္။ x = x + bytePP ေပးထားတာကေတာ့ BGRA ေလးလံုး တစ္ Group ျဖစ္တဲ့အတြက္ Group ခုန္သြားႏိုင္ေအာင္ပါ။ currentAddress[x]; currentAddress[x + 1]; currentAddress[x + 2]; ဒီေကာင္ေတြကေတာ့ Group တစ္ခါခုန္တိုင္း BGRA တန္ဖိုးေတြကို asign လုပ္ႏိုင္ေအာင္ပါ။ ဒီေလ့က်င့္ခန္းမွာေတာ့ Alpha တန္ဖိုးကို က်ေနာ္ ခ်န္လွန္ထားခဲ့ပါတရ္။ စားေရးၿပီး ရွင္းျပရ တာေတာ့ အနည္းငယ္ ခဲယဥ္းပါတရ္ဗ်ာ။ မိတ္ေဆြတို႔ Trace လုိက္ၾကည့္ရင္ေတာ့ Coding Flow ရဲ႕ အလုပ္ လုပ္သြားပံုကို ေသခ်ာနားလည္သြားမွာပါ။

6. Processing Color Channel ဒီေကာင္ကေတာ့ Color Channel အတြက္ Processing လုပ္ထားတာပါ။ ရိုးရိုးရွင္းရွင္းပါပဲ။ မိမိ ေျပာင္းလဲ ခ်င္ေသာ Color Channel ကိုခ်န္ၿပီး က်န္ Channel ေတြကို သုည(0) assign လုပ္လိုက္တာပါ။ နားလည္မရ္လို႔ ေမွ်ာ္လင့္ပါတရ္ဗ်။

7. Unlock bitmap
cloneImg.UnlockBits(bmpData);
ဒါကေတာ့ Processing လုပ္ငန္းစဥ္ အားလံုးၿပီးသြားၿပီျဖစ္တဲ့အတြက္ Lock ခ်ထားတဲ့ Image ကို unLock ျပန္လုပ္တာပါ။

8. Return Bitmap Image
return cloneImg;
ေနာက္ဆံုးအဆင့္ကေတာ့ Processing လုပ္ၿပီးသား Image တစ္ခုကို Method သို႔ ျပန္လည္အသံုးျပဳ/ေဖာ္ျပႏိုင္ရန္ Return ျပန္ေပးထားတာ ျဖစ္ပါတရ္။ Coding အလုပ္ လုပ္သြားပံုကိုေတာ့ က်ေနာ္ တတ္ႏိုင္သမွ် အေသးစိတ္ရွင္းထားပါတရ္။ က်ေနာ္ တင္ျပပံု မွားယြင္းခဲ့တာ ရွိခဲ့ရင္လည္း နားလည္ေပးၾကဖို႔ ေတာင္းပန္ပါရေစဗ်ာ။
အခု ဆက္လက္ၿပီး Calling Method ကို ေလ့လာၾကည့္ၾကပါ့မရ္။ btnProcessing Button ကို D_Click ေပးၿပီး ေအာက္ပါ Coding ကို ေရးယူလိုက္ပါဗ်ာ။ Coding မွာ ခက္ခဲတာ ဘာမွမပါ ပါဘူးဗ်ာ။ Stopwatch သံုးထားတာကေတာ့ Processing Time ကို မွတ္ထားေစခ်င္လို႔ပါ။ cbProcessingMethod မွ ေရြးခ်ယ္လိုက္ေသာ Index အေပၚမူတည္ၿပီး Processing Method ေတြကို ေခၚယူမွာ ျဖစ္ပါတရ္။ ဒီေလာက္ဆိုရင္ေတာ့ မိတ္ေဆြ၊ ညီအစ္ကို၊ ေမာင္ႏွမတို႔အေနနဲ႔ Fast Image Processing အတြက္ Lockbits(), Unsafe context and Parallel.For Method ကို အသံုးျပဳၿပီး Processing လုပ္တတ္ၿပီလို႔ ထင္ပါတရ္ဗ်ာ။ မိတ္ေဆြ၊ ညီအစ္ကို၊ ေမာင္ႏွမအားလံုး ေလ့လာျခင္းျဖင့္ ေက်နပ္ႏိုင္ၾကပါေစ။

0 Responses so far.

Post a Comment