بایگانی برچسب برای: بهسان اندیش
استفاده از متد WhenAll برای اجرای چندین Task به صورت همزمان در سی شارپ
فرض کنید که داخل یک متد باید چندین متد را به صورت await فراخوانی کنید. به صورت عادی زمانی که متدها فراخوانی می شوند هر بخش await بعد از تکمیل await قبلی اجرا خواهد شد و مقادیر بازگشتی به صورت یکجا در اختیار شما قرار نمیگیرند. برای مثال، کد زیر را در نظر بگیرید:
private async void AsyncBtn_Click(object sender, EventArgs e) { Result1TextBox.Text = (await Task1()).ToString(); Result12extBox.Text = (await Task2()).ToString(); } private Task < long > Task1() { return Task.Run<long>(() = > { var num = Enumerable.Repeat(10, 1000); long sum = 0; foreach (var item in num) { System.Threading.Thread.Sleep(2); sum += item; } return sum; }); } private Task < long > Task2() { return Task.Run<long>(() = > { var num = Enumerable.Repeat(10, 1000); long sum = 0; foreach (var item in num) { System.Threading.Thread.Sleep(2); sum += item; } return sum; }); }
در کد بالا، ابتدا عملیات Task1 انجام شده و نتیجه نمایش داده می شود و پس از آن Task2 اجرا شده و نتیجه نمایش داده می شود. برای رفع وقفه بین اجرای دو Task از متد WhenAll استفاده می کنیم. برای استفاده از متد WhenAll کد BtnAsync_Click را به صورت زیر تغییر می دهیم:
private async void AsyncBtn_Click(object sender, EventArgs e) { var results = await Task.WhenAll(Task1(), Task2()); txtBox.Text = results[0].ToString(); txtSecond.Text = results[1].ToString(); }
با ایجاد تغییر کد بالا، خروجی متد WhenAll یک آرایه از نوع long خواهد بود که هر یک از اندیس های آرایه به ترتیب خروجی متدهای اول و دوم می باشد و به صورت بالا می توان خروجی ها را در TextBox ها نمایش داد.
منبع
قسمت اول آموزش-برنامه نویسی Asynchronous – آشنایی با Process ها، Thread ها و AppDomain ها
قسمت دوم آموزش- آشنایی با ماهیت Asynchronous در Delegate ها
قسمت سوم آموزش-آشنایی با فضای نام System.Threading و کلاس Thread
قسمت چهارم آموزش- آشنایی با Thread های Foreground و Background در دات نت
قسمت پنجم آموزش- آشنایی با مشکل Concurrency در برنامه های Multi-Threaded و راهکار های رفع این مشکل
قسمت ششم آموزش- آشنایی با کلاس Timer در زبان سی شارپ
قسمت هفتم آموزش-آشنایی با CLR ThreadPool در دات نت
قسمت هشتم آموزش- مقدمه ای بر Task Parallel Library و کلاس Parallel در دات نت
قسمت نهم آموزش- برنامه نویسی Parallel:آشنایی با کلاس Task در سی شارپ
قسمت دهم آموزش-برنامه نویسی Parallel در سی شارپ :: متوقف کردن Task ها در سی شارپ – کلاس CancellationToken
قسمت یازدهم آموزش- برنامه نویسی Parallel در سی شارپ :: کوئری های Parallel در LINQ
قسمت دوازدهم آموزش- آشنایی با کلمات کلیدی async و await در زبان سی شارپ
قسمت سیزدهم آموزش- استفاده از متد WhenAll برای اجرای چندین Task به صورت همزمان در سی شارپ
برنامه نویسی Parallel در سی شارپ :: مقدمه ای بر Task Parallel Library و کلاس Parallel در دات نت
پیش از این ما در سری مطالب مرتبط با بحث کار با Thread با نحوه ایجاد و مدیریت Thread ها در دات نت آشنا شدیم. از نسخه 4 دات نت قابلیتی اضافه شد با نام Task Parallel Programming یا TPL که روش جدیدی برای نوشتن برنامه Multi-Theaded است. این قابلیت بوسیله یکسری از کلاس ها که در فضای نام System.Threading.Tasks قرار دارد فراهم شده و به ما این اجازه را می دهد که بدون درگیر شدن مستقیم با Thread ها و Thread Pool ها برنامه های Multi-Threaded بنوسیم.
دقت کنید که زمان استفاده از قابلیت TPL دیگر نیازی به استفاده از کلاس های فضای نام System.Threading نمی باشد و به صورت پشت زمینه عملیات ساخت و مدیریت Thread ها برای ما انجام می شود. با این کار شیوه کار با Threadها بسیار ساده شده و یکسری از پیچیدگی ها در این بین حذف می شود.
فضای نام System.Threading.Tasks
همانطور که گفتیم TPL در حقیقت مجموعه ای از کلاس ها است که در فضای نام System.Threading.Tasks قرار گرفته. یکی از قابلیت های TPL این است که کارهای محوله را به صورت خودکار بین CPU های سیستم (در صورت وجود) توزیع می کند که این کار در پشت زمینه بوسیله CLR Thread Pool انجام می شود.
کارهای انجام شده توسط TPL در پشت زمینه عبارتند از تقسیم بندی وظایف، زمانبندی Thread ها، مدیریت وضعیت (State Management) و یکسری از کارهای اصطلاحاً Low-Level دیگر. نتیجه این کار برای شما بالا رفتن کارآیی برنامه ها بوده بدون اینکه درگیر پیچیدگی های کار با Thread ها شوید. همانطور که گفتیم فضای نام System.Threading.Tasks شامل یکسری کلاس ها مانند کلاس Parallel، کلاس Task و … می باشد که در ادامه با این کلاس ها بیشتر آشنا می شویم.
نقش کلاس Parallel
یکی از کلاس های TPL که نقش کلیدی را در نوشتن کدهای Parallel ایفا می کند، کلاس Parallel است، این کلاس یکسری متدها در اختیار ما قرار می دهد که بتوانیم بر روی آیتم های یک مجموعه (علی الخصوص مجموعه هایی که اینترفیس IEnumerable را پیاده سازی کرده اند) به صورت parallel عملیات هایی را انجام دهیم.
متدهای این کلاس عبارتند از متد های For و ForEach که البته Overload های متفاوتی برای این متدها وجود دارد. بوسیله این متدها می توان کدهایی نوشتن که عملیات مورد نظر را به صورت parallel بر روی آیتم های یک مجموعه انجام دهند. دقت کنید کدهایی که برای این متدها نوشته می شوند در حقیقت همان کدهایی هستند که معمولاً در حلقه های for و foreach استفاده می شوند، با این تفاوت که به صورت parallel اجرا شده و اجرا و مدیریت کدها بوسیله thread ها و CLR Thread Pool انجام شده و البته بحث همزمانی نیز به صورت خودکار مدیریت می شود.
کار با متد ForEach
در ابتدا به سراغ متد ForEach می رویم، این متد یک مجموعه که ایترفیس IEnumerable را پیاده سازی کرده به عنوان پارامتر اول و متدی که باید بر روی هر یک اعضای این مجموعه انجام شود را به عنوان پارامتر دوم قبول می کند:
var numbers = new List < int > {2, 6, 8, 1, 3, 9, 6, 10, 5, 4}; Parallel.For(3, 6, index = > { Console.WriteLine(numbers[index]); Console.WriteLine("Thread Id: {0}", System.Threading.Thread.CurrentThread.ManagedThreadId); });
در کد بالا یک آرایه از لیست از نوع int تعریف کرده و در مرحله بعد بوسیله متد ForEach در کلاس Parallel اعضای لیست را پردازش می کنیم، با هر بار اجرا خروجی های متفاوتی دریافت خواهیم کرد:
8 5 Thread Id: 6 4 2 Thread Id: 1 6 3 Thread Id: 5 9 Thread Id: 5 10 Thread Id: 5 1 Thread Id: 5 Thread Id: 4 Thread Id: 6 6 Thread Id: 1 Thread Id: 3
همانطور که مشاهده می کنید شناسه های مربوط به thread در هر بار اجرای کدی مشخص شده در متد ForEach با یکدگیر متفاوت است، دلیل این موضوع ایجاد و مدیریت Thread ها توسط CLR Thread Pool است که ممکن است با هر بار فراخوانی متد مشخص شده به عنوان پارامتر دوم یک thread جدید ایجاد شده یا عملیات در یک thread موجود انجام شود.
کار با متد For
اما علاوه بر متد ForEach متد For نیست را می توان برای پردازش یک مجموعه استفاده کرد. در ساده ترین حالت این متد یک عدد به عنوان اندیس شروع حلقه، عدد دوم به عنوان اندیس پایان حلقه و یک پارامتر که متدی با پارامتر ورودی از نوع int یا long که نشان دهنده اندیس جاری است قبول می کند، برای مثال در متد زیر بوسیله متد For لیست numbers را در خروجی چاپ می کنیم، اما نه همه خانه های آن را پس عبارت اند از:
var numbers = new List < int > {2, 6, 8, 1, 3, 9, 6, 10, 5, 4}; Parallel.For(3, 6, index = > { Console.WriteLine(numbers[index]); Console.WriteLine("Thread Id: {0}", System.Threading.Thread.CurrentThread.ManagedThreadId); });
با اجرای کد بالا خروجی زیر نمایش داده می شود، البته با هر بار اجرا ممکن است خروجی ها با هم متفاوت باشند:
1 Thread Id: 1 9 3 Thread Id: 3 Thread Id: 4
یکی از کاربردی ترین موارد برای استفاده از کلاس Parallel و متدهای For و ForEach زمانی است که قصد داریم مجموعه حجیمی از اطلاعات را پردازش کنیم و البته پردازش هر المان وابسته به سایر المان ها نیست، زیرا عملیات پردازش المان ها به دلیل اینکه در Thread های مختلف انجام می شوند، ترتیبی در زمان اجرای المان ها در نظر گرفته نشده و ممکن است آیتمی در وسط لیست قبل از آیتم ابتدای لیست پردازش شود.
برای مثال، فرض کنید قصد دارید لیستی از تصاویر را گرفته و بر روی آن ها پردازشی انجام دهید یا لیستی از فایل ها را می خواهیم پردازش کنید، در اینجور مواقع به راحتی می توان از کلاس Parallel و متدهای آن استفاده کرد. یکی از مزیت های استفاده از کلاس Task این است که علاوه بر توزیع انجام کارها در میان Thread ها، در صورت موجود بودن بیش از یک CPU در سیستم شما، از سایر CPU ها هم برای پردازش اطلاعات استفاده می کند. در قسمت بعدی در مورد کلاس Task صحبت خواهیم کرد.
منبع
قسمت اول آموزش-برنامه نویسی Asynchronous – آشنایی با Process ها، Thread ها و AppDomain ها
قسمت دوم آموزش- آشنایی با ماهیت Asynchronous در Delegate ها
قسمت سوم آموزش-آشنایی با فضای نام System.Threading و کلاس Thread
قسمت چهارم آموزش- آشنایی با Thread های Foreground و Background در دات نت
قسمت پنجم آموزش- آشنایی با مشکل Concurrency در برنامه های Multi-Threaded و راهکار های رفع این مشکل
قسمت ششم آموزش- آشنایی با کلاس Timer در زبان سی شارپ
قسمت هفتم آموزش-آشنایی با CLR ThreadPool در دات نت
قسمت هشتم آموزش- مقدمه ای بر Task Parallel Library و کلاس Parallel در دات نت
قسمت نهم آموزش- برنامه نویسی Parallel:آشنایی با کلاس Task در سی شارپ
قسمت دهم آموزش-برنامه نویسی Parallel در سی شارپ :: متوقف کردن Task ها در سی شارپ – کلاس CancellationToken
قسمت یازدهم آموزش- برنامه نویسی Parallel در سی شارپ :: کوئری های Parallel در LINQ
قسمت دوازدهم آموزش- آشنایی با کلمات کلیدی async و await در زبان سی شارپ
قسمت سیزدهم آموزش- استفاده از متد WhenAll برای اجرای چندین Task به صورت همزمان در سی شارپ
کار با Thread ها در زبان سی شارپ :: آشنایی با CLR ThreadPool در دات نت
به عنوان آخرین مبحث از سری مباحث مرتبط کار با Thread ها به سراغ نقش CLR ThreadPool می رویم. در قسمت ماهیت Asynchronous در delegate ها گفتیم که بوسیله متد BeginInvoke و EndInvoke می توان یک متد را به صورت Asynchronous فراخوانی کرد، اما نکته ای که اینجا وجود دارد این است که CLR با این کار به صورت مستقیم Thread جدیدی ایجاد نمی کند!
برای کارآیی بیشتر متد BeginInvoke یک آیتم در ThreadPool ایجاد می کند که فراخوانی آن در زمان اجرا مدیریت می شود. برای اینکه بتوانیم به طور مستقیم با این قابلیت در ارتباط باشیم، کلاسی با نام ThreadPool در فضای نام System.Threading وجود دارد که قابلیت این کار را به ما می دهد.
برای مثال اگر بخواهیم فراخوانی یک متد را به Thread Pool بسپاریم، کافیست از متد استاتیکی که در کلاس ThreadPool و با نام QueueUserWorkItem وجود دارد استفاده کنیم. بوسیله این متد می توان Callback مرتبط با کار مد نظر و همچنین شئ ای که به عنوان state استفاده می شود را به این متد ارسال کنیم. در زیر ساختار این کلاس را مشاهده می کنید:
public static class ThreadPool { public static bool QueueUserWorkItem(WaitCallback callback); public static bool QueueUserWorkItem(WaitCallback callback, object state); }
پارامتر WaitCallBack می تواند به هر متدی که نوع بازگشتی آن void و پارامتر ورودی آن از نوع System.Object است اشاره کند. دقت کنید که اگر مقداری برای state مشخص نکنید، به صورت خودکار مقدار null به آن پاس داده می شود. برای آشنایی بیشتر با این کلاس به مثال زیر که همان مثال نمایش اعداد در خروجی است دقت کنید، در ابتدا کلاس Printer:
public class Printer { object threadLock = new object(); public void PrintNumbers() { lock (threadLock) { Console.Write("{0} is printing numbers > ", Thread.CurrentThread.Name); for (int counter = 0; counter < 10; counter++) { Thread.Sleep(200 * new Random().Next(5)); Console.Write("{0},", counter); } Console.WriteLine(); } } }
در ادامه کد متد Main که با استفاده از ThreadPool عملیات ایجاد Thread ها را انجام می دهد:
class Program { static void Main(string[] args) { var printer = new Printer(); for (int index = 0; index < 10; index++) ThreadPool.QueueUserWorkItem(PrintNumbers, printer); Console.ReadLine(); } static void PrintNumbers(object state) { var printer = (Printer) state; printer.PrintNumbers(); } }
شاید این سوال برای شما پیش بیاید که مزیت استفاده از ThreadPool نسبت به اینکه به صورت دستی عملیات ایجاد و فراخوانی thread ها را انجام دهیم چیست؟ در زیر به برخی از مزایای اینکار اشاره می کنیم:
- Thread Pool به صورت بهینه تعداد عملیات مدیریت thread هایی که می بایست ایجاد شوند، شروع بشوند یا متوقف شوند را برای ما انجام می دهد.
- با استفاده از Thread Pool شما می توانید تمرکز خود را به جای ایجاد و مدیریت Thread ها بر روی منطق و اصل برنامه بگذارید و سایر کارها را به عهده CLR بگذارید.
اما موارد زیر نیز را مد نظر داشته باشید که مزیت ایجاد و مدیریت thread ها به صورت دستی می باشند:
- thread های ایجاد شده توسط thread pool به صورت پیش فرض از نوع foreground هستند، همچنین شما می توانید بوسیله ایجاد thread ها به صورت دستی Priority آن ها را نیز مشخص کنید.
- اگر ترتیب اجرای thread ها برای شما مهم باشند یا نیاز داشته باشید thread ها را به صورت دستی حذف یا متوقف کنید این کار بوسیله thread pool امکان پذیر نیست.
با به پایان رسیدن این مطلب، بحث ما بر روی Thread ها به پایان می رسد. به امید خدا در مطالب بعدی راجع به بحث Parallel Programming صحبت خواهیم خواهیم کرد.
منبع
قسمت اول آموزش-برنامه نویسی Asynchronous – آشنایی با Process ها، Thread ها و AppDomain ها
قسمت دوم آموزش- آشنایی با ماهیت Asynchronous در Delegate ها
قسمت سوم آموزش-آشنایی با فضای نام System.Threading و کلاس Thread
قسمت چهارم آموزش- آشنایی با Thread های Foreground و Background در دات نت
قسمت پنجم آموزش- آشنایی با مشکل Concurrency در برنامه های Multi-Threaded و راهکار های رفع این مشکل
قسمت ششم آموزش- آشنایی با کلاس Timer در زبان سی شارپ
قسمت هفتم آموزش-آشنایی با CLR ThreadPool در دات نت
قسمت هشتم آموزش- مقدمه ای بر Task Parallel Library و کلاس Parallel در دات نت
قسمت نهم آموزش- برنامه نویسی Parallel:آشنایی با کلاس Task در سی شارپ
قسمت دهم آموزش-برنامه نویسی Parallel در سی شارپ :: متوقف کردن Task ها در سی شارپ – کلاس CancellationToken
قسمت یازدهم آموزش- برنامه نویسی Parallel در سی شارپ :: کوئری های Parallel در LINQ
قسمت دوازدهم آموزش- آشنایی با کلمات کلیدی async و await در زبان سی شارپ
قسمت سیزدهم آموزش- استفاده از متد WhenAll برای اجرای چندین Task به صورت همزمان در سی شارپ
کار با Thread ها در زبان سی شارپ :: آشنایی با مشکل Concurrency در برنامه های Multi-Threaded و راهکار های رفع این مشکل
زمانی که ما برنامه های Multi-Threaded می نویسیم، برخی اوقات Thread های ایجاد شده به داده های مشترک در سطح برنامه دسترسی دارند و وظیفه ما به عنوان برنامه نویس این است که مطمئن باشیم دسترسی چند Thread به داده های مشترک باعث بروز مشکل نمی شود. برای آشنایی بیشتر با این موضوع شرایطی را در نظر بگیرید که یک متد قرار است در چندین thread مختلف به صورت جداگانه اجرا شود، بعد از شروع کار هر thread زمانبندی اجرا توسط CLR به هر thread به صورت خودکار انجام شده و ما نمی توانیم دخالتی در این موضوع داشته باشیم، ممکن است در این بین اختصاص زمان به یک thread بیش از thread دیگر انجام شود و در این بین خروجی مناسب مد نظر ما ایجاد نمی شود. برای آشنایی با این موضوع متد PrintNumbers که در زیر تعریف کردیم را در نظر بگیرید:
public static void PrintNumbers() { Console.Write("{0} is printing numbers > " , Thread.CurrentThread.Name); for (int counter = 0; counter < 10 ; counter++) { Thread.Sleep(200*new Random().Next(5)); Console.Write("{0},", counter); } Console.WriteLine(); }
در مرحله بعد متد Main را به صورت زیر تغییر می دهیم تا 10 thread ایجاد شده و سپس کلیه thread ها اجرا شوند:
Thread[] threads = new Thread[10]; for (int index = 0; index < 10 ; index++) { threads[index] = new Thread(PrintNumbers); threads[index].Name = string.Format("Worker thread #{0}.", index); } foreach (var thread in threads) { thread.Start(); } Console.ReadLine();
همانطور که مشاهده می کنید کلیه thread ها به صورت همزمان اجرا می شوند، اما پس از اجرا کد بالا، خروجی برای بار اول به صورت خواهد بود، البته دقت کنید که با هر بار اجرا خروجی تغییر می کند و ممکن است برای بار اول خروجی زیر برای شما تولید نشود:
Worker thread #0. is printing numbers > Worker thread #1. is printing numbers > Worker thread #2. is printing numbers > Worker thread #3. is printing numbers > 0,Worker thread #4. is printing numbers > 0,1,1,2,3,2,Worker thread #5. is printing numbers > 0,4,3,1,4,2,5,Worker thread #6. is printing numbers > 5,3,Worker thread #7. is printing numbers > 4,6,6,0,Worker thread #8. is printing numbers > Worker thread #9. is printing numbers > 0,0,7,5,7,0,1,0,1,0,6,8,8,1,2,1,1,0,2,9, 7,3,2,2,9, 2,1,8,3,3,3,4,2,1,9, 4,4,4,5,3,3,5,5,5,6,2,6,6,7,6,4,4,7,7,8,9, 8,9, 7,8,9, 5,5,3,8,6,7,8,9, 6,7,8,9, 4,5,9, 6,7,8,9,
اگر برنامه را مجدد اجرا کنید خروجی متفاوتی از خروجی قبلی دریافت خواهیم کرد:
Worker thread #0. is printing numbers > Worker thread #1. is printing numbers > Worker thread #2. is printing numbers > Worker thread #3. is printing numbers > Worker thread #4. is printing numbers > Worker thread #5. is printing numbers > Worker thread #6. is printing numbers > 0,Worker thread #7. is printing numbers > 1,2,0,1,3,2,4,3,Worker thread #8. is printing numbers > Worker thread #9. is printing numbers > 0,0,0,0,0,0,5,4,5,6,7,8,9, 6,7,8,9, 1,1,0,0,1,1,1,1,1,1,2,2,2,2,2,2,2,2,3,3,3,4,5,6,7,8,9, 3,3,3,3,3,4,4,4,4,5,6,7,8,9, 5,6,7,8,9, 5,6,7,5,6,7,8,9, 8,4,4,4,5,5,6,6,7,7,9, 5,8,8,6,9, 9, 7,8,9,
همانطور که مشاهده می کنید خروجی های ایجاد کاملاً با یکدیگر متفاوت هستند. مشخص است که در اینجا مشکلی وجود دارد و همانطور که در ابتدا گفتیم این مشکل وجود همزمانی یا Concurrency در زمان اجرای thread هاست. زمابندی CPU برای اجرای thread ها متفاوت است و با هر بار اجرا زمان های متفاوتی به thread ها برای اجرا تخصیص داده می شود. اما خوشبختانه مکانیزم های مختلفی برای رفع این مشکل و پیاده سازی Synchronizqation وجوددارد که در ادامه به بررسی راهکاری های مختلف برای حل مشکل همزمانی می پردازیم.
پیاده سازی Synchronization با کلمه کلیدی lock
اولین مکانیزم مدیریت همزمانی در زمان اجرای Thread ها استفاده از کلمه کلیدی lock است. این کلمه کلیدی به شما این اجازه را می دهد تا یک scope مشخص کنید که این scope باید به صورت synchronized بین thread ها به اشتراک گذاشته شود، یعنی زمانی که یک thread وارد scope ای شد که با کلمه کلیدی lock مشخص شده، thread های دیگر باید منتظر شوند تا thread جاری که در scope قرار دارد از آن خارج شود. برای استفاده از lock شما اصطلاحاً می بایست یک token را برای scope مشخص کنید که معمولاً این کار با ایجاد یک شئ از نوع object و مشخص کردن آن به عنوان token برای synchronization استفاده می شود. شیوه کلی استفاده از lock به صورت زیر است:
lock(token) { // all code in this scope are thread-safe }
اصطلاحاً می گویند کلیه کدهایی که در بدنه lock قرار دارند thread-safe هستند. برای اینکه کد داخل متد PrintNumbers به صورت thread-safe اجرا شود، ابتدا باید یک شئ برای استفاده به عنوان token در کلاس Program تعریف کنیم:
class Program { public static object threadLock = new object(); ....
در قدم بعدی کد داخل متد PrintNumbers را به صورت زیر تغییر می دهیم:
public static void PrintNumbers() { lock (threadLock) { Console.Write("{0} is printing numbers > " , Thread.CurrentThread.Name); for (int counter = 0; counter < 10 ; counter++) { Thread.Sleep(200 * new Random().Next(5)); Console.Write("{0},", counter); } Console.WriteLine(); } }
با اعمال تغییر بالا، زمانی که thread جدیدی قصد وارد شدن به scope مشخص شده را داشته باشد، باید منتظر بماند تا کار thread جاری به اتمام برسد تا اجرای thread جدید شروع شود. با اعمال تغییر بالا، هر چند بار که کد نوشته شده را اجرا کنید خروجی زیر را دریافت خواهید کرد:
Worker thread #0. is printing numbers > 0,1,2,3,4,5,6,7,8,9, Worker thread #1. is printing numbers > 0,1,2,3,4,5,6,7,8,9, Worker thread #2. is printing numbers > 0,1,2,3,4,5,6,7,8,9, Worker thread #3. is printing numbers > 0,1,2,3,4,5,6,7,8,9, Worker thread #4. is printing numbers > 0,1,2,3,4,5,6,7,8,9, Worker thread #5. is printing numbers > 0,1,2,3,4,5,6,7,8,9, Worker thread #6. is printing numbers > 0,1,2,3,4,5,6,7,8,9, Worker thread #7. is printing numbers > 0,1,2,3,4,5,6,7,8,9, Worker thread #8. is printing numbers > 0,1,2,3,4,5,6,7,8,9, Worker thread #9. is printing numbers > 0,1,2,3,4,5,6,7,8,9,
پیاده سازی Synchronization بوسیله کلاس Monitor
در قسمت قبل که از کلمه کلیدی lock استفاده کردیم، در حقیقت به در پشت زمینه از کلاس Monitor که در فضای نام System.Threading قرار دارد استفاده شده است. زمانی که از کلمه کلیدی lock استفاده می کنیم این کد تبدیل به کدی می شود که از Monitor برای پیاده سازی Synchronization استفاده می کند (می توان این موضوع را با ابزار ildasm.exe و مشاهده کد IL متوجه شد). نحوه استفاده از کلاس Mutex را در کد زیر که تغییر داده شده متد PrintNumbers است مشاهده می کنید:
public static void PrintNumbers() { Monitor.Enter(threadLock); try { Console.Write("{0} is printing numbers > " , Thread.CurrentThread.Name); for (int counter = 0; counter < 10 ; counter++) { Thread.Sleep(200*new Random().Next(5)); Console.Write("{0},", counter); } Console.WriteLine(); } finally { Monitor.Exit(threadLock); } }
متد Enter در کلاس Monitor اعلام می کند که thread ای وارد محدوده ای شده است که باید thread-safe باشد. به عنوان پارامتر ورودی برای این متد token ایجاد شده یعنی obj را ارسال می کنیم. در قدم بعدی کل کدی که مربوط به ناحیه thread-safe است باید داخل بدنه try-catch نوشته شود و البته بخش finaly نیز برای آن نوشته شده باشد، همانطور که می دانید بخش finally قسمتی از بدنه try است که در هر صورت اجرا می شود. در قسمت finally متد Exit را با پارامتر threadLock که همان token مربوطه است فراخوانی می کنیم، یعنی thread در حال اجرا از محدوده thread-safe خارج شده است. دلیل نوشتن try-catch در کد بالا این است که در صورت وقوع خطا عملیات خروج از محدوده thread-safe در هر صورت انجام شود و thread های دیگر منتظر ورود به این محدوده نمانند. شاید این سوال برای شما بوجود بیاید که با وجود کلمه کلیدی lock چه دلیلی برای استفاده از کلاس Monitor وجود دارد؟ دلیل این موضوع کنترل بیشتر بر روی ورود thread ها و اجرای کدهای thread-safe است. برای مثال، بوسیله متد Wait در کلاس Monitor می توان مشخص کرد که یک thread چه مدت زمانی را برای ورود به ناحیه thread-safe باید منتظر بماند و همچنین متدهای Pulse و PulseAll را می توان برای اطلاع رسانی به سایر thread ها در مورد اینکه کار thread جاری به اتمام رسیده استفاده کرد. البته در اکثر موقعیت ها استفاده از کلمه کلیدی lock کافی است و نیازی به استفاده از کلاس Monitor نمی باشد.
پیاده سازی Synchronization با استفاده از کلاس Interlocked
زمانی که قصد داریم مقدار یک متغیر را تغییر دهیم یا عملگرهای ریاضی را بر روی دو متغیر اعمال کنیم، عملیات های انجام شده به دو صورت Atomic و Non-Atomic انجام می شوند. مبحث عملیات عملیات های Atomic چیزی بیش از چند خط نیاز دارد تا توضیح داده شود، اما به طور خلاصه می توان گفت که عملیات های Atomic عملیات هایی هستند که تنها در یک مرحله یا step انجام می شوند. اگر کدهای IL مرتبط به مقدار دهی متغیرها و البته عملگرهای ریاضی را بر روی بیشتر نوع های داده در دات نت مشاهده کنیم میبینیم که این عملیات ها بیشتر به صورت non-atomic هستند، یعنی بیش از یک مرحله برای انجام عملیات مربوط نیاز است که این موضوع می تواند در برنامه های Multi-threaded مشکل ساز شود. در حالت ساده می توان بوسیله مکانیزم lock عملیات synchronization را برای این عملیات ها پیاده سازی کرد:
int value = 1; lock(token) { value++; }
اما برای شرایطی مانند مثال بالا استفاده از lock یا کلاس monitor باعث ایجاد overhead اضافی می شود که برای حل این مشکل می توان از کلاس Interlocked برای اعمال synchronization در اعمال انتساب مقدار یا مقایسه مقادیر استفاده کرد. برای مثال، زمانی که می خواهیم مقدار یک متغیر را با استفاده از کلاس Interlocked اضافه کنیم به صورت می توانیم این کار را پیاده سازی کنیم:
int myNumber = 10; Interlocked.Increment(ref myNumber);
متد Increment در کلاس Interlocked یک مقدار به متغیر مشخص شده اضافه می کند و البته این کار در محیط Thread-Safe انجام می شود. برای کاهش مقدار می توان از متد Decrement به صورت مشابه استفاده کرد:
int myNumber = 10; Interlocked.Decrement(ref myNumber);
همچنین برای مقایسه و مقدار دهی مقدار یک متغیر می توان از متد CompareExchange استفاده به صورت زیر استفاده کرد:
int myNumber = 10; Interlocked.CompareExchange(ref myNumber, 15, 10);
در کد بالا در صورتی که مقدار متغیر myNumber برابر 10 باشد، مقدار آن با 15 عوض خواهد شد.
پیاده سازی Synchronization بوسیله خاصیت [Synchronization]
می دانیم که خاصیت [Synchronization] زمانی که بر روی یک کلاس قرار میگیرد، باعث می شود که کد داخل آن کلاس به صورت Thread-Safe اجرا شود. در این قسمت می خواهیم کد مربوط به متد PrintNumbers را به صورت Thread-Safe و با کمک Object Context Boundry و همچنین خاصیت [Synchronization] پیاده سازی کنیم. برای این کار ابتدا یک کلاس با نام Printer پیاده سازی کرده و متد PrintNumbers را داخل آن قرار می دهیم. دقت کنید که کلاس Printer می بایست از کلاس ContextBoundObject مشتق شده باشد و خاصیت [Synchronization] بر روی آن قرار گرفته باشد:
[Synchronization] public class Printer : ContextBoundObject { public void PrintNumbers() { Console.Write("{0} is printing numbers > " , Thread.CurrentThread.Name); for (int counter = 0; counter < 10 ; counter++) { Thread.Sleep(200*new Random().Next(5)); Console.Write("{0},", counter); } Console.WriteLine(); } }
پس از انجام تغییرات بالا متد Main را به صورتی تغییر می دهیم که از متد Print در کلاس Printer استفاده کند:
Printer printer = new Printer(); Thread[] threads = new Thread[10]; for (int index = 0; index < 10 ; index++) { threads[index] = new Thread(printer.PrintNumbers); threads[index].Name = string.Format("Worker thread #{0}.", index); } foreach (var thread in threads) { thread.Start(); } Console.ReadLine();
با انجام کارهای بالا و اجرای برنامه مشاهده خواهیم کرد که با وجود عدم استفاده از کلمه کلیدی lock یا کلاس Monitor، برنامه به صورت Thread-Safe اجرا شده و خروجی مناسب برای ما تولید می شود. در اینجا مبحث مربوط به Synchronization به اتمام رسیده و در قسمت در مورد کلاس Timer در فضای نام System.Threading صحبت خواهیم کرد.
منبع
قسمت اول آموزش-برنامه نویسی Asynchronous – آشنایی با Process ها، Thread ها و AppDomain ها
قسمت دوم آموزش- آشنایی با ماهیت Asynchronous در Delegate ها
قسمت سوم آموزش-آشنایی با فضای نام System.Threading و کلاس Thread
قسمت چهارم آموزش- آشنایی با Thread های Foreground و Background در دات نت
قسمت پنجم آموزش- آشنایی با مشکل Concurrency در برنامه های Multi-Threaded و راهکار های رفع این مشکل
قسمت ششم آموزش- آشنایی با کلاس Timer در زبان سی شارپ
قسمت هفتم آموزش-آشنایی با CLR ThreadPool در دات نت
قسمت هشتم آموزش- مقدمه ای بر Task Parallel Library و کلاس Parallel در دات نت
قسمت نهم آموزش- برنامه نویسی Parallel:آشنایی با کلاس Task در سی شارپ
قسمت دهم آموزش-برنامه نویسی Parallel در سی شارپ :: متوقف کردن Task ها در سی شارپ – کلاس CancellationToken
قسمت یازدهم آموزش- برنامه نویسی Parallel در سی شارپ :: کوئری های Parallel در LINQ
قسمت دوازدهم آموزش- آشنایی با کلمات کلیدی async و await در زبان سی شارپ
قسمت سیزدهم آموزش- استفاده از متد WhenAll برای اجرای چندین Task به صورت همزمان در سی شارپ
الگوریتم Canny
لبه یاب کنی توسط جان اف کنی در سال 1986 ایجاد شد و هنوز یک لبه یاب استاندارد و با دقت و کیفیت بالا میباشد.الگوریتم لبه یابی کنی یکی از بهترین لبه یابها تا به امروز است. در ادامه روش کار این الگوریتم و هم چنین کد الگوریتم Canny در C را بررسی خواهیم کرد. این الگوریتم لبه یابی از سه بخش اصلی زیر تشکیل شده است:
- تضعیف نویز
- پیدا کردن نقاطی که بتوان آنها را به عنوان لبه در نظر گرفت
- جذب نقاطی که احتمال لبه بودن آنها کم است
معیارهایی که در لبه یاب کنی مطرح می باشد:
1 -پایین آوردن نرخ خطا- یعنی تا حد امکان هیچ لبه ای در تصویر نباید گم شود و هم چنین هیچ چیزی که لبه نیست نباید به جای لبه فرض شود. لبه هان پیدا شده تا حد ممکن به لبه ها اصلی
نزدیک باشند.
2 -لبه در مکان واقعی خود باشد- یعنی تا حد ممکن لبه ها کمترین فاصله را با مکان واقعی خود داشته باشند.
3 -بران هر لبه فقط یک پاسخ داشته باشیم.
4 -لبه ها کمترین ضخامت را داشته باشند- (در صورت امکان یک پیکسل).
لبه یاب کنی بخاطر توانایی در تولید لبه های نازک تا حد یک ییکسل برای لبه های پیوسته معروف شده است. این لبه یاب شامل چهار مرحله و چهار ورودی زیر است:
یک تصویر ورودی
یک پارامتر به نام سیگما جهت مقدار نرم کنندگی تصویر
یک حد آستانه بالا (Th)
یک حد آستانه پایین (Tl)
مراحل الگوریتم Canny:
1- در ابتدا باید تصویر رنگی را به جهت لبه یابی بهتر به یک تصویر سطح خاکسترن تبدیب کرد.
2- نویز را از تصویر دریافتی حذف کرد. بدلیل اینکه فیلتر گاوسین از یک ماسک ساده برای حذف نویز استفاده می کند لبه یاب کنی در مرحله اول برای حذف نویز آن را بکار می گیرد.
3- در یک تصویر سطح خاکستر جایی را که بیشترین تغییرات را داشته باشند به عنوان لبه در نظر گرفته می شوند و این مکانها با گرفتن گرادیان تصویر با استفاده عملگر سوبل بدست می آیند. سپس لبه های مات یافت شده به لبه های تیزتر تبدیل می شوند.
4- برخی از لبه های کشف شده واقعا لبه نیستند و در واقع نویز هستند که باید آنها توسط حد آستانه هیسترزیس فیلتر شوند.هیسترزیس از دو حد آستانه بالاتر (Th) و حد آستانه پایین تر (Tl) استفاده کرده و کنی پیشنهاد می کند که نسبت استانه بالا به پایین سه به یک باشد.
این روش بیشتر به کشف لبه های ضعیف به درستی می پردازد و کمتر فریب نویز را می خورد و از بقیه روش ها بهتر است.
کد الگوریتم Canny در C :
برنامه زیر یک فایل BMP سیاه و سفید 8 بیت در هر پیکسل را می خواند و نتیجه را در ‘out.bmp’ ذخیره می کند.با `-lm ‘ کامپایل می شود.
#include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <float.h> #include <math.h> #include <string.h> #include <stdbool.h> #include <assert.h> #define MAX_BRIGHTNESS 255 // C99 doesn't define M_PI (GNU-C99 does) #define M_PI 3.14159265358979323846264338327 /* * Loading part taken from * http://www.vbforums.com/showthread.php?t=261522 * BMP info: * http://en.wikipedia.org/wiki/BMP_file_format * * Note: the magic number has been removed from the bmpfile_header_t * structure since it causes alignment problems * bmpfile_magic_t should be written/read first * followed by the * bmpfile_header_t * [this avoids compiler-specific alignment pragmas etc.] */ typedef struct { uint8_t magic[2]; } bmpfile_magic_t; typedef struct { uint32_t filesz; uint16_t creator1; uint16_t creator2; uint32_t bmp_offset; } bmpfile_header_t; typedef struct { uint32_t header_sz; int32_t width; int32_t height; uint16_t nplanes; uint16_t bitspp; uint32_t compress_type; uint32_t bmp_bytesz; int32_t hres; int32_t vres; uint32_t ncolors; uint32_t nimpcolors; } bitmap_info_header_t; typedef struct { uint8_t r; uint8_t g; uint8_t b; uint8_t nothing; } rgb_t; // Use short int instead `unsigned char' so that we can // store negative values. typedef short int pixel_t; pixel_t *load_bmp(const char *filename, bitmap_info_header_t *bitmapInfoHeader) { FILE *filePtr = fopen(filename, "rb"); if (filePtr == NULL) { perror("fopen()"); return NULL; } bmpfile_magic_t mag; if (fread(&mag, sizeof(bmpfile_magic_t), 1, filePtr) != 1) { fclose(filePtr); return NULL; } // verify that this is a bmp file by check bitmap id // warning: dereferencing type-punned pointer will break // strict-aliasing rules [-Wstrict-aliasing] if (*((uint16_t*)mag.magic) != 0x4D42) { fprintf(stderr, "Not a BMP file: magic=%c%c\n", mag.magic[0], mag.magic[1]); fclose(filePtr); return NULL; } bmpfile_header_t bitmapFileHeader; // our bitmap file header // read the bitmap file header if (fread(&bitmapFileHeader, sizeof(bmpfile_header_t), 1, filePtr) != 1) { fclose(filePtr); return NULL; } // read the bitmap info header if (fread(bitmapInfoHeader, sizeof(bitmap_info_header_t), 1, filePtr) != 1) { fclose(filePtr); return NULL; } if (bitmapInfoHeader->compress_type != 0) fprintf(stderr, "Warning, compression is not supported.\n"); // move file point to the beginning of bitmap data if (fseek(filePtr, bitmapFileHeader.bmp_offset, SEEK_SET)) { fclose(filePtr); return NULL; } // allocate enough memory for the bitmap image data pixel_t *bitmapImage = malloc(bitmapInfoHeader->bmp_bytesz * sizeof(pixel_t)); // verify memory allocation if (bitmapImage == NULL) { fclose(filePtr); return NULL; } // read in the bitmap image data size_t pad, count=0; unsigned char c; pad = 4*ceil(bitmapInfoHeader->bitspp*bitmapInfoHeader->width/32.) - bitmapInfoHeader->width; for(size_t i=0; i<bitmapInfoHeader->height; i++){ for(size_t j=0; j<bitmapInfoHeader->width; j++){ if (fread(&c, sizeof(unsigned char), 1, filePtr) != 1) { fclose(filePtr); return NULL; } bitmapImage[count++] = (pixel_t) c; } fseek(filePtr, pad, SEEK_CUR); } // If we were using unsigned char as pixel_t, then: // fread(bitmapImage, 1, bitmapInfoHeader->bmp_bytesz, filePtr); // close file and return bitmap image data fclose(filePtr); return bitmapImage; } // Return: true on error. bool save_bmp(const char *filename, const bitmap_info_header_t *bmp_ih, const pixel_t *data) { FILE* filePtr = fopen(filename, "wb"); if (filePtr == NULL) return true; bmpfile_magic_t mag = {{0x42, 0x4d}}; if (fwrite(&mag, sizeof(bmpfile_magic_t), 1, filePtr) != 1) { fclose(filePtr); return true; } const uint32_t offset = sizeof(bmpfile_magic_t) + sizeof(bmpfile_header_t) + sizeof(bitmap_info_header_t) + ((1U << bmp_ih->bitspp) * 4); const bmpfile_header_t bmp_fh = { .filesz = offset + bmp_ih->bmp_bytesz, .creator1 = 0, .creator2 = 0, .bmp_offset = offset }; if (fwrite(&bmp_fh, sizeof(bmpfile_header_t), 1, filePtr) != 1) { fclose(filePtr); return true; } if (fwrite(bmp_ih, sizeof(bitmap_info_header_t), 1, filePtr) != 1) { fclose(filePtr); return true; } // Palette for (size_t i = 0; i < (1U << bmp_ih->bitspp); i++) { const rgb_t color = {(uint8_t)i, (uint8_t)i, (uint8_t)i}; if (fwrite(&color, sizeof(rgb_t), 1, filePtr) != 1) { fclose(filePtr); return true; } } // We use int instead of uchar, so we can't write img // in 1 call any more. // fwrite(data, 1, bmp_ih->bmp_bytesz, filePtr); // Padding: http://en.wikipedia.org/wiki/BMP_file_format#Pixel_storage size_t pad = 4*ceil(bmp_ih->bitspp*bmp_ih->width/32.) - bmp_ih->width; unsigned char c; for(size_t i=0; i < bmp_ih->height; i++) { for(size_t j=0; j < bmp_ih->width; j++) { c = (unsigned char) data[j + bmp_ih->width*i]; if (fwrite(&c, sizeof(char), 1, filePtr) != 1) { fclose(filePtr); return true; } } c = 0; for(size_t j=0; j<pad; j++) if (fwrite(&c, sizeof(char), 1, filePtr) != 1) { fclose(filePtr); return true; } } fclose(filePtr); return false; } // if normalize is true, map pixels to range 0..MAX_BRIGHTNESS void convolution(const pixel_t *in, pixel_t *out, const float *kernel, const int nx, const int ny, const int kn, const bool normalize) { assert(kn % 2 == 1); assert(nx > kn && ny > kn); const int khalf = kn / 2; float min = FLT_MAX, max = -FLT_MAX; if (normalize) for (int m = khalf; m < nx - khalf; m++) for (int n = khalf; n < ny - khalf; n++) { float pixel = 0.0; size_t c = 0; for (int j = -khalf; j <= khalf; j++) for (int i = -khalf; i <= khalf; i++) { pixel += in[(n - j) * nx + m - i] * kernel; c++; } if (pixel < min) min = pixel; if (pixel > max) max = pixel; } for (int m = khalf; m < nx - khalf; m++) for (int n = khalf; n < ny - khalf; n++) { float pixel = 0.0; size_t c = 0; for (int j = -khalf; j <= khalf; j++) for (int i = -khalf; i <= khalf; i++) { pixel += in[(n - j) * nx + m - i] * kernel; c++; } if (normalize) pixel = MAX_BRIGHTNESS * (pixel - min) / (max - min); out[n * nx + m] = (pixel_t)pixel; } } /* * gaussianFilter: * http://www.songho.ca/dsp/cannyedge/cannyedge.html * determine size of kernel (odd #) * 0.0 <= sigma < 0.5 : 3 * 0.5 <= sigma < 1.0 : 5 * 1.0 <= sigma < 1.5 : 7 * 1.5 <= sigma < 2.0 : 9 * 2.0 <= sigma < 2.5 : 11 * 2.5 <= sigma < 3.0 : 13 ... * kernelSize = 2 * int(2*sigma) + 3; */ void gaussian_filter(const pixel_t *in, pixel_t *out, const int nx, const int ny, const float sigma) { const int n = 2 * (int)(2 * sigma) + 3; const float mean = (float)floor(n / 2.0); float kernel[n * n]; // variable length array fprintf(stderr, "gaussian_filter: kernel size %d, sigma=%g\n", n, sigma); size_t c = 0; for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) { kernel = exp(-0.5 * (pow((i - mean) / sigma, 2.0) + pow((j - mean) / sigma, 2.0))) / (2 * M_PI * sigma * sigma); c++; } convolution(in, out, kernel, nx, ny, n, true); } /* * Links: * http://en.wikipedia.org/wiki/Canny_edge_detector * http://www.tomgibara.com/computer-vision/CannyEdgeDetector.java * http://fourier.eng.hmc.edu/e161/lectures/canny/node1.html * http://www.songho.ca/dsp/cannyedge/cannyedge.html * * Note: T1 and T2 are lower and upper thresholds. */ pixel_t *canny_edge_detection(const pixel_t *in, const bitmap_info_header_t *bmp_ih, const int tmin, const int tmax, const float sigma) { const int nx = bmp_ih->width; const int ny = bmp_ih->height; pixel_t *G = calloc(nx * ny * sizeof(pixel_t), 1); pixel_t *after_Gx = calloc(nx * ny * sizeof(pixel_t), 1); pixel_t *after_Gy = calloc(nx * ny * sizeof(pixel_t), 1); pixel_t *nms = calloc(nx * ny * sizeof(pixel_t), 1); pixel_t *out = malloc(bmp_ih->bmp_bytesz * sizeof(pixel_t)); if (G == NULL || after_Gx == NULL || after_Gy == NULL || nms == NULL || out == NULL) { fprintf(stderr, "canny_edge_detection:" " Failed memory allocation(s).\n"); exit(1); } gaussian_filter(in, out, nx, ny, sigma); const float Gx[] = {-1, 0, 1, -2, 0, 2, -1, 0, 1}; convolution(out, after_Gx, Gx, nx, ny, 3, false); const float Gy[] = { 1, 2, 1, 0, 0, 0, -1,-2,-1}; convolution(out, after_Gy, Gy, nx, ny, 3, false); for (int i = 1; i < nx - 1; i++) for (int j = 1; j < ny - 1; j++) { const int c = i + nx * j; // G = abs(after_Gx) + abs(after_Gy); G = (pixel_t)hypot(after_Gx, after_Gy); } // Non-maximum suppression, straightforward implementation. for (int i = 1; i < nx - 1; i++) for (int j = 1; j < ny - 1; j++) { const int c = i + nx * j; const int nn = c - nx; const int ss = c + nx; const int ww = c + 1; const int ee = c - 1; const int nw = nn + 1; const int ne = nn - 1; const int sw = ss + 1; const int se = ss - 1; const float dir = (float)(fmod(atan2(after_Gy, after_Gx) + M_PI, M_PI) / M_PI) * 8; if (((dir <= 1 || dir > 7) && G > G[ee] && G > G[ww]) || // 0 deg ((dir > 1 && dir <= 3) && G > G[nw] && G > G[se]) || // 45 deg ((dir > 3 && dir <= 5) && G > G[nn] && G > G[ss]) || // 90 deg ((dir > 5 && dir <= 7) && G > G[ne] && G > G[sw])) // 135 deg nms = G; else nms = 0; } // Reuse array // used as a stack. nx*ny/2 elements should be enough. int *edges = (int*) after_Gy; memset(out, 0, sizeof(pixel_t) * nx * ny); memset(edges, 0, sizeof(pixel_t) * nx * ny); // Tracing edges with hysteresis . Non-recursive implementation. size_t c = 1; for (int j = 1; j < ny - 1; j++) for (int i = 1; i < nx - 1; i++) { if (nms >= tmax && out == 0) { // trace edges out = MAX_BRIGHTNESS; int nedges = 1; edges[0] = c; do { nedges--; const int t = edges[nedges]; int nbs[8]; // neighbours nbs[0] = t - nx; // nn nbs[1] = t + nx; // ss nbs[2] = t + 1; // ww nbs[3] = t - 1; // ee nbs[4] = nbs[0] + 1; // nw nbs[5] = nbs[0] - 1; // ne nbs[6] = nbs[1] + 1; // sw nbs[7] = nbs[1] - 1; // se for (int k = 0; k < 8; k++) if (nms[nbs[k]] >= tmin && out[nbs[k]] == 0) { out[nbs[k]] = MAX_BRIGHTNESS; edges[nedges] = nbs[k]; nedges++; } } while (nedges > 0); } c++; } free(after_Gx); free(after_Gy); free(G); free(nms); return out; } int main(const int argc, const char ** const argv) { if (argc < 2) { printf("Usage: %s image.bmp\n", argv[0]); return 1; } static bitmap_info_header_t ih; const pixel_t *in_bitmap_data = load_bmp(argv[1], &ih); if (in_bitmap_data == NULL) { fprintf(stderr, "main: BMP image not loaded.\n"); return 1; } printf("Info: %d x %d x %d\n", ih.width, ih.height, ih.bitspp); const pixel_t *out_bitmap_data = canny_edge_detection(in_bitmap_data, &ih, 45, 50, 1.0f); if (out_bitmap_data == NULL) { fprintf(stderr, "main: failed canny_edge_detection.\n"); return 1; } if (save_bmp("out.bmp", &ih, out_bitmap_data)) { fprintf(stderr, "main: BMP image not saved.\n"); return 1; } free((pixel_t*)in_bitmap_data); free((pixel_t*)out_bitmap_data); return 0; }
دانلود کد فوق از طریق لینک زیر:
Canny in C
رمز فایل : behsanandish.com
الگوریتم Canny
لبه یاب کنی توسط جان اف کنی در سال 1986 ایجاد شد و هنوز یک لبه یاب استاندارد و با دقت و کیفیت بالا میباشد.الگوریتم لبه یابی کنی یکی از بهترین لبه یابها تا به امروز است. در ادامه روش کار این الگوریتم و هم چنین کد الگوریتم Canny در Visual Basic را بررسی خواهیم کرد. این الگوریتم لبه یابی از سه بخش اصلی زیر تشکیل شده:
- تضعیف نویز
- پیدا کردن نقاطی که بتوان آنها را به عنوان لبه در نظر گرفت
- حذب نقاطی که احتمال لبه بودن آنها کم است
معیارهایی که در لبه یا کنی مطرح است:
1 -پایین آوردن نرخ خطا- یعنی تا حد امکان هیچ لبه ای در تصویر نباید گم شود و هم چنین هیچ چیزی که لبه نیست نباید به جای لبه فرض شود. لبه هان پیدا شده تا حد ممکن به لبه ها اصلی
نزدیک باشند.
2 -لبه در مکان واقعی خود باشد- یعنی تا حد ممکن لبه ها کمترین فاصله را با مکان واقعی خود داشته باشند.
3 -بران هر لبه فقط یک پاسخ داشته باشیم.
4 -لبه ها کمترین ضخامت را داشته باشند- (در صورت امکان یک پیکسل).
لبه یاب کنی بخاطر توانایی در تولید لبه های نازک تا حد یک ییکسل برای لبه های پیوسته معروف شده است. این لبه یاب شامل چهار مرحله و چهار ورودی زیر است:
یک تصویر ورودی
یک پارامتر به نام سیگما جهت مقدار نرم کنندگی تصویر
یک حد آستانه بالا (Th)
یک حد آستانه پایین (Tl)
مراحل الگوریتم Canny:
1- در ابتدا باید تصویر رنگی را به جهت لبه یابی بهتر به یک تصویر سطح خاکسترن تبدیب کرد.
2- نویز را از تصویر دریافتی حذف کرد. بدلیل اینکه فیلتر گاوسین از یک ماسک ساده برای حذف نویز استفاده می کند لبه یاب کنی در مرحله اول برای حذف نویز آن را بکار میگیرد.
3- در یک تصویر سطح خاکستر جایی را که بیشترین تغییرات را داشته باشند به عنوان لبه در نظر گرفته می شوند و این مکانها با گرفتن گرادیان تصویر با استفاده عملگر سوبل بدست می آیند. سپس لبه های مات یافت شده به لبه های تیزتر تبدیل می شوند.
4- برخی از لبه های کشف شده واقعا لبه نیستند و در واقع نویز هستند که باید آنها توسط حد آستانه هیسترزیس فیلتر شوند.هیسترزیس از دو حد آستانه بالاتر (Th) و حد آستانه پایین تر (Tl) استفاده کرده و کنی پیشنهاد می کند که نسبت استانه بالا به پایین سه به یک باشد.
این روش بیشتر به کشف لبه های ضعیف به درستی می پردازد و کمتر فریب نویز را می خورد و از بقیه روش ها بهتر است.
الگوریتم Canny در Visual Basic:
کد زیر یک کد تکمیل نشده است.تکمیل آن به عنوان تمرین به خواننده واگذار می شود.
Imports System.Drawing Imports System.Drawing.Imaging Public Class clsEdges Public Sub EdgeDetectDifference(ByVal b As Bitmap, ByVal threshold As Byte) ' first we create a clone o the image we want to find the edges on Dim b2 As Bitmap = b.Clone ' we create bitmapdata of the images at the same time locking them Dim bmData1 As BitmapData = b.LockBits(New Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb) Dim bmData2 As BitmapData = b2.LockBits(New Rectangle(0, 0, b2.Width, b2.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb) ' the stride describes the distance between image bytes Dim stride As Integer = bmData2.Stride ' scan0 is some sort of OS handle or something to identify the actual data in the memory Dim scan01 As IntPtr = bmData1.Scan0 Dim scan02 As IntPtr = bmData2.Scan0 ' we need to know how big the data is so that we can create the correct size for the file Dim bytes As Integer = b.Height * b.Width * 3 ' we create the byte arrays so that we can edit them Dim p01(bytes - 1) As Byte Dim p02(bytes - 1) As Byte ' put the images into the byte arrays System.Runtime.InteropServices.Marshal.Copy(scan01, p01, 0, bytes) System.Runtime.InteropServices.Marshal.Copy(scan02, p02, 0, bytes) ' the nWidth describes the width of the actual image multiplied by three for each byte in the pixel (3 bytes per pixel 24 bits ;)) Dim nWidth As Integer = b2.Width * 3 ' for some reason although the original code show a formula to come up with the offset this doesn't work very well. ' I found that it is just easier to make the offset 0 and so all bits are handled. Basically the problem comes when ' using this on files that don't have Dim nOffset As Integer = 0 Dim nPixel As Integer = 0, npixelmax As Integer = 0 Dim pos1 As Integer = stride + 3 Dim pos2 As Integer = stride + 3 Dim p2minusplus As Integer, p2plusminus As Integer, p2plusplus As Integer, p2minusminus As Integer Dim p2minusstride As Integer, p2plusstride As Integer Dim p2plus As Integer, p2minus As Integer For y As Integer = 1 To b.Height - 1 For x As Integer = 1 To nWidth - 3 p2minusplus = pos2 - stride + 3 p2plusminus = pos2 + stride - 3 p2plusplus = pos2 + stride + 3 p2minusminus = pos2 - stride - 3 p2minusstride = pos2 - stride p2plusstride = pos2 + stride p2minus = pos2 - 3 p2plus = pos2 + 3 If p2minusplus <= p02.Length - 1 And p2minusplus >= 0 And p2plusminus <= p02.Length - 1 And p2plusminus >= 0 And _ p2plusplus <= p02.Length - 1 And p2plusplus >= 0 And p2minusminus <= p02.Length - 1 And p2minusminus >= 0 And _ p2minusstride <= p02.Length - 1 And p2minusstride >= 0 And p2plusstride <= p02.Length - 1 And p2plusstride >= 0 And _ p2plus <= p02.Length - 1 And p2plus >= 0 And p2minus <= p02.Length - 1 And p2minus >= 0 And pos1 < p01.Length Then npixelmax = Math.Abs(CInt(p02(p2minusplus)) - CInt(p02(p2plusminus))) nPixel = Math.Abs(CInt(p02(p2plusplus)) - CInt(p02(p2minusminus))) If nPixel > npixelmax Then npixelmax = nPixel nPixel = Math.Abs(CInt(p02(p2minusstride)) - CInt(p02(p2plusstride))) If nPixel > npixelmax Then npixelmax = nPixel nPixel = Math.Abs(CInt(p02(p2plus)) - CInt(p02(p2minus))) If nPixel > npixelmax Then npixelmax = nPixel If npixelmax < CInt(threshold) Then npixelmax = 0 p01(pos1) = CByte(npixelmax) End If pos1 += 1 pos2 += 1 Next pos1 += nOffset pos2 += nOffset Next System.Runtime.InteropServices.Marshal.Copy(p01, 0, scan01, bytes) b.UnlockBits(bmData1) b2.UnlockBits(bmData2) End Sub Public Sub EdgeDetectHomogenity(ByVal b As Bitmap, ByVal threshold As Byte) Dim b2 As Bitmap = b.Clone Dim bmData1 As BitmapData = b.LockBits(New Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb) Dim bmData2 As BitmapData = b2.LockBits(New Rectangle(0, 0, b2.Width, b2.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb) Dim stride As Integer = bmData2.Stride Dim scan01 As IntPtr = bmData1.Scan0 Dim scan02 As IntPtr = bmData2.Scan0 Dim bytes As Integer = b.Height * b.Width * 3 Dim p01(bytes - 1) As Byte Dim p02(bytes - 1) As Byte System.Runtime.InteropServices.Marshal.Copy(scan01, p01, 0, bytes) System.Runtime.InteropServices.Marshal.Copy(scan02, p02, 0, bytes) Dim nWidth As Integer = b2.Width * 3 Dim nOffset As Integer = 0 Dim nPixel As Integer = 0, npixelmax As Integer = 0 Dim pos1 As Integer = stride + 3 Dim pos2 As Integer = stride + 3 Dim p2plusminus As Integer, p2plusstride As Integer, p2plusplus As Integer, p2minusstride As Integer, _ p2minusminus As Integer, p2minusplus As Integer For y As Integer = 1 To b.Height - 1 For x As Integer = 1 To nWidth - 3 p2plusminus = pos2 + stride - 3 p2plusstride = pos2 + stride p2plusplus = pos2 + stride + 3 p2minusstride = pos2 - stride p2minusminus = pos2 - stride - 3 p2minusplus = pos2 - stride + 3 If p2plusminus < p02.Length And p2plusminus >= 0 And p2plusstride < p02.Length And p2plusstride >= 0 And _ p2plusplus < p02.Length And p2plusplus >= 0 And p2minusstride < p02.Length And p2minusstride >= 0 And _ p2minusstride < p02.Length And p2minusstride >= 0 And p2minusminus < p02.Length And p2minusminus >= 0 And _ p2minusplus < p02.Length And p2minusplus >= 0 Then npixelmax = Math.Abs(CInt(p02(pos2)) - CInt(p02(p2plusminus))) nPixel = Math.Abs(CInt(p02(pos2)) - CInt(p02(p2plusstride))) If nPixel > npixelmax Then npixelmax = nPixel nPixel = Math.Abs(CInt(p02(pos2)) - CInt(p02(p2plusplus))) If nPixel > npixelmax Then npixelmax = nPixel nPixel = Math.Abs(CInt(p02(pos2)) - CInt(p02(p2minusstride))) If nPixel > npixelmax Then npixelmax = nPixel nPixel = Math.Abs(CInt(p02(pos2)) - CInt(p02(p2plusstride))) If nPixel > npixelmax Then npixelmax = nPixel nPixel = Math.Abs(CInt(p02(pos2)) - CInt(p02(p2minusminus))) If nPixel > npixelmax Then npixelmax = nPixel nPixel = Math.Abs(CInt(p02(pos2)) - CInt(p02(p2minusstride))) If nPixel > npixelmax Then npixelmax = nPixel nPixel = Math.Abs(CInt(p02(pos2)) - CInt(p02(p2minusplus))) If nPixel > npixelmax Then npixelmax = nPixel If npixelmax < threshold Then npixelmax = 0 p01(pos1) = CByte(npixelmax) End If pos1 += 1 pos2 += 1 Next pos1 += nOffset pos2 += nOffset Next System.Runtime.InteropServices.Marshal.Copy(p01, 0, scan01, bytes) b.UnlockBits(bmData1) b2.UnlockBits(bmData2) End Sub Public Function EdgeEnhance(ByVal b As Bitmap, ByVal threshold As Byte) As Boolean Dim b2 As Bitmap = b.Clone Dim bmData1 As BitmapData = b.LockBits(New Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb) Dim bmData2 As BitmapData = b2.LockBits(New Rectangle(0, 0, b2.Width, b2.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb) Dim stride As Integer = bmData2.Stride Dim scan01 As IntPtr = bmData1.Scan0 Dim scan02 As IntPtr = bmData2.Scan0 Dim bytes As Integer = b.Height * b.Width * 3 Dim p01(bytes - 1) As Byte Dim p02(bytes - 1) As Byte System.Runtime.InteropServices.Marshal.Copy(scan01, p01, 0, bytes) System.Runtime.InteropServices.Marshal.Copy(scan02, p02, 0, bytes) Dim nWidth As Integer = b2.Width * 3 Dim nOffset As Integer = 0 Dim nPixel As Integer = 0, npixelmax As Integer = 0 Dim pos1 As Integer = stride + 3 Dim pos2 As Integer = stride + 3 Dim p2minusplus As Integer, p2plusminus As Integer, p2plusplus As Integer, p2minusminus As Integer Dim p2minusstride As Integer, p2plusstride As Integer Dim p2plus As Integer, p2minus As Integer For y As Integer = 1 To b.Height - 1 For x As Integer = 1 To nWidth - 3 p2minusplus = pos2 - stride + 3 p2plusminus = pos2 + stride - 3 p2plusplus = pos2 + stride + 3 p2minusminus = pos2 - stride - 3 p2minusstride = pos2 - stride p2plusstride = pos2 + stride p2minus = pos2 - 3 p2plus = pos2 + 3 If p2minusplus <= p02.Length - 1 And p2minusplus >= 0 And p2plusminus <= p02.Length - 1 And p2plusminus >= 0 And _ p2plusplus <= p02.Length - 1 And p2plusplus >= 0 And p2minusminus <= p02.Length - 1 And p2minusminus >= 0 And _ p2minusstride <= p02.Length - 1 And p2minusstride >= 0 And p2plusstride <= p02.Length - 1 And p2plusstride >= 0 And _ p2plus <= p02.Length - 1 And p2plus >= 0 And p2minus <= p02.Length - 1 And p2minus >= 0 And pos1 < p01.Length Then npixelmax = Math.Abs(CInt(p02(pos2 - stride + 3)) - CInt(p02(pos2 + stride - 3))) nPixel = Math.Abs(CInt(p02(pos2 + stride + 3)) - CInt(p02(pos2 - stride - 3))) If nPixel > npixelmax Then npixelmax = nPixel nPixel = Math.Abs(CInt(p02(pos2 - stride)) - CInt(p02(pos2 + stride))) If nPixel > npixelmax Then npixelmax = nPixel nPixel = Math.Abs(CInt(p02(pos2 + 3)) - CInt(p02(pos2 - 3))) If nPixel > npixelmax Then npixelmax = nPixel If npixelmax > threshold And npixelmax > p01(pos1) Then p01(pos1) = CByte(Math.Max(CInt(p01(pos1)), npixelmax)) End If End If pos1 += 1 pos2 += 1 Next pos1 += nOffset pos2 += nOffset Next System.Runtime.InteropServices.Marshal.Copy(p01, 0, scan01, bytes) b.UnlockBits(bmData1) b2.UnlockBits(bmData2) Return True End Function End Class
این کد کامل نیست!
دانلود کد فوق از طریق لینک زیر:
CannyInVisualBasic
رمز فایل : behsanandish.com
مقدمه
الگوریتم های لبه یابی- انسان مي تواند بسیاري از اشیاء را از روي تصویر خطوط آنها شناسایي كند. بهترین مثال برای آن تصاویر کارتنی است. سیستم بینایي انسان قبل از بازشناسي رنگ یا شدت روشنایي نوعی كشف لبه انجام مي دهد. بنابراین انجام كشف لبه قبل از تفسیر تصاویر در سیستمهاي خودكار منطقي به نظر مي رسد. انجام عملیات كشف لبه پردازش مهمي در بسیاري از سیستمهاي بینایي مصنوعي محسوب مي شود. هدف اصلی لبه یابی کاهش حجم داده ها در تصویر به همراه حفظ ساختار و شکل اصلی تصویر است. مرزماند سایه یک واقعیت فیزیکی نیست و عبارت است از جایی که بخشی از تصویر شروع یا تمام میشود. لبه را میتوان به عنوان جایی که صفحات افقی و عمودی جسم به هم میرسند در نظر گرفت.
یکی از متداولترین اعمال در تحلیل تصویر تشخیص لبه می باشد به این دلیل که لبه مرز میان یک شی و زمینهء آن است به عبارت دیگر لبه تغییر دو سطح خاکستري یا مقادیر مربوط به روشنایی دو پیکسل مجاور است که در مکان خاصی از تصویر رخ می دهد.هر چه این تغییر در سطح بیشتر باشد تشخیص لبه ساده تر خواهد بود.
نقاطي از تصویر كه داراي تغییرات روشنایي ناگھاني ھستند اغلب لبه یا نقاط لبه نامیده مي شوند. نقاط لبه معمولا ً شامل مرزھاي اشیاء و دیگر انواع تغییرات روشنایي و ھمچنین لبه ھاي نویزي مي باشند.
انواع لبه:
انواع الگوریتم های لبه یابی
1- الگوریتم soble
این متد لبه ها را با استفاده از تخمین زدن مشتق پیدا می کند، که لبه ها را در آن نقاطی بر می گرداند که گرادیان تصویر I ، max است. در فیلتر سوبل دو ماسک به صورت زیر وجود دارد:
ماسک سوبل افقی بیشتر لبه هاي افقی را مشخص میکند و ماسک سوبل عمودي،لبه هاي عمودي را مشخص میکند.
براي مشخص شدن کلیه لبه ها:
اگر Gx و Gy تصاویر فیلتر شده به وسیله ماسک افقی و عمودي باشند، آنگاه تصویر لبه هاي تصویر را بهتر نشان میدهد. روال فوق به عملگر یا الگورریتم سوبل موسوم است.
در عمل، به منظور کاهش هزینه محاسبات، به جاي میتوان از تقریب [Gx] + [Gy] استفاده میشود. توجه شود که نتیجه این دو فرمول تقریبا یکسان است ولی فرمول دوم با هزینه کمتري قابل محاسبه است.
کد الگوریتم Sobel در Matlab:
clc; clear; close all; warning off; I=imread('lena.bmp'); I=im2double(I); I=imnoise(I, 'gaussian', 0, 0.001); figure(1); imshow(I);title('org img'); [height width R]=size(I); for i=2:height-1 for j=2:width-1 Dx(i,j)=[I(i+1,j-1)-I(i-1,j-1)]+2*[I(i+1,j)-I(i-1,j)]+[I(i+1,j+1)-I(i-1,j+1)]; Dy(i,j)=[I(i-1,j+1)-I(i-1,j-1)]+2*[I(i,j+1)-I(i,j-1)]+[I(i+1,j+1)-I(i+1,j-1)]; S(i,j)=sqrt(Dx(i,j)^2+Dy(i,j)^2); if Dx(i,j)<1 Dx(i,j)=0; else Dx(i,j)=1; end if Dy(i,j)<1 Dy(i,j)=0; else Dy(i,j)=1; end end end figure(2); imshow(Dx,[]); figure(3); imshow(Dy,[]); for i=1:255 for j=1:255 if (S(i,j)<1) S(i,j)=0; else S(i,j)=1; end end end
الگوریتم سوبل به زبان متلب
رمز فایل : behsanandish.com
2- الگوریتم Canny
لبه یاب کنی توسط جان اف کنی در سال 1986 ایجدداد شد و هنوز یک لبه یاب استاندارد و با دقت و کیفیت بالا میباشد.الگوریتم لبه یابی کنی یکی از بهترین لبه یابها تا به امروز است. این الگوریتم لبه یابی از سه بخش اصلی زیر تشکیل شده است:
- تضعیف نویز
- پیدا کردن نقاطی که بتوان آنها را به عنوان لبه در نظر گرفت
- حذب نقاطی که احتمال لبه بودن آنها کم است
معیارهایی که در لبه یا کنی مطرح است:
1 -پایین آوردن نرخ خطا- یعنی تا حد امکان هیچ لبه ای در تصویر نباید گم شود و هم چنین هیچ چیزی که لبه نیست نباید به جای لبه فرض شود. لبه هان پیدا شده تا حد ممکن به لبه ها اصلی
نزدیک باشند.
2 -لبه در مکان واقعی خود باشد- یعنی تا حد ممکن لبه ها کمترین فاصله را با مکان واقعی خود داشته باشند.
3 -بران هر لبه فقط یک پاسخ داشته باشیم.
4 -لبه ها کمترین ضخامت را داشته باشند- (در صورت امکان یک پیکسل).
لبه یاب کنی بخاطر توانایی در تولید لبه های نازک تا حد یک ییکسل برای لبه های پیوسته معروف شده است. این لبه یاب شامل چهار مرحله و چهار ورودی زیر است:
یک تصویر ورودی
یک پارامتر به نام سیگما جهت مقدار نرم کنندگی تصویر
یک حد آستانه بالا (Th)
یک حد آستانه پایین (Tl)
و مراحل شامل
1- در ابتدا باید تصویر رنگی را به جهت لبه یابی بهتر به یک تصویر سطح خاکسترن تبدیب کرد.
2- نویز را از تصویر دریافتی حذف کرد. بدلیل اینکه فیلتر گاوسین از یک ماسک ساده برای حذف نویز استفاده می کند لبه یاب کنی در مرحله اول برای حذف نویز آن را بکار میگیرد.
3- در یک تصویر سطح خاکستر جایی را که بیشترین تغییرات را داشته باشند به عنوان لبه در نظر گرفته می شوند و این مکانها با گرفتن گرادیان تصویر با استفاده عملگر سوبل بدست می آیند. سپس لبه های مات یافت شده به لبه های تیزتر تبدیل می شوند.
4- برخی از لبه های کشف شده واقعا لبه نیستند و در واقع نویز هستند که باید آنها توسط حد آستانه هیسترزیس فیلتر شوند.هیسترزیس از دو حد آستانه بالاتر (Th) و حد آستانه پایین تر (Tl) استفاده کرده و کنی پیشنهاد می کند که نسبت استانه بالا به پایین سه به یک باشد.
این روش بیشتر به کشف لبه های ضعیف به درستی می پردازد و کمتر فریب نویز را می خورد و از بقیه روش ها بهتر است.
clear; clc; close all; warning off; I=imread('siahosefid.bmp'); I=imnoise(I, 'gaussian', 0, 0.001); figure(1) imshow(I); A=filter2(fspecial('average',3),I)/255; figure(2) imshow(A); gauss_I=I; Isize=size(I); ans=zeros(size(I)); dir=zeros(size(I)); I=double(I); gauss_I=double(gauss_I); fx=0; fy=0; for i=2:Isize(1)-1 for j=2:Isize(2)-1 fx=gauss_I(i,j)+gauss_I(i,j+1)-gauss_I(i+1,j)-gauss_I(i+1,j+1); fy=gauss_I(i,j)+gauss_I(i+1,j)-gauss_I(i,j+1)-gauss_I(i+1,j+1); ans(i,j)=sqrt(fx*fx+fy*fy); dir(i,j)=atan(fy/fx); end end figure(3) imshow(ans) for i=2:Isize(1)-1 for j=2:Isize(2)-1 if dir(i,j)>=-pi/8 & dir(i,j)<pi/8</pre> <pre> if ans(i,j)<=ans(i,j-1) | ans(i,j)<=ans(i,j+1) ans(i,j)=0; end end if dir(i,j)>=pi/8 & dir(i,j)<3*pi/8 if ans(i,j)<=ans(i-1,j+1) | ans(i,j)<=ans(i+1,j-1) ans(i,j)=0; end end if dir(i,j)>=3*pi/8 | dir(i,j)<-3*pi/8 if ans(i,j)<=ans(i-1,j) | ans(i,j)<=ans(i+1,j) ans(i,j)=0; end end if dir(i,j)<-pi/8 & dir(i,j)>=3*pi/8 if ans(i,j)<=ans(i-1,j-1) | ans(i,j)<=ans(i+1,j+1) ans(i,j)=0; end end if ans(i,j)<40 ans(i,j)=0; else ans(i,j)=255; end end end figure(4) imshow(ans)
دانلود کد فوق از طریق لینک زیر:
رمز فایل : behsanandish.com
3- الگوریتم Roberts
این الگوریتم به نویز حساسیت زیادی دارد وپیکسل های کمتری را برای تقریب گرادیان بکار می برد،درضمن نسبت به الگوریتم canny هم قدرت کمتری دارد.
clc; clear; close all; warning off; I=imread('siahosefid.bmp'); I=imnoise(I, 'gaussian', 0, 0.001); I=im2double(I); figure(1); imshow(I); [height width R]=size(I); for i=2:height-1 for j=2:width-1 R(i,j)=abs(I(i+1,j+1)-I(i,j))+abs(I(i+1,j)-I(i,j+1)); Z(i,j)=abs(I(i+1,j+1)-I(i,j)); X(i,j)=abs(I(i+1,j)-I(i,j+1)); end end for i=1:height-1 for j=1:width-1 if (R(i,j)<0.25) R(i,j)=0; else R(i,j)=1; end if (Z(i,j)<0.25) Z(i,j)=0; else Z(i,j)=1; end if (X(i,j)<0.25) X(i,j)=0; else X(i,j)=1; end end end figure(2); imshow(Z,[]); figure(3); imshow(X,[]); figure(4); imshow(R,[]);
دانلود کد فوق از طریق لینک زیر:
Robert in Matlab
رمز فایل : behsanandish.com
4- الگوریتم Prewitt
این الگوریتم شباهت زیادی با الگوریتم sobel دارد با این تفاوت که ضرایب ماسک آنها با هم فرق می کند.
I=imread('siahosefid.bmp'); I=im2double(I); I=imnoise(I, 'gaussian', 0, 0.001); figure(1); imshow(I,[]); [height width R]=size(I); for i=2:height-1 for j=2:width-1 Dx(i,j)=[I(i+1,j-1)-I(i-1,j-1)]+[I(i+1,j)-I(i-1,j)]+[I(i+1,j+1)-I(i-1,j+1)]; Dy(i,j)=[I(i-1,j+1)-I(i-1,j-1)]+[I(i,j+1)-I(i,j-1)]+[I(i+1,j+1)-I(i+1,j-1)]; P(i,j)=sqrt(Dx(i,j)^2+Dy(i,j)^2); if Dx(i,j)<0.5 Dx(i,j)=0; else Dx(i,j)=1; end if Dy(i,j)<0.5 Dy(i,j)=0; else Dy(i,j)=1; end end end figure(2); imshow(Dx,[]); figure(3); imshow(Dy,[]); for i=1:height-1 for j=1:width-1 if (P(i,j)<0.5) P(i,j)=0; else P(i,j)=1; end end end figure(4); imshow(P,[]);
دانلود کد فوق از طریق لینک زیر:
Prewitt In Matlab
رمز فایل : behsanandish.com
5- الگوریتم Zerocross
این الگوریتم قسمت هایی از لاپلاس یک تصویر را جستجو می کند که مقدار لاپلاس از صفر می گذرد. به عبارت دیگر نقاطی که لاپلاس علامت را تغییر می دهد.
[BW,threshOut] =edge(graypic,'zerocross') [BW,threshOut] = edge(graypic,'zerocross',sensitive,filter name);
دانلود کد فوق از طریق لینک زیر:
Zerocross In Matlab
رمز فایل : behsanandish.com
6- الگوریتم LOG)Laplacian of gaussian)
لاپلاس یک اندازه گیری ایزوتروپیک دوبعدی از مشتق فضایی مرتبه دوم از یک تصویر است. لاپلاس یک تصویر، مناطق تغییرات شدت سریع را نشان می دهد و بنابراین اغلب برای تشخیص لبه استفاده می شود.
clc; clear; close all; warning off; f=imread('siahosefid.bmp'); %f=imnoise(f, 'gaussian', 0, 0.001); k=double(f); figure(1) imshow(f) [m,n]=size(f); k=[zeros(m,1) k zeros(m,1)]; k=[zeros(1,n+2);k;zeros(1,n+2)]; T=30; for i=2:m+1 for j=2:n+1 g(i,j)=k(i-1,j)+k(i+1,j)+k(i,j-1)+k(i,j+1)-4*k(i,j); end end for i=2:m+1 for j=2:n+1 if g(i,j)<T g(i,j)=0; else g(i,j)=1; end end end figure(2) imshow(g)
دانلود کد فوق از طریق لینک زیر:
LOG In Matlab
رمز فایل : behsanandish.com
الگوریتم sobel
کد الگوریتم سوبل – متد سوبل لبه ها را با استفاده از تخمین زدن مشتق پیدا می کند، که لبه ها را در آن نقاطی بر می گرداند که گرادیان تصویر I ، max است. پیشنهاد می کنیم جهت آشنایی با الگوریتم های لبه یابی، مطلب «الگوریتم های لبه یابی و انواع آن» را مشاهده نمایید. در فیلتر سوبل دو ماسک به صورت زیر وجود دارد:
ماسک سوبل افقی بیشتر لبه هاي افقی را مشخص میکند و ماسک سوبل عمودي،لبه هاي عمودي را مشخص میکند.
براي مشخص شدن کلیه لبه ها:
اگر Gx و Gy تصاویر فیلتر شده به وسیله ماسک افقی و عمودي باشند، آنگاه تصویر لبه هاي تصویر را بهتر نشان میدهد. روال فوق به عملگر یا الگورریتم سوبل موسوم است.
در عمل، به منظور کاهش هزینه محاسبات، به جاي میتوان از تقریب [Gx] + [Gy] استفاده میشود. توجه شود که نتیجه این دو فرمول تقریبا یکسان است ولی فرمول دوم با هزینه کمتري قابل محاسبه می باشد.
کد الگوریتم سوبل( Sobel ) در Matlab:
clc; clear; close all; warning off; I=imread('lena.bmp'); I=im2double(I); I=imnoise(I, 'gaussian', 0, 0.001); figure(1); imshow(I);title('org img'); [height width R]=size(I); for i=2:height-1 for j=2:width-1 Dx(i,j)=[I(i+1,j-1)-I(i-1,j-1)]+2*[I(i+1,j)-I(i-1,j)]+[I(i+1,j+1)-I(i-1,j+1)]; Dy(i,j)=[I(i-1,j+1)-I(i-1,j-1)]+2*[I(i,j+1)-I(i,j-1)]+[I(i+1,j+1)-I(i+1,j-1)]; S(i,j)=sqrt(Dx(i,j)^2+Dy(i,j)^2); if Dx(i,j)&lt;1 Dx(i,j)=0; else Dx(i,j)=1; end if Dy(i,j)&lt;1 Dy(i,j)=0; else Dy(i,j)=1; end end end figure(2); imshow(Dx,[]); figure(3); imshow(Dy,[]); for i=1:255 for j=1:255 if (S(i,j)&lt;1) S(i,j)=0; else S(i,j)=1; end end end figure(4); imshow(S,[]);
الگوریتم سوبل به زبان متلب
رمز فایل : behsanandish.com
کد الگوریتم سوبل( Sobel ) در #C:
1.کد برای فیلتر کانولوشن: بخش اول این تابع برای گرفتن اطلاعات تصویر و ذخیره آن به آرایه اختصاص داده شده است.
private static Bitmap ConvolutionFilter(Bitmap sourceImage, double[,] xkernel, double[,] ykernel, double factor = 1, int bias = 0, bool grayscale = false) { //Image dimensions stored in variables for convenience int width = sourceImage.Width; int height = sourceImage.Height; //Lock source image bits into system memory BitmapData srcData = sourceImage.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); //Get the total number of bytes in your image - 32 bytes per pixel x image width x image height -&gt; for 32bpp images int bytes = srcData.Stride * srcData.Height; //Create byte arrays to hold pixel information of your image byte[] pixelBuffer = new byte[bytes]; byte[] resultBuffer = new byte[bytes]; //Get the address of the first pixel data IntPtr srcScan0 = srcData.Scan0; //Copy image data to one of the byte arrays Marshal.Copy(srcScan0, pixelBuffer, 0, bytes); //Unlock bits from system memory -&gt; we have all our needed info in the array sourceImage.UnlockBits(srcData);
2.کد تبدیل سیاه و سفید: از آنجایی که اپراتور Sobel اغلب برای تصاویر سیاه و سفید استفاده می شود، در اینجا یک کد برای تبدیل به سیاه و سفید است که توسط پارامتر boolean شما می توانید انتخاب کنید تبدیل کردن را یا نه.
//Convert your image to grayscale if necessary if (grayscale == true) { float rgb = 0; for (int i = 0; i &lt; pixelBuffer.Length; i += 4) { rgb = pixelBuffer[i] * .21f; rgb += pixelBuffer[i + 1] * .71f; rgb += pixelBuffer[i + 2] * .071f; pixelBuffer[i] = (byte)rgb; pixelBuffer[i + 1] = pixelBuffer[i]; pixelBuffer[i + 2] = pixelBuffer[i]; pixelBuffer[i + 3] = 255; } }
3.کد برای تنظیم متغیرهای مورد استفاده در فرآیند کانولوشن:
/Create variable for pixel data for each kernel double xr = 0.0; double xg = 0.0; double xb = 0.0; double yr = 0.0; double yg = 0.0; double yb = 0.0; double rt = 0.0; double gt = 0.0; double bt = 0.0; //This is how much your center pixel is offset from the border of your kernel //Sobel is 3x3, so center is 1 pixel from the kernel border int filterOffset = 1; int calcOffset = 0; int byteOffset = 0; //Start with the pixel that is offset 1 from top and 1 from the left side //this is so entire kernel is on your image for (int OffsetY = filterOffset; OffsetY &lt; height - filterOffset; OffsetY++) { for (int OffsetX = filterOffset; OffsetX &lt; width - filterOffset; OffsetX++) { //reset rgb values to 0 xr = xg = xb = yr = yg = yb = 0; rt = gt = bt = 0.0; //position of the kernel center pixel byteOffset = OffsetY * srcData.Stride + OffsetX * 4;
4. اعمال کانولوشن هسته به پیکسل فعلی:
//kernel calculations for (int filterY = -filterOffset; filterY &lt;= filterOffset; filterY++) { for (int filterX = -filterOffset; filterX &lt;= filterOffset; filterX++) { calcOffset = byteOffset + filterX * 4 + filterY * srcData.Stride; xb += (double)(pixelBuffer[calcOffset]) * xkernel[filterY + filterOffset, filterX + filterOffset]; xg += (double)(pixelBuffer[calcOffset + 1]) * xkernel[filterY + filterOffset, filterX + filterOffset]; xr += (double)(pixelBuffer[calcOffset + 2]) * xkernel[filterY + filterOffset, filterX + filterOffset]; yb += (double)(pixelBuffer[calcOffset]) * ykernel[filterY + filterOffset, filterX + filterOffset]; yg += (double)(pixelBuffer[calcOffset + 1]) * ykernel[filterY + filterOffset, filterX + filterOffset]; yr += (double)(pixelBuffer[calcOffset + 2]) * ykernel[filterY + filterOffset, filterX + filterOffset]; } } //total rgb values for this pixel bt = Math.Sqrt((xb * xb) + (yb * yb)); gt = Math.Sqrt((xg * xg) + (yg * yg)); rt = Math.Sqrt((xr * xr) + (yr * yr)); //set limits, bytes can hold values from 0 up to 255; if (bt &gt; 255) bt = 255; else if (bt &lt; 0) bt = 0; if (gt &gt; 255) gt = 255; else if (gt &lt; 0) gt = 0; if (rt &gt; 255) rt = 255; else if (rt &lt; 0) rt = 0; //set new data in the other byte array for your image data resultBuffer[byteOffset] = (byte)(bt); resultBuffer[byteOffset + 1] = (byte)(gt); resultBuffer[byteOffset + 2] = (byte)(rt); resultBuffer[byteOffset + 3] = 255; } }
5. کد خروجی تصویر پردازش شده:
//Create new bitmap which will hold the processed data Bitmap resultImage = new Bitmap(width, height); //Lock bits into system memory BitmapData resultData = resultImage.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb); //Copy from byte array that holds processed data to bitmap Marshal.Copy(resultBuffer, 0, resultData.Scan0, resultBuffer.Length); //Unlock bits from system memory resultImage.UnlockBits(resultData); //Return processed image return resultImage; }
6. کد برای هسته سوبل:
//Sobel operator kernel for horizontal pixel changes private static double[,] xSobel { get { return new double[,] { { -1, 0, 1 }, { -2, 0, 2 }, { -1, 0, 1 } }; } } //Sobel operator kernel for vertical pixel changes private static double[,] ySobel { get { return new double[,] { { 1, 2, 1 }, { 0, 0, 0 }, { -1, -2, -1 } }; } }
همه این کد در اینجا موجود است (پروژه با ویژوال استودیو 2015 ایجاد شد):
رمز فایل : behsanandish.com
کد الگوریتم سوبل( Sobel ) در ++C:
در ادامه دو کد برای الگوریتم Sobel در ++C آماده کردیم:
1.
#include<iostream> #include<cmath> #include<opencv2/imgproc/imgproc.hpp> #include<opencv2/highgui/highgui.hpp> using namespace std; using namespace cv; // Computes the x component of the gradient vector // at a given point in a image. // returns gradient in the x direction int xGradient(Mat image, int x, int y) { return image.at<uchar>(y-1, x-1) + 2*image.at<uchar>(y, x-1) + image.at<uchar>(y+1, x-1) - image.at<uchar>(y-1, x+1) - 2*image.at<uchar>(y, x+1) - image.at<uchar>(y+1, x+1); } // Computes the y component of the gradient vector // at a given point in a image // returns gradient in the y direction int yGradient(Mat image, int x, int y) { return image.at<uchar>(y-1, x-1) + 2*image.at<uchar>(y-1, x) + image.at<uchar>(y-1, x+1) - image.at<uchar>(y+1, x-1) - 2*image.at<uchar>(y+1, x) - image.at<uchar>(y+1, x+1); } int main() { Mat src, dst; int gx, gy, sum; // Load an image src = imread("lena.jpg", CV_LOAD_IMAGE_GRAYSCALE); dst = src.clone(); if( !src.data ) { return -1; } for(int y = 0; y < src.rows; y++) for(int x = 0; x < src.cols; x++) dst.at<uchar>(y,x) = 0.0; for(int y = 1; y < src.rows - 1; y++){ for(int x = 1; x < src.cols - 1; x++){ gx = xGradient(src, x, y); gy = yGradient(src, x, y); sum = abs(gx) + abs(gy); sum = sum > 255 ? 255:sum; sum = sum < 0 ? 0 : sum; dst.at<uchar>(y,x) = sum; } } namedWindow("final"); imshow("final", dst); namedWindow("initial"); imshow("initial", src); waitKey(); return 0; }
Sobel in C++-Code1
رمز فایل : behsanandish.com
2.
#include "itkImage.h" #include "itkImageFileReader.h" #include "itkImageFileWriter.h" #include "itkSobelEdgeDetectionImageFilter.h" int main( int argc, char* argv[] ) { if( argc != 3 ) { std::cerr << "Usage: "<< std::endl; std::cerr << argv[0]; std::cerr << "<InputFileName> <OutputFileName>"; std::cerr << std::endl; return EXIT_FAILURE; } constexpr unsigned int Dimension = 2; using InputPixelType = unsigned char; using InputImageType = itk::Image< InputPixelType, Dimension >; using ReaderType = itk::ImageFileReader< InputImageType >; ReaderType::Pointer reader = ReaderType::New(); reader->SetFileName( argv[1] ); using OutputPixelType = float; using OutputImageType = itk::Image< OutputPixelType, Dimension >; using FilterType = itk::SobelEdgeDetectionImageFilter< InputImageType, OutputImageType >; FilterType::Pointer filter = FilterType::New(); filter->SetInput( reader->GetOutput() ); using WriterType = itk::ImageFileWriter< OutputImageType >; WriterType::Pointer writer = WriterType::New(); writer->SetFileName( argv[2] ); writer->SetInput( filter->GetOutput() ); try { writer->Update(); } catch( itk::ExceptionObject & error ) { std::cerr << "Error: " << error << std::endl; return EXIT_FAILURE; } return EXIT_SUCCESS; }
Sobel in C++-Code2
رمز فایل : behsanandish.com
کد الگوریتم سوبل( Sobel ) در C:
/* sobel.c */ #include <stdio.h> #include <stdlib.h> #include <float.h> #include "mypgm.h" void sobel_filtering( ) /* Spatial filtering of image data */ /* Sobel filter (horizontal differentiation */ /* Input: image1[y][x] ---- Outout: image2[y][x] */ { /* Definition of Sobel filter in horizontal direction */ int weight[3][3] = {{ -1, 0, 1 }, { -2, 0, 2 }, { -1, 0, 1 }}; double pixel_value; double min, max; int x, y, i, j; /* Loop variable */ /* Maximum values calculation after filtering*/ printf("Now, filtering of input image is performed\n\n"); min = DBL_MAX; max = -DBL_MAX; for (y = 1; y < y_size1 - 1; y++) { for (x = 1; x < x_size1 - 1; x++) { pixel_value = 0.0; for (j = -1; j <= 1; j++) { for (i = -1; i <= 1; i++) { pixel_value += weight[j + 1][i + 1] * image1[y + j][x + i]; } } if (pixel_value < min) min = pixel_value; if (pixel_value > max) max = pixel_value; } } if ((int)(max - min) == 0) { printf("Nothing exists!!!\n\n"); exit(1); } /* Initialization of image2[y][x] */ x_size2 = x_size1; y_size2 = y_size1; for (y = 0; y < y_size2; y++) { for (x = 0; x < x_size2; x++) { image2[y][x] = 0; } } /* Generation of image2 after linear transformtion */ for (y = 1; y < y_size1 - 1; y++) { for (x = 1; x < x_size1 - 1; x++) { pixel_value = 0.0; for (j = -1; j <= 1; j++) { for (i = -1; i <= 1; i++) { pixel_value += weight[j + 1][i + 1] * image1[y + j][x + i]; } } pixel_value = MAX_BRIGHTNESS * (pixel_value - min) / (max - min); image2[y][x] = (unsigned char)pixel_value; } } } main( ) { load_image_data( ); /* Input of image1 */ sobel_filtering( ); /* Sobel filter is applied to image1 */ save_image_data( ); /* Output of image2 */ return 0; }
کد الگوریتم سوبل( Sobel ) در Visual Basic:
Private Sub bEdge_Click(sender As Object, e As EventArgs) _ Handles bEdge.Click 'Sobel Edge' Dim tmpImage As Bitmap = New Bitmap(picOriginal.Image) Dim bmpImage As Bitmap = New Bitmap(picOriginal.Image) Dim intWidth As Integer = tmpImage.Width Dim intHeight As Integer = tmpImage.Height Dim intOldX As Integer(,) = New Integer(,) {{-1, 0, 1}, _ {-2, 0, 2}, {-1, 0, 1}} Dim intOldY As Integer(,) = New Integer(,) {{1, 2, 1}, _ {0, 0, 0}, {-1, -2, -1}} Dim intR As Integer(,) = New Integer(intWidth - 1, _ intHeight - 1) {} Dim intG As Integer(,) = New Integer(intWidth - 1, _ intHeight - 1) {} Dim intB As Integer(,) = New Integer(intWidth - 1, _ intHeight - 1) {} Dim intMax As Integer = 128 * 128 For i As Integer = 0 To intWidth - 1 For j As Integer = 0 To intHeight - 1 intR(i, j) = tmpImage.GetPixel(i, j).R intG(i, j) = tmpImage.GetPixel(i, j).G intB(i, j) = tmpImage.GetPixel(i, j).B Next Next Dim intRX As Integer = 0 Dim intRY As Integer = 0 Dim intGX As Integer = 0 Dim intGY As Integer = 0 Dim intBX As Integer = 0 Dim intBY As Integer = 0 Dim intRTot As Integer Dim intGTot As Integer Dim intBTot As Integer For i As Integer = 1 To tmpImage.Width - 1 - 1 For j As Integer = 1 To tmpImage.Height - 1 - 1 intRX = 0 intRY = 0 intGX = 0 intGY = 0 intBX = 0 intBY = 0 intRTot = 0 intGTot = 0 intBTot = 0 For width As Integer = -1 To 2 - 1 For height As Integer = -1 To 2 - 1 intRTot = intR(i + height, j + width) intRX += intOldX(width + 1, height + 1) * intRTot intRY += intOldY(width + 1, height + 1) * intRTot intGTot = intG(i + height, j + width) intGX += intOldX(width + 1, height + 1) * intGTot intGY += intOldY(width + 1, height + 1) * intGTot intBTot = intB(i + height, j + width) intBX += intOldX(width + 1, height + 1) * intBTot intBY += intOldY(width + 1, height + 1) * intBTot Next Next If intRX * intRX + intRY * intRY > intMax OrElse intGX * intGX + intGY * intGY > intMax OrElse intBX * intBX + intBY * intBY > intMax Then bmpImage.SetPixel(i, j, Color.Black) Else bmpImage.SetPixel(i, j, Color.Transparent) End If Next Next picModified.Image = bmpImage End Sub
پیشنهاد می کنیم جهت آشنایی با الگوریتم های لبه یابی، مطلب «الگوریتم های لبه یابی و انواع آن» را مشاهده نمایید.
اصلاح اثر مخرب مژهها بر تصاویر عنبیه به کمک فیلتر میانه با قاب افقی
تشخیص عنبیه : چکيده – یکی از مشکلات موجود در سیستمهای تشخیص هویت به کمک الگوهای عنبیه، مسدود شدن عنبیهی چشم بوسیلهی مژههاست. با توجه به ماهیت مژهها که بصورت خطوطی عمودی با اختلاف رنگ زیاد نسبت به عنبیه هستند، میتوان با استفاده از فیلتر میانه با قاب افقی آنها را حذف نمود. به منظور تطابق دو تصویر ما از شبکههای عصبی استفاده مینماییم. در روش مورد استفاده ما تصویر نرمال شدهی عنبیه را بلاک بندی میکنیم و به شبکه عصبی میدهیم. به دلیل اینکه از بلاکهای افقی به منظور بلاک بندی تصویر استفاده مینماییم، استفاده از قابهای افقی در فیلتر میانه به منظور حذف مژهها نتیجهی خوبی را به همراه خواهد داشت.
كليد واژه- تشخیص عنبیه ، حذف مژه ، شبکه عصبی ، فیلتر میانه با قاب افقی
1- مقدمه
شکلگیری ساختار منحصربهفرد عنبیه به صورت تصادفی رخ میدهد و به عوامل ژنتیکی بستگی ندارد و فقط رنگدانههای عنبیه به عوامل ژنتیکی بستگی دارند و در طول زمان تغییر میکنند، که همین امر عنبیه را به عنوان یک عنصر مهم در تعیین هویت تبدیل کرده است.
مراحل انجام عمل تشخیص هویت به کمک عنبیه شامل موارد زیر است: (شکل 1)
- تصویربرداری
- قطعهبندی
- نرمال سازی
- استخراج ویژگی
- تطابق
یکی از نویزهای شایع در تصاویر عنبیه، که از دقت تشخیص عنبیه میکاهد، نویز ناشی از مژهها میباشد. در پارهای از تصویر عنبیهها که توسط پلکها مسدود شدهاند ، نویز ناشی از مژهها نیز مشاهده میگردد که با تکنیکهایی میتوان پلکها را شناسایی کرده و اثر آنها را در سیستم تشخیص عنبیه خنثی نمود. ابعاد،تعداد و پراکندگی متفاوتی که مژهها دارند، از دشواریهای شناسایی آنها است.
نویسندگان : محمد مهدی ابراهیمی، ناصر قاسم آقایی و حسین ابراهیم پور
تعداد صفحات : 6 صفحه
سال انتشار: 1392
پسورد : behsanandish.com
دانلود رایگان : مقاله-اصلاح اثر مخرب مژهها بر تصاویر عنبیه به کمک فیلتر میانه با قاب افقی
تلفن های تماس:
تلفن: ۹۱۰۰۱۸۸۱(۰۳۱)
بازرگانی و فروش:۰۹۱۳۶۵۳۱۸۸۱
پشتیبانی: ۰۹۱۱۷۶۱۰۲۷۵
ساعات کاری
از شنبه تا چهارشنبه : ۰۹:۰۰ تا ۱۷:۰۰
پنچ شنبه ها : از ۰۹:۰۰ تا ۱۳:۳۰