امروزه پردازش تصاویر دیجیتال و روشهای تسریع و هوشمند کردن این پروسه نسبت به گذشته اهمیت بسیار زیادی یافته است . دلیل این امر هم فراگیر شدن سیستمهای سخت افزاری مبتنی برتولید تصاویر دیجیتال است که از دسته می توان انواع دوربینهای عکاسی و فیلم برداری دیجیتال وموبایلها و اسکنرها و... نام برد . مبحث پردازش تصویر بسیار گسترده است و گنجایش آن در این صفحات غیر ممکن است اما تا آنجای که امکان دارد سعی شده است مفاهیم کلی بیان شود . در شماره گذشته مباحث بنیادی مربوط به این بحث به همت یکی از دانشجویان گرد آوری شد . در این شماره سعی کردیم تا بر مطالب گذشته جامع عمل بپوشانیم . در واقع از الگوریتمها و شبه کدها در محیط ویژوال استودیو استفاده می کنیم و در عمل این موضوع را بررسی می کنیم . مطمئنا هم لذت بخش تر است و هم قابل فهم تر . در ابتدا به عنوان یاد آوری ساختار یک فایل تصویر Bitmap  را بررسی می کنیم .عکس هایی با این پسوند معمولا 8 بیتی 16 بیتی و یا 24 بیتی هستند .

پایه ی رنگ ها 3 رنگ آبی(B=Blue) سبز(G=Green) قرمز(R=Red) هستند که ما فعلا فقط با این سه رنگ سروکار داریم . در هر عکسی هم که به صورت دیجیتال تهیه می شود درون هر پیکسل آن این سه رنگ رنگنهایی پیکسل را مشخص می کنند .

اگر عکس شما 24 بیتی باشد به این معنی است که هر پیکسل این عکس 24 بیت فضا در حافظه اشغال می کند . هر پیکسل ما از سه رنگ تشکیل شده است پس ا ین 24 بیت را باید بین 3 رنگ مختلف تقسیم کرد . یعنی برای هررنگ 8 بیت  در نظر گرفته شده است . یک عدد 8 بیتی 256 حالت مختلف از یک رنگ را ایجاد می کند . به عنوان مثال رنگ آبی را از سفید به سمت آبی کمرنگ و در ادامه آبی پر رنگ و در نهایت به سیاه می رساند . و این موضوع برای دو طیف رنگ دیگر هم صادق است . در نهایت مجموع این سه رنگ 8 بیتی ، رنگ یک پیکسل را تشکیل می دهد .البته حجم یک فایل Bitmap را می توان از همین طریق محاسبه کرد . کافی است که این مقدار 24 بیت را در تعداد پیکسل ها (ارتفاع عکس × عرض عکس) ضرب کرد. عدد نهایی دقیقا برابر حجم عکس است . تمام عملیات پردازش روی همین 3 رنگ و با تغییر مقادیر آنها انجام می شود . حال زمان آن است که این موضوع را به صورت عملی مورد بررسی قرار دهیم . از آنجایی که عملیات فراخوانی و ذخیره سازی فایل های تصویری با فرمتهای مختلف کمی پیچیده است ما کار را از روی یک برنامه ی از قبل تهیه شده توسط مایکروسافت ادامه می دهیم .این برنامه شامل چند منو و چند ابزار از پیش تهیه شده است که ما این بخش جدید را به یکی از منو های آن اضافه می کنم .از طریق لینک زیر می توانید سورس برنامه را دریافت کنید . البته باید ویژوال استودیو 2005 را روی سیستم خود نصب کرده باشید تا بتوانید سورس کد را ادیت کنید .

http://download.microsoft.com/download/4/0/9/40946FEC-EE5C-48C2-8750-B0F8DA1C99A8/MFC/general/SimpleImage.zip.exe

 

فایل CImage.vcproj را اجرا کنید . برای شروع کار ابتدا برنامه را اجرا کنید (با کلید F5 می توانید این کار را انجام هید) . پس از اجرا وارد منوی File شوید و گزینه ی Open راانتخاب کنید یک فایل عکس باز کنید و از منوی Tools گزینه ی Convert to B&W را انتخاب کنید .تغییرات روی عکس اعمال می شود .

برنامه را ببندید و در محیط ویژوال درون پنجره ی Solution Explorer روی فایل ChildView.cpp دوبار کلیک کنید .ChildView  یک کلاس است که اصل آن در فایل Childview.h که در برنامه کمی  بالاتر قرار دارد مشاهده می شود . در واقع توابع در ChildView.h  اعلان می شوند و  در ChildView.cpp تعریف می شوند . اگر به محتوای فایل ChildView.h توجه کنید متوجه می شوید که تمام توابع مربوط به باز کردن ، تغییرات سایز و رنگ و ذخیره کردن یک فایل عکس در آن اعلان شده است و البته متغییرهایی هم در آن اعلان شده است که در زمان استفاده از آن به شرح آن خواهم پرداخت . برای مشاهده ی محیط برنامه و دیدن منوها روی فایل Cimage.rc دوبار کلیک کنید و در این قسمت از پوشه ی Menu روی تنها گزینه ی آن IDR_MAINFRAME دوبار کلیک کنید . در وسط مانیتور می توانید شاهد منو ها باشید . اگر دقت کنید منوی Tools شامل یک ابزار به نا م Convert to B&W است که شما به کمک این ابزار می توانید عکس باز شده توسط برنامه ی خود را به عکس سیاه و سفید تبدیل کنید .در واقع این گزینه از منو تابعی را فراخوانی می کند که باعث ایجاد تغییرات در یک فایل تصویر می شود. حالا به مهمترین بخش برنامه می رسیم . تابعی که این کار را انجام می دهد در فایل ChildView.cpp قرار دارد و ما برای پردازش تصویر و برای درک بهتر این موضوع به این تابع رجوع می کنیم . در پنجره ی SolutionExplorer در سمت چپ برنامه روی فایل ChildView.cpp دوبار کلیک کنید . تابعی با نام OnToolsMakeBW در این فایل وجود دارد که با کمی جستجو می توانید آنرا بیابید . با بررسی محتوای تابع به ادامه ی کار می پردازیم .ما در این تابع شاهد دستوراتی هستیم که مفاهیم آنرا قبلا بررسی کردیم اما کد نوشته شده در این تابع نیازمند توضیحاتی است تا کاملا درک شود . این تابع به طور پیش فرض در دو روش به تجزیه ی عکس می پردازد . در روش اول اطلاعات هر پیکسل یک عکس گرفته می شود و پس از تغییرات روی این اطلاعات ، دوباره روی همین پیکسل ذخیره می شود اما در روش دوم جدول رنگی از هر پیکسل یک عکس گرفته می شود و پس از ایجاد تغییرات روی هر پیکسل این جدول رنگ که در حافظه قرار دارد ، این تغییرات به تصویر مورد نظر اعمال می شود و این روش سریع تر است . اما چون درک روش اول آسان تر است برای ما بقیه ی توابع را به روش اول پیاده سازی می کنیم .البته روش دوم که در تابع بیان شده باید توسط  تصویر مورد نظر پشتیبانی شود .البته  توضیحات را بر مبنای اینکه می خواهیم در برنامه یک فایل Bitmap  باز کنیم ، ارائه می دهم .به کد موجود در تابع زیر دقت کنید .

 

 

 

 1: COLORREF pixel;   متغییری مخصوص دریافت اطلاعات رنگی یک پیکسل   //     

 

 2: int maxY = imgOriginal.GetHeight(),maxX = imgOriginal.GetWidth();

 

 3:   byte r,g,b,avg; //      تعیین متغیر هایی از نوع بایت (8 بیت)

 

 4:         for (int x=0; x

 

 5:               for (int y=0; y

 6:                     pixel = imgOriginal.GetPixel(x,y);

 7:                     r = GetRValue(pixel);

 8:                     g = GetGValue(pixel);

 9:                     b = GetBValue(pixel);

 

10:                     avg = ((r + g + b)/3);

 

11:                     imgOriginal.SetPixelRGB(x,y,avg,avg,avg);

 

12:               }

13:         Invalidate();

14:         UpdateWindow();

15:         }

 

 

در خط 2 برنامه دو متغیر از نوع  int تعریف شده است که یکی از آنها ارتفاع (maxY)  تصویر و دیگری عرض تصویر (maxX) است . در مورد imgOriginal که در این خط آمده است باید توضیح داد که این یک شیئ از نوع کلاس Cimage است که خود این کلاس حاوی توابعی برای کار با تصویر است . می توانید با کلیک راست کردن برروی همین کلمه و انتخاب گزینه ی go to Definition  محل تعریف این متغیر را شناسا یی کنید . در واقع شما می توانید تصور کنید که شیئ imgOriginal  در این تابع دقیقا خود تصویری است که شما بعدا آنرا باز می کنید. ما با استفاده از توابع GetHeight() , GetWidth() که توابع عضو کلاس Cimage  هستند مقدار طول و عرض را به متغیر های maxX و maxY می دهیم .

هر پیکسلی که ما در برنامه داریم از 3 رنگ اصلی تشکیل شده . برای اینکه ما تصویر load  شده را از حالت رنگی به سیاه و سفید تبدیل کنیم ، باید  این  3  رنگ اصلی ( r , g , b  ) را تغییر بدهیم . برای اینکه تغییر حالت تصویر از رنگی به سیاه سفید را بر روی عکس مورد نظر اعمال کنیم ، مقدار عددی 3 متغییر r , g ,b را با هم جمع کرده و میانگین می گیریم و مقدار تولید شده را به متغییر avg که این هم از نوع byte  است می دهیم .

برای این منظور باید تمام پیکسل ها را پیمایش کنیم این کار را با دو حلقه for() که یکی شمارنده عرض و دیگری شمارنده طول تصویر است انجام می دهیم .

در بلوک for دوم ما یکسری دستورات داریم که هر سری تکرار می شود ، با کمک این دستورات ما محتوای یک  پیکسل را از رنگی به سیاه و سفید تغییر می دهیم .

خط 6 . تابع GetPixel()  ، دو آرگومان دارد که  با کمک آنها مختصات پیکسل مورد نظر را معین می کند . و این تابع محتوای ان پیکسل که در واقع همان خصوصیات رنگی آن پیکسل می باشد را به متغیر pixel  می دهد .

متغیر pixel  که از نوع COLORREF تعریف شده در واقع یک عدد هگزادسیمال 10 رقمی به شکل 0x00rrggbb است . که همان طور که مشاهده می کنید دو رقم اول سمت راست مقدار رنگ آبی یک پیکسل و دو رقم بعدی مقدار رنگ سبز و دو رقم بعدی مقدار رنگ قرمز را مشخص می کند . برای مثال عدد 0x0022a4ff در واقع رنگ بخش آبی آن را عدد ff  مشخص می کند .البته  ff هم در مبنای 2 یک عدد 8 بیتی است به همین دلیل ما هر رنگ را یک بایت در نظر می گیریم و مقدار آن برابر 255 در مبنای 10 است که بیشترین مقدار یک عدد 8 بیتی است یعنی کم رنگ ترین حالت رنگ آبی . برای اینکه ما یک پیکسل را از حالت رنگی خارج کنیم باید اختلاف این سه رنگ را به صفر برسانیم  مثلا اگر میانگین مقادیر 22 و a4 و ff شد 93 (هگزا دسیمال) پس باید مقادیر r و g و b این پیکسل را به 93 تغییر دهیم . البته با انجام این کار رنگ این پیکسل به یک رنگ بین سیاه و سفید تغییر می کند . که با تکرار این عمل برای همه ی پیکسلها در نهایت تمام تصویر مورد نظر به یک عکس سیاه و سفید تبدیل می شود .

خط 7 و8 و9.  گرفتن مقادیر r و g و b از متغیر پیکسل (0x00rrggbb )  به کمک 3 تابع GetRVlaue() , GetGValue() , GetBValue()انجام میشود. مقدار 3 رنگ اصلی آن پیکسل را می گیریم و به 3 متغییر r , g , b  انتقال می دهیم .

درخط 10 میانگین سه رنگ محاسبه می شود و در متغیر avg قرار می گیرد .

در خط 11 ما با کمک تابع SetPixelRGB() که  5 ارگومان دارد به هدف اصلی خودمان که همان تغییر حالت پیکسل از رنگی به سیاه سفید است می رسیم . دو آرگومان اول این تابع  مختصات همان پیکسلی است که در ابتدای حلقه آنرا گرفتیم و 3 ارگومان باقیمانده مقادیر جدید r , g, b است که به این تابع می دهیم و از انجایی که این مقادیر باید مساوی هم باشند تا پیکسل مورد نظر، رنگی بین سیاه و سفید باشد همان مقدار avg را به جای این سه آرگومان قرار می دهیم .

توابعی هم که در خط 13 و 14 از آنها استفاده شده به منظور رفرش کردن صفحه و اعمال تغییرات روی عکس مورد نظر است .البته شما می توانید با جابجایی این 2 تابع و قرار دادن آنها در حلقه ی For() مراحل تغییر رنگ هر پیکسل یک عکس را مشاهده کنید .چون دقیقا پس از اعمال تغییرات روی یک پیکسل عکس شما Update می شود و این مراحل قابل مشاهده می شود .

حالا که خوب از جزئیات کار با پیکسل ها آگاه شدید می توانید از الگوریتم هایی که در شماره ی قبل ارائه شد برای نوشتن ابزار جدید استفاده کنید . البته در این شماره یکی از آنها را پیاده سازی می کنیم اما به علت محدودیت صفحات ادامه ی کار را در شماره ی بعد تشریح می کنیم .

 

تشخیص لبه

 

برای پیاده سازی این ابزار ما نیاز مندیم تا در منوی Tools  گزینه ی جدیدی با نام Edge Detection ایجاد کنیم تا پس از اجرای برنامه و با کلیک بر روی این ابزار لبه های تصویر مشخص شود . الگوریتم آن در شماره قبل آورده شده بود اما برای پیش گیری از هر گونه احتمالی آنرا در اینجا آوردم .

 

Algorithm : Edge Detection

For every pixel ( i , j ) on the source bitmap

Extract the (R,G ,B) components of this pixel, its right neighbour (R1,G1,B1), and its bottom neighboor (R2,G2,B2)

Compute D(C,C1) and D(C,C2) using (R1)

If D(C,C1) OR D(C,C2) superior to a parameter K, then we have an edge pixel

 

عملکرد این الگوریتم به این صورت است که هر پیکسل را نسبت به پیکسل سمت راست و پایین خود مقایسه می کند و اگر اختلاف رنگ آن نسبت به این دو پیکسل بیشتر از یک مقدار ثابت(که ما تعیین می کنیم) بود آنگاه آن نقطه یک نقطه ی لبه است .

ابتدا در پنجره ی Soltion Explorer  روی گزینه ی Cimage.rc دابل کلیک کنید و در پوشه ی منو روی گزینه ی IDR_Menuframe دوبار کلیک کنید و در سمت راست گزینه ی Tools را انتخاب کنید و در زیر منوی آن روی گزینه ی آخر کلیک کنید و بدون هیچ حرکت اضافه ای تایپ کنید Edge Detection .

حالا باید برای این گزینه یک تابع ایجاد کنیم که عمل تشخیص لبه را انجام دهد . برای این منظور روی همین گزینه ای که ایجاد کردید راست کلیک کنید و گزینه ی AddEventHandler  را انتخاب کنید . یک پنجره ی جدید باز می شود که در این پنجره در قسمت Function handler name باید نام تابعی که می خواهید ایجاد شود را وارد کنید(OnMenuEdge ) و در قسمت سمت راست در بخش Class list نام فایلی را که این تابع باید یکی از اعضای آن باشد انتخاب کنید چون کد تبدیل به سیاه و سفید در فایل ChildView.cpp قرار دارد ما این تابع را هم در این فایل قرار میدهیم پس در این بخش هم گزینه ی ChildView را انتخاب کنید د قسمت Messag Type  هم گزینه ی Command را انتخاب کنید . حال دکمه ی Add&Edit  را بزنید تا تابع مورد نظر در فایل Childview.cpp ایجاد شود . در این مرحله ما باید الگوریتم تشخیص لبه را در این تابع پیاده سازی کنیم .

 

void CChildView::OnMenuedge()

{

      COLORREF pixel0;

      COLORREF pixel1;

      COLORREF pixel2;

      int maxY = imgOriginal.GetHeight(),maxX =imgOriginal.GetWidth();

            int r0,r1,r2,g0,g1,g2,b0,b1,b2;

            for (int x=0; x

                  for (int y=0; y

                        pixel0 = imgOriginal.GetPixel(x,y);

                        r0 = GetRValue(pixel0);

                        g0 = GetGValue(pixel0);

                        b0 = GetBValue(pixel0);

 

                        pixel1 = imgOriginal.GetPixel(x+1,y);

                        r1 = GetRValue(pixel1);

                        g1 = GetGValue(pixel1);

                        b1 = GetBValue(pixel1);

                       

                        pixel2 = imgOriginal.GetPixel(x,y+1);

                        r2 = GetRValue(pixel2);

                        g2 = GetGValue(pixel2);

                        b2 = GetBValue(pixel2);

                       

                       if((sqrt((double)((r0-r1)*(r0-r1)+

                          (g0-g1)*(g0-g1)+(b0-b1)*(b0-b1)))>40)||

                          (sqrt((double)((r0-r2)*(r0-r2)+

                          (g0-g2)*(g0-g2)+(b0-b2)*(b0-b2)))>40))

                      

                         imgOriginal.SetPixelRGB(x,y,255,255,255);

                       else

                         imgOriginal.SetPixelRGB(x,y,0,0,0);

                  }

            }

      Invalidate();

      UpdateWindow();  

}

 

 

این دستورات را در  تابع OnMenuEdge بنویسید . برنامه را اجرا کنید ( با کلید میانبر F5 ) . حالا از منوی فایل گزینه ی Open را کلیک کنید و یک فایل تصویر باز کنید . از منوی Tools  ابزار Edge Detection را انتخاب کنید پس از سپری شدن چند ثانیه مشاهده می کنید که لبه های تصویر تشخیص داده شده است .البته من دراین کد مقدار حساسیت را عدد 40 قرار دادم اما شما می توانید آنرا تغییر دهید و نتیجه را مشاهده کنید .هر چه این مقدار بیشتر باشد اختلاف رنگ پیکسلها باید خیلی زیاد باشد تا برنامه آن نقطه را یک لبه تشخیص دهد .فرمول محاسباتی این این ابزار را در شماره قبل بیان کردیم و توضیح دادیم اما شما با توجه به کد می توانید در یابید که این ابزار از چه الگوریتمی پیروی می کند . در روند پیمایش پیکسلها یک پیکسل از راست و یک پیکسل از پایین پیکسل مورد نظر گرفته می شود و اطلاعات رنگ آنها با این پیکسل مقایسه می شود اگر اختلاف بیش از یک عدد ثابت (که در بالا 40بود) باشد این پیکسل یک لبه محسوب می شود .

در شماره ی بعد به ایجاد ابزار های بیشتری می پردازیم .