کار با Thread ها در زبان سی شارپ :: آشنایی با کلاس Timer

خیلی وقت ها در برنامه ها نیاز است که کد ما در بازه های زمانی مشخص اجرا شود، برای مثال کدی که باید هر ۵ ثانیه یا هر یک دقیقه یکبار اجرا شده و عملیات خاصی را انجام دهد، مانند نمایش تاریخ و ساعت در برنامه و یا بررسی ایمیل ها و مطلع کردن کاربر از ایمیل های جدید. برای شرایطی از این قبیل می توانیم از کلاس Timer که در فضای نام System.Threading قرار گرفته و وابسته به delegate ای با نام TimerCallback است استفاده کنیم. برای آشنایی بیشتر با این کلاس برنامه ای در محیط کنسول می نویسیم که زمان جاری را تا زمانی که کاربر کلیدی را فشار دهد بر روی خروجی نمایش می دهد. ابتدا یک متد با نام PrintTime به صورت زیر ایجاد می کنیم:

public static void PrintTime(object state)
{
    Console.WriteLine(DateTime.Now.ToString("hh:mm:ss"));
}

کار این متد نمایش تاریخ به صورت ساعت:دقیقه:ثانیه می باشد. دقت کنید که این متد یک پارامتر از نوع object و با نام state میگیرد. دلیل وجود این پارامتر به این خاطر است که TimerCallback که delegate مورد استفاده در کلاس Timer است متدی با این Signature قبول می کند. در قدم بعدی باید یک شئ از کلاس Timer ایجاد کنیم. علاوه بر اینکه باید TimerCallback را نیز ایجاد کنیم، کلاس Timer به عنوان سازنده به ما این اجازه را می دهد تا پارامترهای مورد نیاز برای آماده سازی Timer را به آن ارسال کنیم، مانند بازه زمانی که کد مورد نظر باید اجرا شود، شئ ای که به عنوان state در زمان فراخوانی متد مورد نظر به آن ارسال می شود و مدت زمان مکس قبل از اولین فراخوانی متد مشخص شده برای Timer. کد زیر را در متد Main می نویسیم:

static void Main(string[] args)
{
    TimerCallback callback = new TimerCallback(PrintTime);

    Timer timer = new Timer(callback, null, 0, 1000);
    Console.WriteLine("Press any key to terminate application...");
    Console.ReadKey();
}

لیست پارامترها

همانطور که مشاهده می کنید ابتدا شئ ای از روی TimerCallback ایجاد کرده، در قدم بعدی کلاس Timer را به همراه پارامتر های مورد نیاز ایجاد کردیم. پارامتر ها به ترتیب:

  1. شئ ای از نوع TimerCallback
  2. شئ ای که برای state استفاده می شود که در کد بالا مقدار null ارسال شده است
  3. مقدار زمان مکس قبل از اولین فراخوانی متد مشخص شده برای Timer
  4. بازه های زمانی برای اجرای متد PrintTime که در اینجا ۱۰۰۰ میلی ثانیه یا ۱ ثانیه یکبار است.

با اجرای کد بالا و تا زمانی که کلیدی را فشار ندهیم زمان جاری بر روی خروجی نمایش داده می شود:

Press any key to terminate application...
۰۶:۴۰:۰۶
۰۶:۴۰:۰۷
۰۶:۴۰:۰۸
۰۶:۴۰:۰۹
۰۶:۴۰:۱۰
۰۶:۴۰:۱۱

برای مشخص کردن state پارامتر دوم را می توانیم تغییر دهیم، برای نمونه کد PrintNumbers را به صورت زیر تغییر می دهیم:

public static void PrintTime(object state)
{
    Console.WriteLine(DateTime.Now.ToString("hh:mm:ss") + " {0}", state);
}

و سپس شئ مورد نظر را از متد Main به صورت زیر به سازنده Timer به عنوان پارامتر دوم ارسال می کنیم:

Timer timer = new Timer(callback, "State from Main Method!", 0, 1000);

با اجرای کد بالا خروجی به صورت زیر خواهد بود:

Press any key to terminate application...
۰۶:۴۲:۱۰ State from Main Method!
۰۶:۴۲:۱۱ State from Main Method!
۰۶:۴۲:۱۲ State from Main Method!
۰۶:۴۲:۱۳ State from Main Method!
۰۶:۴۲:۱۴ State from Main Method!
۰۶:۴۲:۱۵ State from Main Method!

از سری مباحث کار با Thread ها موضوع CLR ThreadPool باقی مانده که در قسمت بعد در مورد آن صحبت کرده و پس از آن کار با Task Parallel Library را شروع خواهیم کرد.

منبع


قسمت اول آموزش-برنامه نویسی 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 را به صورت زیر تغییر می دهیم تا ۱۰ 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,
۷,۳,۲,۲,۹,
۲,۱,۸,۳,۳,۳,۴,۲,۱,۹,
۴,۴,۴,۵,۳,۳,۵,۵,۵,۶,۲,۶,۶,۷,۶,۴,۴,۷,۷,۸,۹,
۸,۹,
۷,۸,۹,
۵,۵,۳,۸,۶,۷,۸,۹,
۶,۷,۸,۹,
۴,۵,۹,
۶,۷,۸,۹,

اگر برنامه را مجدد اجرا کنید خروجی متفاوتی از خروجی قبلی دریافت خواهیم کرد:

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,
۶,۷,۸,۹,
۱,۱,۰,۰,۱,۱,۱,۱,۱,۱,۲,۲,۲,۲,۲,۲,۲,۲,۳,۳,۳,۴,۵,۶,۷,۸,۹,
۳,۳,۳,۳,۳,۴,۴,۴,۴,۵,۶,۷,۸,۹,
۵,۶,۷,۸,۹,
۵,۶,۷,۵,۶,۷,۸,۹,
۸,۴,۴,۴,۵,۵,۶,۶,۷,۷,۹,
۵,۸,۸,۶,۹,
۹,
۷,۸,۹,

همانطور که مشاهده می کنید خروجی های ایجاد کاملاً با یکدیگر متفاوت هستند. مشخص است که در اینجا مشکلی وجود دارد و همانطور که در ابتدا گفتیم این مشکل وجود همزمانی یا 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 برابر ۱۰ باشد، مقدار آن با ۱۵ عوض خواهد شد.

پیاده سازی 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

لبه یاب کنی توسط جان اف کنی در سال ۱۹۸۶ ایجاد شد و هنوز یک لبه یاب استاندارد و با دقت و کیفیت بالا میباشد.الگوریتم لبه یابی کنی یکی از بهترین لبه یابها تا به امروز است. در ادامه روش کار این الگوریتم و هم چنین کد الگوریتم Canny در C را بررسی خواهیم کرد. این الگوریتم لبه یابی از سه بخش اصلی زیر تشکیل شده است:

  • تضعیف نویز
  • پیدا کردن نقاطی که بتوان آنها را به عنوان لبه در نظر گرفت
  • جذب نقاطی که احتمال لبه بودن آنها کم است

 

معیارهایی که در لبه یاب کنی مطرح می باشد:
۱ -پایین آوردن نرخ خطا- یعنی تا حد امکان هیچ لبه ای در تصویر نباید گم شود و هم چنین هیچ چیزی که لبه نیست نباید به جای لبه فرض شود. لبه هان پیدا شده تا حد ممکن به لبه ها اصلی
نزدیک باشند.

۲ -لبه در مکان واقعی خود باشد- یعنی تا حد ممکن لبه ها کمترین فاصله را با مکان واقعی خود داشته باشند.
۳ -بران هر لبه فقط یک پاسخ داشته باشیم.

۴ -لبه ها کمترین ضخامت را داشته باشند- (در صورت امکان یک پیکسل).
لبه یاب کنی بخاطر توانایی در تولید لبه های نازک تا حد یک ییکسل برای لبه های پیوسته معروف شده است. این لبه یاب شامل چهار مرحله و چهار ورودی زیر است:
یک تصویر ورودی
یک پارامتر به نام سیگما جهت مقدار نرم کنندگی تصویر
یک حد آستانه بالا (Th)
یک حد آستانه پایین (Tl)

 

مراحل الگوریتم Canny:

۱- در ابتدا باید تصویر رنگی را به جهت لبه یابی بهتر به یک تصویر سطح خاکسترن تبدیب کرد.

۲- نویز را از تصویر دریافتی حذف کرد. بدلیل اینکه فیلتر گاوسین از یک ماسک ساده برای حذف نویز استفاده می کند لبه یاب کنی در مرحله اول برای حذف نویز آن را بکار می گیرد.

۳- در یک تصویر سطح خاکستر جایی را که بیشترین تغییرات را داشته باشند به عنوان لبه در نظر گرفته می شوند و این مکانها با گرفتن گرادیان تصویر با استفاده عملگر سوبل بدست می آیند. سپس لبه های مات یافت شده به لبه های تیزتر تبدیل می شوند.

۴- برخی از لبه های کشف شده واقعا لبه نیستند و در واقع نویز هستند که باید آنها توسط حد آستانه هیسترزیس فیلتر شوند.هیسترزیس از دو حد آستانه بالاتر (Th) و حد آستانه پایین تر (Tl) استفاده کرده و کنی پیشنهاد می کند که نسبت استانه بالا به پایین سه به یک باشد.

 این روش بیشتر به کشف لبه های ضعیف به درستی می پردازد و کمتر فریب نویز را می خورد و از بقیه روش ها بهتر است.

 

الگوریتم Canny    عملکرد الگوریتم Canny

 

 

کد الگوریتم Canny در C :

برنامه زیر یک فایل BMP سیاه و سفید ۸ بیت در هر پیکسل را می خواند و نتیجه را در ‘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),
 ۱, filePtr) != 1) {
 fclose(filePtr);
 return NULL;
 }
 
 // read the bitmap info header
 if (fread(bitmapInfoHeader, sizeof(bitmap_info_header_t),
 ۱, 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) +
 ((۱U << 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 #)
 * ۰٫۰ <= sigma < 0.5 : 3
 * ۰٫۵ <= sigma < 1.0 : 5
 * ۱٫۰ <= sigma < 1.5 : 7
 * ۱٫۵ <= sigma < 2.0 : 9
 * ۲٫۰ <= sigma < 2.5 : 11
 * ۲٫۵ <= 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)))
 / (۲ * 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,
 -۲, ۰, ۲,
 -۱, ۰, ۱};
 
 convolution(out, after_Gx, Gx, nx, ny, 3, false);
 
 const float Gy[] = { 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

لبه یاب کنی توسط جان اف کنی در سال ۱۹۸۶ ایجاد شد و هنوز یک لبه یاب استاندارد و با دقت و کیفیت بالا میباشد.الگوریتم لبه یابی کنی یکی از بهترین لبه یابها تا به امروز است. در ادامه روش کار این الگوریتم و هم چنین کد الگوریتم Canny در OpenCV را بررسی خواهیم کرد. این الگوریتم لبه یابی از سه بخش اصلی زیر تشکیل شده:

  • تضعیف نویز
  • پیدا کردن نقاطی که بتوان آنها را به عنوان لبه در نظر گرفت
  • حذب نقاطی که احتمال لبه بودن آنها کم است

 

معیارهایی که در لبه یاب کنی مطرح است:
۱ -پایین آوردن نرخ خطا- یعنی تا حد امکان هیچ لبه ای در تصویر نباید گم شود و هم چنین هیچ چیزی که لبه نیست نباید به جای لبه فرض شود. لبه هان پیدا شده تا حد ممکن به لبه ها اصلی
نزدیک باشند.

۲ -لبه در مکان واقعی خود باشد- یعنی تا حد ممکن لبه ها کمترین فاصله را با مکان واقعی خود داشته باشند.
۳ -بران هر لبه فقط یک پاسخ داشته باشیم.

۴ -لبه ها کمترین ضخامت را داشته باشند- (در صورت امکان یک پیکسل).
لبه یاب کنی بخاطر توانایی در تولید لبه های نازک تا حد یک ییکسل برای لبه های پیوسته معروف شده است. این لبه یاب شامل چهار مرحله و چهار ورودی زیر است:
یک تصویر ورودی
یک پارامتر به نام سیگما جهت مقدار نرم کنندگی تصویر
یک حد آستانه بالا (Th)
یک حد آستانه پایین (Tl)

 

مراحل الگوریتم Canny:

۱- در ابتدا باید تصویر رنگی را به جهت لبه یابی بهتر به یک تصویر سطح خاکسترن تبدیب کرد.

۲- نویز را از تصویر دریافتی حذف کرد. بدلیل اینکه فیلتر گاوسین از یک ماسک ساده برای حذف نویز استفاده می کند لبه یاب کنی در مرحله اول برای حذف نویز آن را بکار میگیرد.

۳- در یک تصویر سطح خاکستر جایی را که بیشترین تغییرات را داشته باشند به عنوان لبه در نظر گرفته می شوند و این مکانها با گرفتن گرادیان تصویر با استفاده عملگر سوبل بدست می آیند. سپس لبه های مات یافت شده به لبه های تیزتر تبدیل می شوند.

۴- برخی از لبه های کشف شده واقعا لبه نیستند و در واقع نویز هستند که باید آنها توسط حد آستانه هیسترزیس فیلتر شوند.هیسترزیس از دو حد آستانه بالاتر (Th) و حد آستانه پایین تر (Tl) استفاده کرده و کنی پیشنهاد می کند که نسبت استانه بالا به پایین سه به یک باشد.

 این روش بیشتر به کشف لبه های ضعیف به درستی می پردازد و کمتر فریب نویز را می خورد و از بقیه روش ها بهتر است.

 

 

الگوریتم Canny    عملکرد الگوریتم Canny

 

کد الگوریتم Canny در OpenCV:

 

#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <stdlib.h>
#include <stdio.h>

using namespace cv;

/// Global variables

Mat src, src_gray;
Mat dst, detected_edges;

int edgeThresh = 1;
int lowThreshold;
int const max_lowThreshold = 100;
int ratio = 3;
int kernel_size = 3;
char* window_name = "Edge Map";

/**
 * @function CannyThreshold
 * @brief Trackbar callback - Canny thresholds input with a ratio 1:3
 */
void CannyThreshold(int, void*)
{
  /// Reduce noise with a kernel 3x3
  blur( src_gray, detected_edges, Size(3,3) );

  /// Canny detector
  Canny( detected_edges, detected_edges, lowThreshold, lowThreshold*ratio, kernel_size );

  /// Using Canny's output as a mask, we display our result
  dst = Scalar::all(0);

  src.copyTo( dst, detected_edges);
  imshow( window_name, dst );
 }

/** @function main */
int main( int argc, char** argv )
{
  /// Load an image
  src = imread( argv[1] );

  if( !src.data )
  { return -1; }</pre>
<pre>  /// Create a matrix of the same type and size as src (for dst)
  dst.create( src.size(), src.type() );

  /// Convert the image to grayscale
  cvtColor( src, src_gray, CV_BGR2GRAY );

  /// Create a window
  namedWindow( window_name, CV_WINDOW_AUTOSIZE );

  /// Create a Trackbar for user to enter threshold
  createTrackbar( "Min Threshold:", window_name, &lowThreshold, max_lowThreshold, CannyThreshold );

  /// Show the image
  CannyThreshold(0, 0);

  /// Wait until user exit program by pressing a key
  waitKey(0);

  return 0;
  }

 

 

دانلود کد فوق از طریق لینک زیر:

CannyInOpenCV

رمز فایل : behsanandish.com

 

الگوریتم Canny

لبه یاب کنی توسط جان اف کنی در سال ۱۹۸۶ ایجاد شد و هنوز یک لبه یاب استاندارد و با دقت و کیفیت بالا میباشد.الگوریتم لبه یابی کنی یکی از بهترین لبه یابها تا به امروز است. در ادامه روش کار این الگوریتم و هم چنین کد الگوریتم Canny در #C را بررسی خواهیم کرد. این الگوریتم لبه یابی از سه بخش اصلی زیر تشکیل شده:

  • تضعیف نویز
  • پیدا کردن نقاطی که بتوان آنها را به عنوان لبه در نظر گرفت
  • حذب نقاطی که احتمال لبه بودن آنها کم است

 

معیارهایی که در لبه یاب کنی مطرح است:
۱ -پایین آوردن نرخ خطا- یعنی تا حد امکان هیچ لبه ای در تصویر نباید گم شود و هم چنین هیچ چیزی که لبه نیست نباید به جای لبه فرض شود. لبه هان پیدا شده تا حد ممکن به لبه ها اصلی
نزدیک باشند.

۲ -لبه در مکان واقعی خود باشد- یعنی تا حد ممکن لبه ها کمترین فاصله را با مکان واقعی خود داشته باشند.
۳ -بران هر لبه فقط یک پاسخ داشته باشیم.

۴ -لبه ها کمترین ضخامت را داشته باشند- (در صورت امکان یک پیکسل).
لبه یاب کنی بخاطر توانایی در تولید لبه های نازک تا حد یک ییکسل برای لبه های پیوسته معروف شده است. این لبه یاب شامل چهار مرحله و چهار ورودی زیر است:
یک تصویر ورودی
یک پارامتر به نام سیگما جهت مقدار نرم کنندگی تصویر
یک حد آستانه بالا (Th)
یک حد آستانه پایین (Tl)

 

مراحل الگوریتم Canny:

۱- در ابتدا باید تصویر رنگی را به جهت لبه یابی بهتر به یک تصویر سطح خاکسترن تبدیب کرد.

۲- نویز را از تصویر دریافتی حذف کرد. بدلیل اینکه فیلتر گاوسین از یک ماسک ساده برای حذف نویز استفاده می کند لبه یاب کنی در مرحله اول برای حذف نویز آن را بکار میگیرد.

۳- در یک تصویر سطح خاکستر جایی را که بیشترین تغییرات را داشته باشند به عنوان لبه در نظر گرفته می شوند و این مکانها با گرفتن گرادیان تصویر با استفاده عملگر سوبل بدست می آیند. سپس لبه های مات یافت شده به لبه های تیزتر تبدیل می شوند.

۴- برخی از لبه های کشف شده واقعا لبه نیستند و در واقع نویز هستند که باید آنها توسط حد آستانه هیسترزیس فیلتر شوند.هیسترزیس از دو حد آستانه بالاتر (Th) و حد آستانه پایین تر (Tl) استفاده کرده و کنی پیشنهاد می کند که نسبت استانه بالا به پایین سه به یک باشد.

 این روش بیشتر به کشف لبه های ضعیف به درستی می پردازد و کمتر فریب نویز را می خورد و از بقیه روش ها بهتر است.

 

الگوریتم Canny    عملکرد الگوریتم Canny

 

 

کد الگوریتم Canny در #C:

الگوریتم در ۵ مرحله جداگانه اجرا می شود:

۱٫ صاف کردن: تار شدن تصویر برای حذف نویز. پیکربندی توسط فیلتر گاوسی با اندازه مشخص هسته (N) و پارامتر پوشش گاوسی سیگما. پوشاننده فیلتر گاوسی توسط تابع زیر تولید می شود:

private void GenerateGaussianKernel(int N, float S ,out int Weight)
{

float Sigma = S ;
float pi;
pi = (float)Math.PI;
int i, j;
int SizeofKernel=N;

float [,] Kernel = new float [N,N];
GaussianKernel = new int [N,N];
float[,] OP = new float[N, N];
float D1,D2;

D1= 1/(2*pi*Sigma*Sigma);
D2= 2*Sigma*Sigma;

float min=1000;

for (i = -SizeofKernel / 2; i <= SizeofKernel / 2; i++)
{
for (j = -SizeofKernel / 2; j <= SizeofKernel / 2; j++)
{
Kernel[SizeofKernel / 2 + i, SizeofKernel / 2 + j] = ((1 / D1) * (float)Math.Exp(-(i * i + j * j) / D2));
if (Kernel[SizeofKernel / 2 + i, SizeofKernel / 2 + j] < min)
min = Kernel[SizeofKernel / 2 + i, SizeofKernel / 2 + j];

}
}
int mult = (int)(1 / min);
int sum = 0;
if ((min > 0) && (min < 1))
{

for (i = -SizeofKernel / 2; i <= SizeofKernel / 2; i++)
{
for (j = -SizeofKernel / 2; j <= SizeofKernel / 2; j++)
{
Kernel[SizeofKernel / 2 + i, SizeofKernel / 2 + j] = (float)Math.Round(Kernel[SizeofKernel / 2 + i, SizeofKernel / 2 + j] * mult, 0);
GaussianKernel[SizeofKernel / 2 + i, SizeofKernel / 2 + j] = (int)Kernel[SizeofKernel / 2 + i, SizeofKernel / 2 + j];
sum = sum + GaussianKernel[SizeofKernel / 2 + i, SizeofKernel / 2 + j];
}

}

}
else
{
sum = 0;
for (i = -SizeofKernel / 2; i <= SizeofKernel / 2; i++)
{
for (j = -SizeofKernel / 2; j <= SizeofKernel / 2; j++)
{
Kernel[SizeofKernel / 2 + i, SizeofKernel / 2 + j] = (float)Math.Round(Kernel[SizeofKernel / 2 + i, SizeofKernel / 2 + j] , 0);
GaussianKernel[SizeofKernel / 2 + i, SizeofKernel / 2 + j] = (int)Kernel[SizeofKernel / 2 + i, SizeofKernel / 2 + j];
sum = sum + GaussianKernel[SizeofKernel / 2 + i, SizeofKernel / 2 + j];
}

}

}
//Normalizing kernel Weight
Weight= sum;

return;
}

 

زیر روال ذیل نویز را توسط فیلتر گوسی حذف می کند.

 

private int[,] GaussianFilter(int[,] Data)
        {
            GenerateGaussianKernel(KernelSize, Sigma,out KernelWeight);

            int[,] Output = new int[Width, Height];
            int i, j,k,l;
            int Limit = KernelSize /2;

            float Sum=0;

 Output = Data; // Removes Unwanted Data Omission due to kernel bias while convolution

            for (i = Limit; i <= ((Width - 1) - Limit); i++)
            {
                for (j = Limit; j <= ((Height - 1) - Limit); j++)
                {
                    Sum = 0;
                    for (k = -Limit; k <= Limit; k++)
                    {

                       for (l = -Limit; l <= Limit; l++)
                        {
                            Sum = Sum + ((float)Data[i + k, j + l] * GaussianKernel [Limit + k, Limit + l]);                        

                        }
                    }
                    Output[i, j] = (int)(Math.Round(Sum/ (float)KernelWeight));
                }

            }

            return Output;
        }

 

۲٫ پیدا کردن شیب ها: لبه ها باید مشخص شوند، جایی که شیب های تصویر بزرگ می شوند.

ماسک های سوبل  X و Y برای تولید گرادیان های تصویر X و Y استفاده می شود؛ تابع بعدی تمایز را با استفاده از فیلتر ماسک sobel اعمال می کند.

 

private float[,] Differentiate(int[,] Data, int[,] Filter)
        {
            int i, j,k,l, Fh, Fw;

            Fw = Filter.GetLength(0);
            Fh = Filter.GetLength(1);
            float sum = 0;
            float[,] Output = new float[Width, Height];

            for (i = Fw / 2; i <= (Width - Fw / 2) - 1; i++)
            {
                for (j = Fh / 2; j <= (Height  - Fh / 2) - 1; j++)
                {
                  sum=0;
                   for(k=-Fw/2; k<=Fw/2; k++)
                   {
                       for(l=-Fh/2; l<=Fh/2; l++)
                       {
                          sum=sum + Data[i+k,j+l]*Filter[Fw/2+k,Fh/2+l];


                       }
                   }
                    Output[i,j]=sum;

                }

            }
            return Output;

        }

 

۳٫ توقیف غیر حداکثر: فقط حداکثرهای محلی باید به عنوان لبه ها مشخص شود.

ما جهت گرادیان را پیدا می کنیم و با استفاده از این جهت، ما توقیف غیر حداکثر را انجام می دهیم (“پردازش تصویر دیجیتال- آموزش توسط گنزالس-پیرسون ” را بخوانید)

 

// Perform Non maximum suppression:
           // NonMax = Gradient;

            for (i = 0; i <= (Width - 1); i++)
            {
                for (j = 0; j <= (Height - 1); j++)
                {
                    NonMax[i, j] = Gradient[i, j];
                }
            }
     
            int Limit = KernelSize / 2;
            int r, c;
            float Tangent;

                for (i = Limit; i <= (Width - Limit) - 1; i++)
            {
                for (j = Limit; j <= (Height - Limit) - 1; j++)
                {

                    if (DerivativeX[i, j] == 0)
                        Tangent = 90F;
                    else
                        Tangent = (float)(Math.Atan(DerivativeY[i, j] / DerivativeX[i, j]) * 180 / Math.PI); //rad to degree



                    //Horizontal Edge
                    if (((-22.5 < Tangent) && (Tangent <= 22.5)) || ((157.5 < Tangent) && (Tangent <= -157.5)))
                    {
                        if ((Gradient[i, j] < Gradient[i, j + 1]) || (Gradient[i, j] < Gradient[i, j - 1]))
                            NonMax[i, j] = 0;
                    }

                    //Vertical Edge
                    if (((-112.5 < Tangent) && (Tangent <= -67.5)) || ((67.5 < Tangent) && (Tangent <= 112.5)))
                    {
                        if ((Gradient[i, j] < Gradient[i + 1, j]) || (Gradient[i, j] < Gradient[i - 1, j]))
                            NonMax[i, j] = 0;
                    }

                    //+۴۵ Degree Edge
                    if (((-67.5 < Tangent) && (Tangent <= -22.5)) || ((112.5 < Tangent) && (Tangent <= 157.5)))
                    {
                        if ((Gradient[i, j] < Gradient[i + 1, j - 1]) || (Gradient[i, j] < Gradient[i - 1, j + 1]))
                            NonMax[i, j] = 0;
                    }

                    //-۴۵ Degree Edge
                    if (((-157.5 < Tangent) && (Tangent <= -112.5)) || ((67.5 < Tangent) && (Tangent <= 22.5)))
                    {
                        if ((Gradient[i, j] < Gradient[i + 1, j + 1]) || (Gradient[i, j] < Gradient[i - 1, j - 1]))
                            NonMax[i, j] = 0;
                    }

                }

            }

 

۴٫ آستانه دوگانه: لبه های بالقوه توسط آستانه تعیین می شود.

۵٫ ردیابی لبه توسط هیسترسیس: لبه های نهایی توسط توقیف تمام لبه هایی که به یک لبه بسیار قطعی (قوی) متصل نیستند، مشخص می شوند.

 

private void HysterisisThresholding(int[,] Edges)
        {

            int i, j;
            int Limit= KernelSize/2;


            for (i = Limit; i <= (Width - 1) - Limit; i++)
                for (j = Limit; j <= (Height - 1) - Limit; j++)
                {
                    if (Edges[i, j] == 1)
                    {
                        EdgeMap[i, j] = 1;

                    }

                }

            for (i = Limit; i <= (Width - 1) - Limit; i++)
            {
                for (j = Limit; j <= (Height  - ۱) - Limit; j++)
                {
                    if (Edges[i, j] == 1)
                    {
                        EdgeMap[i, j] = 1;
                        Travers(i, j);
                        VisitedMap[i, j] = 1;
                    }
                }
            }




            return;
        }

//Recursive Procedure 
private void Travers(int X, int Y)
        {

            
            if (VisitedMap[X, Y] == 1)
            {
                return;
            }

            //۱
            if (EdgePoints[X + 1, Y] == 2)
            {
                EdgeMap[X + 1, Y] = 1;
                VisitedMap[X + 1, Y] = 1;
                Travers(X + 1, Y);
                return;
            }
            //۲
            if (EdgePoints[X + 1, Y - 1] == 2)
            {
                EdgeMap[X + 1, Y - 1] = 1;
                VisitedMap[X + 1, Y - 1] = 1;
                Travers(X + 1, Y - 1);
                return;
            }

           //۳

            if (EdgePoints[X, Y - 1] == 2)
            {
                EdgeMap[X , Y - 1] = 1;
                VisitedMap[X , Y - 1] = 1;
                Travers(X , Y - 1);
                return;
            }

           //۴

            if (EdgePoints[X - 1, Y - 1] == 2)
            {
                EdgeMap[X - 1, Y - 1] = 1;
                VisitedMap[X - 1, Y - 1] = 1;
                Travers(X - 1, Y - 1);
                return;
            }
            //۵
            if (EdgePoints[X - 1, Y] == 2)
            {
                EdgeMap[X - 1, Y ] = 1;
                VisitedMap[X - 1, Y ] = 1;
                Travers(X - 1, Y );
                return;
            }
            //۶
            if (EdgePoints[X - 1, Y + 1] == 2)
            {
                EdgeMap[X - 1, Y + 1] = 1;
                VisitedMap[X - 1, Y + 1] = 1;
                Travers(X - 1, Y + 1);
                return;
            }
            //۷
            if (EdgePoints[X, Y + 1] == 2)
            {
                EdgeMap[X , Y + 1] = 1;
                VisitedMap[X, Y + 1] = 1;
                Travers(X , Y + 1);
                return;
            }
            //۸

            if (EdgePoints[X + 1, Y + 1] == 2)
            {
                EdgeMap[X + 1, Y + 1] = 1;
                VisitedMap[X + 1, Y + 1] = 1;
                Travers(X + 1, Y + 1);
                return;
            }


            //VisitedMap[X, Y] = 1;
            return;

        } 
          
        //Canny Class Ends

    }

 

این کار با یک تابع بازگشتی انجام می شود که آستانه دوگانه را با دو آستانه بالا (Threshold (TH و (Low Threshold (TL و تجزیه و تحلیل ۸-اتصال انجام می دهد.

 

دانلود کد فوق از طریق لینک زیر:

Canny Edge Detection C#

رمز فایل : behsanandish.com

الگوریتم Canny

لبه یاب کنی توسط جان اف کنی در سال ۱۹۸۶ ایجاد شد و هنوز یک لبه یاب استاندارد و با دقت و کیفیت بالا میباشد.الگوریتم لبه یابی کنی یکی از بهترین لبه یابها تا به امروز است. در ادامه روش کار این الگوریتم و هم چنین کد الگوریتم Canny در Visual Basic را بررسی خواهیم کرد. این الگوریتم لبه یابی از سه بخش اصلی زیر تشکیل شده:

  • تضعیف نویز
  • پیدا کردن نقاطی که بتوان آنها را به عنوان لبه در نظر گرفت
  • حذب نقاطی که احتمال لبه بودن آنها کم است

 

معیارهایی که در لبه یا کنی مطرح است:
۱ -پایین آوردن نرخ خطا- یعنی تا حد امکان هیچ لبه ای در تصویر نباید گم شود و هم چنین هیچ چیزی که لبه نیست نباید به جای لبه فرض شود. لبه هان پیدا شده تا حد ممکن به لبه ها اصلی
نزدیک باشند.

۲ -لبه در مکان واقعی خود باشد- یعنی تا حد ممکن لبه ها کمترین فاصله را با مکان واقعی خود داشته باشند.
۳ -بران هر لبه فقط یک پاسخ داشته باشیم.

۴ -لبه ها کمترین ضخامت را داشته باشند- (در صورت امکان یک پیکسل).
لبه یاب کنی بخاطر توانایی در تولید لبه های نازک تا حد یک ییکسل برای لبه های پیوسته معروف شده است. این لبه یاب شامل چهار مرحله و چهار ورودی زیر است:
یک تصویر ورودی
یک پارامتر به نام سیگما جهت مقدار نرم کنندگی تصویر
یک حد آستانه بالا (Th)
یک حد آستانه پایین (Tl)

 

مراحل الگوریتم Canny:

۱- در ابتدا باید تصویر رنگی را به جهت لبه یابی بهتر به یک تصویر سطح خاکسترن تبدیب کرد.

۲- نویز را از تصویر دریافتی حذف کرد. بدلیل اینکه فیلتر گاوسین از یک ماسک ساده برای حذف نویز استفاده می کند لبه یاب کنی در مرحله اول برای حذف نویز آن را بکار میگیرد.

۳- در یک تصویر سطح خاکستر جایی را که بیشترین تغییرات را داشته باشند به عنوان لبه در نظر گرفته می شوند و این مکانها با گرفتن گرادیان تصویر با استفاده عملگر سوبل بدست می آیند. سپس لبه های مات یافت شده به لبه های تیزتر تبدیل می شوند.

۴- برخی از لبه های کشف شده واقعا لبه نیستند و در واقع نویز هستند که باید آنها توسط حد آستانه هیسترزیس فیلتر شوند.هیسترزیس از دو حد آستانه بالاتر (Th) و حد آستانه پایین تر (Tl) استفاده کرده و کنی پیشنهاد می کند که نسبت استانه بالا به پایین سه به یک باشد.

 این روش بیشتر به کشف لبه های ضعیف به درستی می پردازد و کمتر فریب نویز را می خورد و از بقیه روش ها بهتر است.

 

الگوریتم Canny    عملکرد الگوریتم Canny

 

الگوریتم 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

 

مقدمه

الگوریتم های لبه یابی- انسان مي تواند بسیاري از اشیاء را از روي تصویر خطوط آنها شناسایي كند. بهترین مثال برای آن تصاویر کارتنی است. سیستم بینایي انسان قبل از بازشناسي رنگ یا شدت روشنایي نوعی كشف لبه انجام مي دهد. بنابراین انجام كشف لبه قبل از تفسیر تصاویر در سیستمهاي خودكار منطقي به نظر مي رسد. انجام عملیات كشف لبه پردازش مهمي در بسیاري از سیستمهاي بینایي مصنوعي محسوب مي شود. هدف اصلی لبه یابی کاهش حجم داده ها در تصویر به همراه حفظ ساختار و شکل اصلی تصویر است. مرزماند سایه یک واقعیت فیزیکی نیست و عبارت است از جایی که بخشی از تصویر شروع یا تمام میشود. لبه را میتوان به عنوان جایی که صفحات افقی و عمودی جسم به هم میرسند در نظر گرفت.

یکی از متداولترین اعمال در تحلیل تصویر تشخیص لبه می باشد به این دلیل که لبه مرز میان یک شی و زمینهء آن است به عبارت دیگر لبه تغییر دو سطح خاکستري یا مقادیر مربوط به روشنایی دو پیکسل مجاور است که در مکان خاصی از تصویر رخ می دهد.هر چه این تغییر در سطح بیشتر باشد تشخیص لبه ساده تر خواهد بود.

نقاطي از تصویر كه داراي تغییرات روشنایي ناگھاني ھستند اغلب لبه یا نقاط لبه نامیده مي شوند. نقاط لبه معمولا ً شامل مرزھاي اشیاء و دیگر انواع تغییرات روشنایي و ھمچنین لبه ھاي نویزي مي باشند.

انواع لبه:

انواع لبه در لبه یابی

انواع الگوریتم های لبه یابی

۱- الگوریتم soble

این متد لبه ها را با استفاده از تخمین زدن مشتق پیدا می کند، که لبه ها را در آن نقاطی بر می گرداند که گرادیان تصویر I ، max است. در فیلتر سوبل دو ماسک به صورت زیر وجود دارد:

ماسک سوبل عمودی         ماسک سوبل افقی

ماسک سوبل افقی بیشتر لبه هاي افقی را مشخص میکند و ماسک سوبل عمودي،لبه هاي عمودي را مشخص میکند.

براي مشخص شدن کلیه لبه ها:
اگر Gو 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
دانلود کد فوق از طریق لینک زیر:

الگوریتم سوبل به زبان متلب

رمز فایل : behsanandish.com

 


۲- الگوریتم Canny

لبه یاب کنی توسط جان اف کنی در سال ۱۹۸۶ ایجدداد شد و هنوز یک لبه یاب استاندارد و با دقت و کیفیت بالا میباشد.الگوریتم لبه یابی کنی یکی از بهترین لبه یابها تا به امروز است. این الگوریتم لبه یابی از سه بخش اصلی زیر تشکیل شده است:

  • تضعیف نویز
  • پیدا کردن نقاطی که بتوان آنها را به عنوان لبه در نظر گرفت
  • حذب نقاطی که احتمال لبه بودن آنها کم است

 

معیارهایی که در لبه یا کنی مطرح است:
۱ -پایین آوردن نرخ خطا- یعنی تا حد امکان هیچ لبه ای در تصویر نباید گم شود و هم چنین هیچ چیزی که لبه نیست نباید به جای لبه فرض شود. لبه هان پیدا شده تا حد ممکن به لبه ها اصلی
نزدیک باشند.

۲ -لبه در مکان واقعی خود باشد- یعنی تا حد ممکن لبه ها کمترین فاصله را با مکان واقعی خود داشته باشند.
۳ -بران هر لبه فقط یک پاسخ داشته باشیم.

۴ -لبه ها کمترین ضخامت را داشته باشند- (در صورت امکان یک پیکسل).
لبه یاب کنی بخاطر توانایی در تولید لبه های نازک تا حد یک ییکسل برای لبه های پیوسته معروف شده است. این لبه یاب شامل چهار مرحله و چهار ورودی زیر است:
یک تصویر ورودی
یک پارامتر به نام سیگما جهت مقدار نرم کنندگی تصویر
یک حد آستانه بالا (Th)
یک حد آستانه پایین (Tl)

 

و مراحل شامل

۱- در ابتدا باید تصویر رنگی را به جهت لبه یابی بهتر به یک تصویر سطح خاکسترن تبدیب کرد.

۲- نویز را از تصویر دریافتی حذف کرد. بدلیل اینکه فیلتر گاوسین از یک ماسک ساده برای حذف نویز استفاده می کند لبه یاب کنی در مرحله اول برای حذف نویز آن را بکار میگیرد.

۳- در یک تصویر سطح خاکستر جایی را که بیشترین تغییرات را داشته باشند به عنوان لبه در نظر گرفته می شوند و این مکانها با گرفتن گرادیان تصویر با استفاده عملگر سوبل بدست می آیند. سپس لبه های مات یافت شده به لبه های تیزتر تبدیل می شوند.

۴- برخی از لبه های کشف شده واقعا لبه نیستند و در واقع نویز هستند که باید آنها توسط حد آستانه هیسترزیس فیلتر شوند.هیسترزیس از دو حد آستانه بالاتر (Th) و حد آستانه پایین تر (Tl) استفاده کرده و کنی پیشنهاد می کند که نسبت استانه بالا به پایین سه به یک باشد.

 این روش بیشتر به کشف لبه های ضعیف به درستی می پردازد و کمتر فریب نویز را می خورد و از بقیه روش ها بهتر است.

 

الگوریتم Canny    عملکرد الگوریتم Canny

 

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)&gt;=-pi/8 &amp; dir(i,j)&lt;pi/8&lt;/pre&gt;
&lt;pre&gt;           if ans(i,j)&lt;=ans(i,j-1) | ans(i,j)&lt;=ans(i,j+1)
                ans(i,j)=0;
            end
        end
        if dir(i,j)&gt;=pi/8 &amp; dir(i,j)&lt;3*pi/8
            if ans(i,j)&lt;=ans(i-1,j+1) | ans(i,j)&lt;=ans(i+1,j-1)
                ans(i,j)=0;
            end
        end
        if dir(i,j)&gt;=3*pi/8 | dir(i,j)&lt;-3*pi/8
            if ans(i,j)&lt;=ans(i-1,j) | ans(i,j)&lt;=ans(i+1,j)
                ans(i,j)=0;
            end
        end
        if dir(i,j)&lt;-pi/8 &amp; dir(i,j)&gt;=3*pi/8
            if ans(i,j)&lt;=ans(i-1,j-1) | ans(i,j)&lt;=ans(i+1,j+1)
                ans(i,j)=0;
            end
        end
        if ans(i,j)&lt;40
            ans(i,j)=0;
        else
            ans(i,j)=255;
        end
    end
end
figure(4)
imshow(ans)

 

دانلود کد فوق از طریق لینک زیر:

رمز فایل : behsanandish.com

 


 ۳- الگوریتم Roberts

این الگوریتم به نویز حساسیت زیادی دارد وپیکسل های کمتری را برای تقریب گرادیان بکار می برد،درضمن نسبت به الگوریتم canny هم قدرت کمتری دارد.

 

الگوریتم Robertsعملکرد الگوریتم Roberts

 

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)&lt;0.25)
            R(i,j)=0;
        else R(i,j)=1;
        end
        if (Z(i,j)&lt;0.25)
            Z(i,j)=0;
        else Z(i,j)=1;
        end
        if (X(i,j)&lt;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

 


۴- الگوریتم Prewitt

این الگوریتم شباهت زیادی با الگوریتم sobel دارد با این تفاوت که ضرایب ماسک آنها با هم فرق می کند.

 

الگوریتم Prewittعملکرد الگوریتم Prewitt

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)&lt;0.5
           Dx(i,j)=0;
        else Dx(i,j)=1;
        end
        if Dy(i,j)&lt;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)&lt;0.5)
            P(i,j)=0;
        else P(i,j)=1;
        end
    end
end
figure(4);
imshow(P,[]);

 

دانلود کد فوق از طریق لینک زیر:

Prewitt In Matlab

رمز فایل : behsanandish.com

 


۵- الگوریتم Zerocross

این الگوریتم قسمت هایی از لاپلاس یک تصویر را جستجو می کند که مقدار لاپلاس از صفر می گذرد. به عبارت دیگر نقاطی که لاپلاس علامت را تغییر می دهد.

 

الگوریتم Zerocrossعملکرد الگوریتم Zerocross

 

[BW,threshOut] =edge(graypic,'zerocross')
[BW,threshOut] = edge(graypic,'zerocross',sensitive,filter name);
sensitive -> حساسیت
بین ۰ و ۱ و حساس ترین حالت ۰

 

دانلود کد فوق از طریق لینک زیر:

Zerocross In Matlab

رمز فایل : behsanandish.com

 


۶- الگوریتم  LOG)Laplacian of gaussian)

لاپلاس یک اندازه گیری ایزوتروپیک دوبعدی از مشتق فضایی مرتبه دوم از یک تصویر است. لاپلاس یک تصویر، مناطق تغییرات شدت سریع را نشان می دهد و بنابراین اغلب برای تشخیص لبه استفاده می شود.

 

الگوریتم LOGعملکرد الگوریتم LOG-2

 

 

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)&lt;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 است. پیشنهاد می کنیم جهت آشنایی با الگوریتم های لبه یابی، مطلب «الگوریتم های لبه یابی و انواع آن» را مشاهده نمایید. در فیلتر سوبل دو ماسک به صورت زیر وجود دارد:

ماسک سوبل عمودی         ماسک سوبل افقی

ماسک سوبل افقی بیشتر لبه هاي افقی را مشخص میکند و ماسک سوبل عمودي،لبه هاي عمودي را مشخص میکند.

براي مشخص شدن کلیه لبه ها:
اگر Gو 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)&amp;lt;1
            Dx(i,j)=0;
        else Dx(i,j)=1;
        end
        if Dy(i,j)&amp;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)&amp;lt;1)
            S(i,j)=0;
        else S(i,j)=1;
        end
    end
end
figure(4);
imshow(S,[]);
دانلود کد فوق از طریق لینک زیر:

الگوریتم سوبل به زبان متلب

رمز فایل : behsanandish.com


 

کد الگوریتم سوبل( Sobel ) در #C:

۱٫کد برای فیلتر کانولوشن: بخش اول این تابع برای گرفتن اطلاعات تصویر و ذخیره آن به آرایه اختصاص داده شده است.

 

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 -&amp;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 -&amp;gt; we have all our needed info in the array
    sourceImage.UnlockBits(srcData);

 

۲٫کد تبدیل سیاه و سفید: از آنجایی که اپراتور Sobel اغلب برای تصاویر سیاه و سفید استفاده می شود، در اینجا یک کد برای تبدیل به سیاه و سفید است که توسط پارامتر boolean شما می توانید انتخاب کنید تبدیل کردن را یا نه.

 

//Convert your image to grayscale if necessary
if (grayscale == true)
{
    float rgb = 0;
    for (int i = 0; i &amp;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;
    }
}

 

۳٫کد برای تنظیم متغیرهای مورد استفاده در فرآیند کانولوشن:

 

/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 &amp;lt; height - filterOffset; OffsetY++)
{
    for (int OffsetX = filterOffset; OffsetX &amp;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;

 

۴٫ اعمال کانولوشن هسته به پیکسل فعلی:

 

//kernel calculations
for (int filterY = -filterOffset; filterY &amp;lt;= filterOffset; filterY++)
{
for (int filterX = -filterOffset; filterX &amp;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 &amp;gt; 255) bt = 255;
else if (bt &amp;lt; 0) bt = 0;
if (gt &amp;gt; 255) gt = 255;
else if (gt &amp;lt; 0) gt = 0;
if (rt &amp;gt; 255) rt = 255;
else if (rt &amp;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;
}
}

 

۵٫ کد خروجی تصویر پردازش شده:

 

//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;
}

 

۶٫ کد برای هسته سوبل:

 

//Sobel operator kernel for horizontal pixel changes
private static double[,] xSobel
{
    get
    {
        return new double[,]
        {
            { -۱, ۰, ۱ },
            { -۲, ۰, ۲ },
            { -۱, ۰, ۱ }
        };
    }
}

//Sobel operator kernel for vertical pixel changes
private static double[,] ySobel
{
    get
    {
        return new double[,]
        {
            {  ۱,  ۲,  ۱ },
            {  ۰,  ۰,  ۰ },
            { -۱, -۲, -۱ }
        };
    }
}

 

همه این کد در اینجا موجود است (پروژه با ویژوال استودیو ۲۰۱۵ ایجاد شد):

SobelOperatorInC#

رمز فایل : behsanandish.com

 

کد الگوریتم sobel در #C

 


 

کد الگوریتم سوبل( Sobel ) در ++C:

در ادامه دو کد برای الگوریتم Sobel  در ++C آماده کردیم:

۱٫

 

#include&lt;iostream&gt;
#include&lt;cmath&gt;
#include&lt;opencv2/imgproc/imgproc.hpp&gt;
#include&lt;opencv2/highgui/highgui.hpp&gt;

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&lt;uchar&gt;(y-1, x-1) +
                ۲*image.at&lt;uchar&gt;(y, x-1) +
                 image.at&lt;uchar&gt;(y+1, x-1) -
                  image.at&lt;uchar&gt;(y-1, x+1) -
                   ۲*image.at&lt;uchar&gt;(y, x+1) -
                    image.at&lt;uchar&gt;(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&lt;uchar&gt;(y-1, x-1) +
                ۲*image.at&lt;uchar&gt;(y-1, x) +
                 image.at&lt;uchar&gt;(y-1, x+1) -
                  image.at&lt;uchar&gt;(y+1, x-1) -
                   ۲*image.at&lt;uchar&gt;(y+1, x) -
                    image.at&lt;uchar&gt;(y+1, x+1);
}

int main()
{

      Mat src, dst;
      int gx, gy, sum;

      // Load an image
      src = imread(&quot;lena.jpg&quot;, CV_LOAD_IMAGE_GRAYSCALE);
      dst = src.clone();
      if( !src.data )
      { return -1; }

        for(int y = 0; y &lt; src.rows; y++)
            for(int x = 0; x &lt; src.cols; x++)
                dst.at&lt;uchar&gt;(y,x) = 0.0;

        for(int y = 1; y &lt; src.rows - 1; y++){
            for(int x = 1; x &lt; src.cols - 1; x++){
                gx = xGradient(src, x, y);
                gy = yGradient(src, x, y);
                sum = abs(gx) + abs(gy);
                sum = sum &gt; 255 ? 255:sum;
                sum = sum &lt; 0 ? 0 : sum;
                dst.at&lt;uchar&gt;(y,x) = sum;
            }
        }

        namedWindow(&quot;final&quot;);
        imshow(&quot;final&quot;, dst);

        namedWindow(&quot;initial&quot;);
        imshow(&quot;initial&quot;, src);

      waitKey();

 
    return 0;
}

 

دانلود کد فوق از طریق لینک زیر:

Sobel in C++-Code1

رمز فایل : behsanandish.com

 

۲٫

 

#include &quot;itkImage.h&quot;
#include &quot;itkImageFileReader.h&quot;
#include &quot;itkImageFileWriter.h&quot;
#include &quot;itkSobelEdgeDetectionImageFilter.h&quot;

int main( int argc, char* argv[] )
{
  if( argc != 3 )
    {
    std::cerr &lt;&lt; &quot;Usage: &quot;&lt;&lt; std::endl;
    std::cerr &lt;&lt; argv[0];
    std::cerr &lt;&lt; &quot;&lt;InputFileName&gt; &lt;OutputFileName&gt;&quot;;
    std::cerr &lt;&lt; std::endl;
    return EXIT_FAILURE;
    }

  constexpr unsigned int Dimension = 2;

  using InputPixelType = unsigned char;
  using InputImageType = itk::Image&lt; InputPixelType, Dimension &gt;;

  using ReaderType = itk::ImageFileReader&lt; InputImageType &gt;;
  ReaderType::Pointer reader = ReaderType::New();
  reader-&gt;SetFileName( argv[1] );

  using OutputPixelType = float;
  using OutputImageType = itk::Image&lt; OutputPixelType, Dimension &gt;;

  using FilterType = itk::SobelEdgeDetectionImageFilter&lt; InputImageType, OutputImageType &gt;;
  FilterType::Pointer filter = FilterType::New();
  filter-&gt;SetInput( reader-&gt;GetOutput() );

  using WriterType = itk::ImageFileWriter&lt; OutputImageType &gt;;
  WriterType::Pointer writer = WriterType::New();
  writer-&gt;SetFileName( argv[2] );
  writer-&gt;SetInput( filter-&gt;GetOutput() );

  try
    {
    writer-&gt;Update();
    }
  catch( itk::ExceptionObject &amp; error )
    {
    std::cerr &lt;&lt; &quot;Error: &quot; &lt;&lt; error &lt;&lt; std::endl;
    return EXIT_FAILURE;
    }

  return EXIT_SUCCESS;
}

 

دانلود کد فوق از طریق لینک زیر:

Sobel in C++-Code2

رمز فایل : behsanandish.com

 


کد الگوریتم سوبل( Sobel ) در C:

 

/* sobel.c */
#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;float.h&gt;
#include &quot;mypgm.h&quot;

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,  ۰,  ۱ },
      { -۲,  ۰,  ۲ },
      { -۱,  ۰,  ۱ }};
  double pixel_value;
  double min, max;
  int x, y, i, j;  /* Loop variable */

  /* Maximum values calculation after filtering*/
  printf(&quot;Now, filtering of input image is performed\n\n&quot;);
  min = DBL_MAX;
  max = -DBL_MAX;
  for (y = 1; y &lt; y_size1 - 1; y++) {
    for (x = 1; x &lt; x_size1 - 1; x++) {
      pixel_value = 0.0;
      for (j = -1; j &lt;= 1; j++) {
    for (i = -1; i &lt;= 1; i++) {
      pixel_value += weight[j + 1][i + 1] * image1[y + j][x + i];
    }
      }
      if (pixel_value &lt; min) min = pixel_value;
      if (pixel_value &gt; max) max = pixel_value;
    }
  }
  if ((int)(max - min) == 0) {
    printf(&quot;Nothing exists!!!\n\n&quot;);
    exit(1);
  }

  /* Initialization of image2[y][x] */
  x_size2 = x_size1;
  y_size2 = y_size1;
  for (y = 0; y &lt; y_size2; y++) {
    for (x = 0; x &lt; x_size2; x++) {
      image2[y][x] = 0;
    }
  }
  /* Generation of image2 after linear transformtion */
  for (y = 1; y &lt; y_size1 - 1; y++) {
    for (x = 1; x &lt; x_size1 - 1; x++) {
      pixel_value = 0.0;
      for (j = -1; j &lt;= 1; j++) {
    for (i = -1; i &lt;= 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 in C

رمز فایل : behsanandish.com


 

کد الگوریتم سوبل( 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}, _
{-۲, ۰, ۲}, {-۱, ۰, ۱}}
Dim intOldY As Integer(,) = New Integer(,) {{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 &gt; intMax OrElse
intGX * intGX + intGY * intGY &gt; intMax OrElse
intBX * intBX + intBY * intBY &gt; intMax Then

bmpImage.SetPixel(i, j, Color.Black)

Else

bmpImage.SetPixel(i, j, Color.Transparent)

End If

Next

Next

picModified.Image = bmpImage

End Sub
دانلود کد فوق از طریق لینک زیر:

Sobel in VB

رمز فایل : behsanandish.com

 

کد الگوریتم sobel در VB

 


 

پیشنهاد می کنیم جهت آشنایی با الگوریتم های لبه یابی، مطلب «الگوریتم های لبه یابی و انواع آن» را مشاهده نمایید.

 

مسئله فروشنده دوره‌گرد

 اگر فروشنده دوره گرد از نقطه A شروع کند و فواصل بین نقاط مشخص باشد، کوتاه‌تربن مسیر که از تمام نقاط یکبار بازدید می‌کند و به A بازمی‌گردد کدام است؟

مسئله فروشنده دوره گرد (به انگلیسی: Travelling salesman problem، به‌اختصار: TSP) مسئله‌ای مشهور است که ابتدا در سده ۱۸مسائل مربوط به آن توسط ویلیام همیلتون و توماس کرکمن مطرح شد و سپس در دهه ۱۹۳۰ شکل عمومی آن به وسیله ریاضیدانانی مثلکارل منگر از دانشگاه هاروارد و هاسلر ویتنی از دانشگاه پرینستون مورد مطالعه قرار گرفت.

شرح مسئله بدین شکل است:

تعدادی شهر داریم و هزینه رفتن مستقیم از یکی به دیگری را می‌دانیم. مطلوب است کم‌هزینه‌ترین مسیری که از یک شهر شروع شود و از تمامی شهرها دقیقاً یکبار عبور کند و به شهر شروع برگردد.

 

مسئله فروشنده دوره گرد

تعداد جواب‌های شدنی مسئله، برابر است با {\displaystyle {\frac {1}{2}}(n-1)!}{\displaystyle {\frac {1}{2}}(n-1)!} برای n>۲ که n تعداد شهرها می‌باشد. در واقع این عدد برابر است با تعداددورهای همیلتونی در یک گراف کامل با n رأس.

مسئله‌های مرتبط

مسئله فروشنده دوره گرد یا Traveling Salesman Problem (به اختصار TSP)، یکی از مسائل بسیار مهم و پرکاربرد در علوم کامپیوتر و تحقیق در عملیات است.

سه روش کلی برای کد کردن راه حل‌های مسئله TSP ارائه شده‌است که در الگوریتم‌های مختلفی قابل استفاده هستند. راه حل‌های سه گاه عبارتند از:

الف) نمایش جواب به صورت رشته گسسته جایگشتی که در الگوریتم‌های زیر قابل استفاده است: الگوریتم ژنتیک یا Genetic Algorithms (به اختصار GA) شبیه‌سازی تبرید یا Simulated Annealing (به اختصار SA) جستجوی ممنوعه یا Tabu Search (به اختصار TS) جستجوی همسایگی متغیر یا Variable Neighborhood Search (به اختصار VNS) بهینه‌سازی کلونی مورچگان یا Ant Colony Optimization (به اختصار ACO) جستجوی هارمونی یا Harmony Search (به اختصار HS) و سایر الگوریتم‌های بهینه‌سازی گسسته

ب) نمایش جواب به صورت کلیدهای تصادفی یا Random Key که در الگوریتم‌های زیر قابل استفاده است: الگوریتم ژنتیک یا Genetic Algorithms (به اختصار GA) بهینه‌سازی ازدحام ذرات یا Particle Swarm Optimization (به اختصار PSO) الگوریتم رقابت استعماری یا Imperialist Competitive Algorithm (به اختصار ICA) تکامل تفاضلی یا Differential Evolution (به اختصار DE) بهینه‌سازی مبتنی بر جغرافیای زیستی یا Bio-geography Based Optimization (به اختصار BBO) استراتژی‌های تکاملی یا Evolution Strategies (به اختصار ES) برنامه‌ریزی تکاملی یا Evolutionary Programming (به اختصار EP) و سایر الگوریتم‌های بهینه‌سازی پیوسته

پ) نمایش جواب به شکل ماتریس‌های شبیه فرومون که توسط تمامی الگوریتم‌های اشاره شده در مورد (ب) قابل استفاده می‌باشد.

  • مسئله معادل در نظریه گراف به این صورت است که یک گراف وزن‌دار کامل داریم که می‌خواهیم کم‌وزن‌ترین دور همیلتونی را پیدا کنیم.
  • مسئله تنگراه فروشنده دوره‌گرد (به انگلیسی: Bottleneck traveling salesman problem، به‌اختصار: bottleneck TSP) مسئله‌ای بسیار کاربردی است که در یک گراف وزن‌دار کم‌وزن‌ترین دور همیلتونی را می‌خواهد که شامل سنگین‌ترین یال باشد.
  • تعمیم‌یافته مسئله فروشنده دوره‌گرد دارای ایالت‌هایی است که هر کدام حداقل یک شهر دارند و فروشنده باید از هر ایالت دقیقاً از یک شهر عبور کند. این مسئله به «مسئله سیاست‌مدار مسافر» نیز شهرت دارد.

الگوریتم‌ها

مسئله فروشنده دوره گرد جزء مسائل ان‌پی سخت است. راه‌های معمول مقابله با چنین مسائلی عبارتند از:

  • طراحی الگوریتم‌هایی برای پیدا کردن جواب‌های دقیق که استفاده از آن‌ها فقط برای مسائل با اندازه کوچک صورت می‌گیرد.
  • استفاده از الگوریتم‌های مکاشفه‌ای که جواب‌هایی به‌دست می‌دهد که احتمالاً درست هستند.
  • پیدا کردن زیرمسئله‌هایی از مسئله یا به عبارت دیگر تقسیم مسئله به مسئله‌های کوچکتر، تا بتوان الگوریتم‌های مکاشفه‌ای بهتر و دقیق‌تری ارائه داد.

الگوریتم‌های دقیق

سرراست‌ترین راه حل امتحان کردن تمامی جایگشتهای ممکن برای پیدا کردن ارزان‌ترین مسیر است که چون تعداد جایگشت‌ها !n است، این راه حل غیرعملی می‌شود. با استفاده از برنامه‌نویسی پویا مسئله می‌تواند با مرتبه زمانی{\displaystyle n^{2}2^{n}}{\displaystyle n^{2}2^{n}} حل شود. راه‌های دیگر استفاده از الگوریتم‌های انشعاب و تحدید برای ۴۰ تا ۶۰ شهر، استفاده از برنامه‌نویسی خطی برای کوچکتر از ۲۰۰ شهر و استفاده از روش برش-صفحه برای اندازه‌های بزرگ است.

الگوریتم‌های مکاشفه‌ای

الگوریتم‌های تقریبی متنوعی وجود دارند که خیلی سریع جواب‌های درست را با احتمال بالا به‌دست می‌دهند که می‌توان آن‌ها را به صورت زیر دسته‌بندی کرد:

  • مکاشفه‌ای سازنده
  • بهبود تکراری
    • مبادله دوبه‌دو
    • مکاشفه‌ای k-opt
    • مکاشفه‌ای V-opt
  • بهبود تصادفی

پیچیدگی محاسباتی الگوریتم فروشنده دوره گرد

این الگوریتم بطور مستقیم در مرتبه زمانی(!O(n حل می‌شود اما اگر به روش برنامه‌نویسی پویا برای حل آن استفاده کنیم مرتبه زمانی آن (O(n^2*2^n خواهد شد که جز مرتبه‌های نمایی است. باید توجه داشت علی‌رغم آنکه مرتبه نمایی مذکور زمان بسیار بدی است اما همچنان بسیار بهتر از مرتبه فاکتوریل می‌باشد. شبه کد الگوریتم فوق به صورت زیر است که در آن تعداد زیر مجموعه‌های یک مجموعه n عضوی ۲ به توان n می‌باشد و for اول یک ضریب n را نیز حاصل می‌شود که به ازای تمام شهرهای غیر مبدأ می‌باشد و حاصل (n*(2^n را پدیدمی‌آورد؛ بنابراین برای جستجوی کمترین مقدار نیاز به یک عملیات خطی از مرتبه n داریم که در زمان فوق نیز ضرب می‌شود و در نهایت زمان (n^2)*(2^n) را برای این الگوریتم حاصل می‌کند.

 

C({1},1) = 0
  for (S=2 to n)
  for All Subsets S subset of {1,2,3,...} of size S and containing1
  C(S,1) = &amp;amp;amp;
  for All J member of S , J&amp;amp;lt;&amp;amp;gt;1
  C (S , J) = min { C (S - { J } , i) + D i,J: i member of S , i &amp;amp;lt;&amp;amp;gt; J }
 return min j C ({1 . . . n}, J) + D J,1

 

شبه کد مسئله فروشنده دوره گرد

مسئله:یک تور بهینه برای یک گراف وزن دار و جهت دار مشخص نمایید. وزن‌ها اعدادی غیر منفی هستند

ورودی:یک گراف وزن دار و جهت دار و n تعداد گره‌های گراف. گراف با یک ارائه دو بعدی w مشخص می‌شود که سطرها و ستون‌هایش از ۱ تا n شاخص دهی شده‌اند و در ان [w[i][j معرف وزن لبه از گره iام به گره jام است.۴

خروجی:یک متغیر minlength که مقدار ان طول تور بهینه است و یک ارائه دو بعدی p که یک تور بهینه را از روی ان می‌توان ساخت . سطرهای p از ۱ تا n و ستونهای ان با تمامی زیر مجموعه‌های {v-{v1 شاخص دهی شده‌اند . [P[i][A شاخص اولین گره بعد از vi بر روی کوتاهترین مسیر از viتاvj است که از تمام گره‌های A دقیقاً یکبار می‌گذرد.

 

* Void travel ( int n ,
 *              const number W[][],
 * index p[][],
 * number&amp;amp;amp;minlength
* )
* {
* Index i, j, k;
* number D[1..n][subset of V-{vi}];
* for (i= 2 ; i&amp;amp;lt;=n;i++)
* D[i][∅} = w[i][1];
* for(k=1; k&amp;amp;lt;=n-2 ; k++)
* for (all subsets A v-{v1} containing k vertices
* for (i such that j≠۱ and vi is not in A){
* D[i][A] = minimum (W[i][j]+ D[vj][A-{vj}]);
* P[i][A]= value of j that gave the minimum
* }
* D[1][v-{vi}]= minimum (W[1][j]+ D[vj][V-{v1}];
* P[1][V-{v1}]= value of j that gave the minimum ;
* Minlength = D[1][V-{v1}];
* }

 

الگوریتم جستجوی ممنوعه یا Tabu Search یا به اختصار TS، یکی از قوی‌ترین الگوریتم‌ها در زمینه حل مسائل بهینه‌سازی، به خصوص مسائل بهینه‌سازی مبتنی بر گراف و مسائل بهینه‌سازی ترکیباتی (Combinatorial Optimization) است. این الگوریتم در اواخر دهه ۱۹۸۰ و توسط گلووِر (Glover) و همکارانش ارائه گردید. غالباً یکی از مسائلی که برای حل آن‌ها از الگوریتم TS استفاده می‌شود، مسئله فروشنده دوره گرد یا TSP است. این الگوریتم پاسخ‌های بسیار مناسبی را برای انواع مسائل گسسته به خصوص مسئله TSP ارائه می‌کند!

منبع


 

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

این مسئله علاوه بر جنبه نظری از جنبه عملی نیز کاربرد فراوانی دارد به عنوان مثال در مواردی مانند مسیریابی، ساخت تراشه های الکترونیکی، زمان بندی کارها و غیره مورد استفاده قرار گیرد. اما  در مواجهه با چالش حل مسائل بهینه سازی، که این نوع مسائل در دنیای واقعی بسیار زیاد هستند، روش های کلاسیک اغلب با مشکل مواجه می شوند. به همین دلیل معمولا از روشهای فرا ابتکاری همانند الگوریتم ژنتیک و سایر الگوریتم های تکاملی برای حل این نوع مسائل استفاده میشود

 

به صورت کلی مسئله فروشنده دوره گرد دارای ۳ حالت زیر می باشد.

۱-    فروشنده دوره گرد متقارن

در حالت متقارن مسئله، تعدادی شهر داریم و هزینه رفتن مستقیم از یکی به دیگری را می‌دانیم .مطلوب است کم ‌هزینه‌ترین مسیری که از یک شهر شروع شود و از تمامی شهرها دقیقا یکبار عبور کند و به شهر شروع بازگردد.

۲-   فروشنده دوره گرد نامتقارن

مسأله ­ي فروشنده ­ي دوره­ گرد نامتقارن, یک TSP است که فاصله بين رئوس آن, متقارن نيست. ATSP بسيار مشکل­تر از TSP است، در حقيقت در حالي که TSP متقارن, حتي در گراف­هاي با چندين هزار  رأس, به طور بهينه, قابل حل است, تنها نمونه­هاي خاصي ازATSP را که ماتريس فاصله­ي آنها, تقريباً متقارن است, تنها در گراف­هاي داراي چندين دوجين رأس, مي­توان به طور بهينه حل کرد. به کاربردن هوش مصنوعی  براي ATSP, راحت­ و سر راست است. چون هيچ تغييراتي در الگوريتم اصلي, لازم ندارد. پيچيدگي محاسباتي در حلقه­ي الگوريتم, برنامه­ي کاربردي TSP, يکسان است, زيرا تنها تفاوت آنها در فاصله­ها و ماتريس­هاي ردپا است که در اينجا ديگر متقارن نيستند.

۳-   فروشنده دوره گرد با پنجره های زمانی

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

 

دوربین های عکاسی آنالوگ

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

  1. کاملاً مکانیکی،
  2. نیمه خودکار،
  3. کاملاً خودکار (ناوبری الکترونیکی)،

طراحی و ساخته شده است.

اولین دوربین ۳۵ میلیمتری با قابلیت تعویض نمایاب و لنز(system camera)، نیکون اِف

اولین دوربین ۳۵ میلیمتری با قابلیت تعویض نمایاب و لنز(system camera)، نیکون اِف

تاریخچه

اتاق تاریک، اولین قدم بزرگ در راه پیدایش عکاسی بود که توسط نقاشان ایتالیا یی در طی قرن شانزدهم میلادی برداشته شد. برای بدست آوردن حداکثر وضوح، تنظیم فاصله روزنه از دیواری که تصویر روی آن بازتابیده می‌شد، از مشکلات اصلی در این سیستم بشمار می‌رفت. رفع این مشکل، باعث ورود عدسی به دنیای عکس و تصویر گردید.

انواع دوربین های آنالوگ

  1. دوربین سوراخ سوزنی (Pin Hole)،
  2. دوربین تک‌لنزی غیربازتابی (rangefinder camera)،
  3. دوربین تک‌لنزی بازتابی (Single Lens Reflex)،
  4. دوربین دولنزی بازتابی (Twin-lens reflex)،
  5. دوربین قطع بزرگ (View Camera).

سیستم ضبط تصویر

در یک دوربین آنالوگ، فیلم حساس به نور، تصویر را ذخیره می‌سازد و بعد از عملیات شیمیایی برای نگهداری تصویر از آن استفاده می‌شود.

نگارخانه

دوربین سوراخ سوزنیکداک رتینا ۱۹۵۷آساهی فلکس ۱۹۵۵رولی فلکسدوربین دولنزی بازتابی (Voigtländer Brillant).اولین دوربین تک لنزی بازتابی، کانتکس اس، تولید سال ۱۹۴۹نیکون اِف ۱۹۵۹، اولین دوربین ۳۵ میلیمتری با قابلیت تعویض نمایاب و لنزدوربین ۵*۷ اینچ تویو.

دوربین آنالوگ به دوربینی گفته میشود که با دست و یا دستگاه های مکانیکی خود دوربین تنظیم میشود و دارای فیلم است. ساختار دوربین های آنالوگ بر این اساس است که؛ نور از داخل لنز گذشته و پس از برخورد با یک آینه، به سوی چشم ناظر هدایت می شود. وقتی عکاس کادر مناسب و دیگر پارامترها را تنظیم کرد، دکمه شاتر را می فشارد و با این کار بخش هایی که مانع رسیدن نور به صفحه حساس می شده اند، از میان برداشته می شوند. طبیعتاً با بالا رفتن آینه، عکاس قادر به مشاهده آن چه دوربین به سوی آن نشانه رفته است، نیست. با توجه به فاصله کانونی لنز، تصویر مطلوب در نقطه ای خاص و وارونه تصویر اصلی تشکیل می شود. دوربین و فوکوسر آن نیز سبب می شوند این تصویر درست روی محل مورد نظر عکاس؛ یعنی، صفحه حساس(که در دوریبن های آنالوگ، فیلم است) تشکیل شود.

اجزای مختلف یک دوربین آنالوگ

۱) لنز دوربین
۲)نگه دارنده ی لنز
۳)دیافراگم
۴)چرخاننده ی فیلم
۵)فیلم عکاسی
۶)محل اتصال بند دوربین
۷)شاتر
۸)دکمه ی کنترل سرعت عکسبرداری
۹)صفحه ی مشخصات عکس و شارژ دوربین(در دوربین هایی که باتری دارند)
۱۰)ویزور
۱۱)محل اتصال فلش خارجی
۱۲)حلقه ی فوکوس

مراحل گرفته شدن یک عکس

هنگامی که فیلم داخل دوربین قرار میگیرد معمولا ۲-۳ تا فریم به علت در معرض نور قرار گرفتن میسوزند بنابراین بهتر است فیلم ها رو ۲-۳ تا جلو بزنیم. جلو زدن فیلم به این صورت است که یک دسته ی مکانیکی که در بالای دوربین قرار دارد را با چرخاندن آن تا انتها، میله ی چرخاننده ی فیلم می چرخد و فیلم یک فریم کامل جلو میرود! معمولا برای اولین استفاده از فیلم باید ۲-۳ بار این حرکت رو تکرار کرد.
دقت کنید وقتی یک فریم رو کامل جلو میبرید تا اینکه یک بار شاتر فشرده نشود (در واقع عکسی گرفته نشود) نمیتوان دسته ی چرخاننده ی فیلم را حرکت داد. از روش معکوس این حرکت (چرخاندن فیلم در خلاف جهت) هم میتوان برای تکنیک هایی مانند مولتی اکسپوز استفاده کرد.و هم با فشردن دکمه ی آزاد کننده ی فیلم، برای جمع کردن کامل فیلم برای ظهور عکس. (جزئیات در برخی دوربین ها کمی متفاوت است) بعد از اینکه یک فریم آماده ی عکس گرفتن شد و پس از انتخاب کادر مناسب و تنظیم دیافراگم مناسب با توجه به حساسیت فیلم مورد استفاده قرار گرفته و فوکوس صحیح دکمه ی شاتر را فشار میدهیم.

آینه بالا می رود و پرده ی شاتر هم بالا میرود. (در دوربین های آنالوگ برخی پرده ها یک تکه هستند و برخی ۳ تکه که پرده های ۳ تکه طبیعتا خیلی بهتر هستند) نور وارد شده به لنز در برخورد به نگاتیو به مواد شیمیایی ای که روی فیلم پوشونده شده برخورد میکند این نور در واقع انرژی فعال سازی یک واکنش شیمیایی است. این مواد شیمیایی با توجه به شدت و رنگ نور (طول موج) واکنش شیمیایی انجام می دهند که بعدا در ظهور عکس، ظاهر کننده این مواد شیمیایی که واکنش های مختلف دادند رو از هم تفکیک کرده و هر فراورده روی صفحه ی فیلم رو به صورت رنگ های نگاتیو (منفی نور های عکس) در می آورد.

یکی از مزیت های دوربین آنالوگ نشان دادن خیلی خوب و واقعی رنگ است. اما اگر به یک نقطه نور زیاد برسد (در واقع اور اکسپوز بشود) آن قسمت کاملا سفید خواهد شد.سپس فیلم ظاهر شده اسکن و اینورت می شود و عکس قابلیت چاپ پیدا می کند. حساسیت فیلم های مورد استفاده قرار گرفته به مواد شیمیایی موجود در اون وابسته است.
فیلم های سیاه سفید از حساسیت ۱۰۰ تا ۳۲۰۰ یا حتی ۶۴۰۰ برخوردارند اما فیلم های رنگی معمولا دارای حساسیت های ۱۰۰ و ۲۰۰ و ۴۰۰ هستند. هرچه حساسیت بیشتر باشد ذرات شیمیایی روی صفحه فیلم بزرگتر هستندکه این باعث پایین آمدن کیفیت عکس خواهد شد ولی فقط در چاپ به ابعاد خیلی بزرگ این افت کیفیت احساس میشود.

مزایای عکاسی آنالوگ

۱- مصرف باطری به مراتب کمتر نسبت به عکاسی دیجیتال و عدم احتمال ایجاد مشکل در کار عکاسی های طولانی مدت به علت تمام شدن باطری
۲- عدم وجود نویز در نوردهی های طولانی مدت
۳- عدم وابستگی کیفیت عکس به نوع دوربین.البته به لنز بستگی دارد ولی به بدنه دوربین خیلی وابسته نیست و کیفیت عکس به نوع فیلم مورد استفاده بستگی دارد.
۴- بیشتر بودن دامنه دینامیکی فیلم نسبت به سنسور دیجیتال
۵- ارزان تر بودن دوربین های فیلمی
۶- امکان استفاده از فیلم های مختلف در یک دوربین ( مثل حساس به مادن قرمز و … )
۷- دوربین های فیلمی نسبت به دیجیتال ها در برابر آسیب ها حساسیت کمتری دارند.ضربه٬ گرد و غبار٬ رطوبت و …
۸- امکان خرابی کمتری دارند و در شرایط بحرانی قابل اطمینان تر هستند.


مزایای دوربین های عکاسی دیجیتالی:

۱- مرور فوری عکس ، بدون این که عکاس منتظر شود که عکس ظاهر شود. اگر مشکلی در عکس باشد ، عکاس میتواند مشکل را فورا تصحیح کند و عکس دیگری بگیرد.

۲- فقط عکس های خوب چاپ میشود، در نتیجه کاربر میتواند تعداد زیادی عکس با اختلافات جزئی و تنظیمات مختلف از یک صحنه بگیرد و بعد بهترین ان را انتخاب و چاپ کند.

۳- اگر شخص رایانه داشته باشد ، ذخیره دائم عکسها ارزانتر از فیلم از کار در می اید.

۴- عکس ها را میتوان از یک جایی به جای دیگر کپی کرد بدون اینکه کیفیت ان کاهش یابد.

۵- هر کس میتواند با داشتن رایانه و یک پرینتر معمولی ، عکس های خودش را چاپ کند. تازه با استفاده از پرینت های مخصوص دوربین ها ، به رایانه هم نیازی نیست و دوربین را میتوان مستقیما به پرینتر وصل کرد.

۶- دوربین های دیجیتال میتوانند کوچکتر از دوربین های انالوگ با همان کیفیت عکس ، ساخته شوند.

۷- قابلیت استفاده کردن داده هایی مثل زمان و تاریخ عکاسی، مدل دوربین، سرعت شاتر، سرعت فیلم و دیگر موارد به فایل عکس مورد نظر، در حالی که این قابلیت در فیلم های عکاسی فقط به چاپ تاریخ روی عکس ها محدود میشود.

۸- در دوربین های دیجیتال از خیلی افکت های تصویری میتوان استفاده کرد که در دوربین های فیلمی امکان ندارد.

۹- قابلیت گرفتن صدها عکس بدون این که نیازی به تغییر چیزی باشد. در حالی که در دوربین های فیلمی بعد از ۲۴ یا ۳۶ عکس باید فیلم را عوض کرد.

۱۰- خیلی از دوربین های دیجیتال،خروجی AV دارند که نشان دادن عکس ها را به دیگران در تلویزیون ممکن میسازد.

۱۱- عکاسی دیجیتال این امکان را فراهم میکند که شما تنظیمات مختلف دوربین و سبکهای مختلف عکاسی را تجزیه کنید و تکنیک عکاسی تان را بهبود بخشید ، بدون این که لازم باشد هزینه زیادی بدهید از نظر زمان و از نظر وقت و انرژی.

۱۲- ابزار ضد تکان دوربین های دیجیتال ، گرفتن عکس های ترو تمیز با دوربین را روی دست ممکن میکند در حالی که قبلا حتما نیاز به سه پایه بود.

۱۳- دیگر هر کس یک تاریک خانه ، خانگی دارد و میتواند با رایانه و نرم افزار تغییرات لازم را در عکس ها بدهد.

۱۴- مقادیر ISO را به راحتی میتوان در وسط عکس تغییر داد. مثلا وقتی هوا افتابی است ولی یکدفعه ابری میشود ، قبلا لازم بود که فیلم را دربیاورید و فیلم جدید با مقدار ISO مناسب را داخل دوربین بگذارید ولی با دوربین دیجیتال این تغییرات فقط با فشار چند دگمه انجام میشود.

۱۵- برای فرستادن عکسها به داخل رایانه ، دیگر نیازی به اسکنر نیست.

 ویژگی های منفی دوربین دیجیتال نسبت به دوربین آنالوگ :

۱- مصرف انرژی باطری های دوربین های دیجیتال نسبت به دوربین های فیلم دار خیلی بیشتر است در نتیجه ممکن است وسط عکاسی یک دفعه با دوربین خاموش مواجه شویم.

۲- استناد عکس های دیجیتال نسبت به عکس های فیلمی کمتر است چون میشود در انها دستکاری کرد البته بعضی از تولید کنندگان تلاش میکنند به روشهایی برای تشخیص عکس های دستکاری شده برسند تا این ضعف را جبران کنند.

۳- سنسورهای دیجیتال اغلب دامنه دینامیکی کمتری نسبت به فیلم چاپی رنگی دارند البته تعدادی از سنسور های CCD جدیدتر مثل FUJIS SUPER CCD که دیودها با حساسیت های مختلف را با هم ترکیب کرده اند برای حل این مشکل به میدان امده اند.

۴- در بعضی از عکس های دیجیتالی نویز تصویری چند رنگ قابل مشاهده است.

۵- ویرایش و پردازش فایل های RAW ( فایلهای حرفه ای عکاسی ) خیلی طول می کشد.

۶- برای عکاسی در محل های پرت و دور افتاده عکاس باید کلی باطری با خودش حمل کند که وزن بار عکاس را افزایش میدهد و کار را برای او سخت تر میکند.

تفاوت تصویر دوربین آنالوگ با دوربین دیجیتال

منبع : http://www.kingit.ir

دلایل استفاده از دوربین آنالوگ توسط کاربران امروزی

۱ . فیلم همچنان خوب است

همه ما کمابیش شانس آن را داشته‌ایم که دوره عکاسی فیلم را تجربه کنیم. عکس‌های خانوادگی ما شامل سفرها و گردش ها و جشن‌های خانوادگی همگی با دوربین‌های آنالوگ و فیلم عکاسی شده‌اند. اما همچنان که آلبوم عکس‌های آ سال‌ها را نگاه می‌کنیم، متوجه کیفیت بارز و خوب عکس‌ها می‌شویم. همچنان عکس‌ها آنقدر کیفیت دارند که با عکس‌های بهترین دوربین‌های دیجیتال امروز برابری کنند.

فیلم همچنان خوب است

امروزه با وجود اینکه ما عکس‌های زیادی چه با دوربین و چه با اسمارتفون خود ثبت می‌کنیم، اما تقریباً آلبومی برای نگهداری آنها نداریم. به همین دلیل یکی از دغدغه‌های ما نگهداری و ساماندهی عکس‌ها است. با این حال هیچ وقت این اتفاق نمی‌افتد. اما در گذشته اینطور نبود و با گرفتن عکس، پس از چاپ عکس‌ها به صورت منظم به آلبوم عکس منتقل می‌شدند. همچنان که ما امروز از دوره فیلم عکس بیش‌تری می‌بینیم.

۲ . «داینامیک رنج» فیلم بیش‌تر است

اشتباه نکنید، تکنیک عکس «HDR» مدتها است که در عکاسی وجود دارد، اما در عکاسی دیجیتال مدت کوتاهی است که باب شده‌است. در عکاسی دیجیتال به دلیل محدودیت‌های موجود، برای رسیدن به یک عکس HDR معقول باید از یک صحنه ۳ عکس با نوردهی‌های متفاوت ثبت کرد. اما عکسی که با فیلم ثبت شده‌باشد، آنقدر داینامیک رنج بالایی دارد که به تنهایی می‌توان آن را به عکس HDR تبدیل کرد.

«داینامیک رنج» فیلم بیش‌تر است

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

۳ . فیلم باعث آرامش می‌شود

وقتی با عکاسی دیجیتال طرف هستیم، از یک سوژه ده‌ها عکس با ترکیب‌بندی‌های مختلف ثبت می‌کنیم. با این وجود باز هم دقت کافی در ثبت عکس نکرده و بسیاری از مشکلات عکس را به فتوشاپ می‌سپاریم.

فیلم باعث آرامش می‌شود

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

۴ . فیلم امنیت بیش‌تری دارد

قدیمی‌ترین عکس دیجیتالی که دارید متعلق به چه زمانی‌است؟ آن را در کجا ذخیره کرده‌اید؟ با وجود پیشرفت بسیار زیاد فناوری، اما همچنان باید گفت که عکس‌های دیجیتال امنیت لازم را ندارند. آنها تنها چند فایل کامپیوتری هستند که با یک اتفاق ساده امکان از بین رفتن و یا آسیب دیدن آنها وجود دارد. حتی نظریه‌ای وجود دارد که ممکن است قرن حاضر به قرن فراموش شده تبدیل شود.

فیلم امنیت بیش‌تری دارد

در مورد فیلم قضیه کاملاً متفاوت است. به صورت اولیه عکس‌ها چاپ می‌شوند و همیشه نسخه‌ واقعی از آنها وجود دارد. علاوه بر این نگاتیو عکس‌ها نیز آرشیو می‌شوند و می‌توانند دوباره چاپ شوند. همانطور که می‌دانید درحال حاضر هر نگاتیوی که از گذشته پیدا شود به راحتی قابل چاپ و حتی ترمیم است. مثال آن عکس‌هایی است که از بیش از ۱۰۰ پیش کشف می‌شوند و به راحتی و با کیفیت بالا چاپ می‌شوند.

۵ . فرآیند ظهور و چاپ در فیلم بسیار لذت بخش است

یکی از جواب‌هایی که برخی از عکاسان به ما دادند، دلبستگی آنها به فرآیند ظهور فیلم و چاپ عکس بود. در عکاسی دیجیتال به محض گرفتن عکس می‌توانید نتیجه کار را ببینید. اما در فیلم این پروسه کاملاً متفاوت است.

فرآیند ظهور و چاپ در فیلم بسیار لذت بخش است

پس از ثبت عکس باید آن را ظهور کرد. مراحل ظهور فیلم و استفاده از داروهای مختلف برای بسیاری از عکاسان بسیار دلپذیر و آرام‌بخش است. پس از طری مراحل ظهور با مرحله چاپ طرف هستیم که در آنجا به مرور عکس در مقابل عکاس شکل می‌گیرد و این دلچسب‌ترین لحظه کار است. لحظه‌ای که نتیجه کار دیده می‌شود.

۶ . نیازی به برق ندارید

تصور کنید دنیا به پایان خود رسیده‌است و شرایط طوری است که برقی برای استفاده وجود ندارد؛ چگونه باطری دوربین خود را شارژ خواهید کرد؟ خارج از شوخی، باید اذعان کرد که بشر امروز به شکل کامل وابسته به انرژی برق است و اگر در شرایطی قرار بگیرد که برق در دسترس نباشد، عملاً از زندگی ساقط می‌شود. در مورد عکاسی دیجیتال هم این قضیه صدق می‌کند و به محض نبود برق، عملاً عکاسی هم تعطیل است.

نیازی به برق ندارید

فرآیند عکاسی با فیلم از ابتدا تا انتها هیچ وابستگی‌ای به برق ندارد. تمام اتفاقاتی که بر روی فیلم می‌افتد یک فرآیند شیمیایی است که در واکنش به نور اتفاق می‌افتد. از این‌رو در هر شرایطی امکان عکاسی وجود دارد.

۷ . فیلم «چشم ‌نواز» تر است

حتی اگر طرفدار این نظریه هم نباشید، حتماً قبول دارید که فیلم رنگ و حسی متفاوت از عکس دیجیتال دارد. درحال حاضر فیلترهای مختلفی بر روی نرم‌افزارهای مختلف برای اعمال بر روی عکس‌های دیجیتال وجود دارد تا آنها را به فیلم شبیه کند. اما واقعیت این است که با وجود نتایج خوب، همچنان فیلم حسی متفاوت دارد که با دیجیتال قابل دستیابی نیست.

فیلم «چشم ‌نواز» تر است

دلیل همه این‌ها فرآیند کاملاً متفاوت فیلم و دیجیتال در ثبت یک عکس است. بسیاری اعتقاد دارند که این چشم‌نواز تر بودن فیلم، به دلیل شبیه‌ت بودن آن به آنچیزی است که با چشم خود می‌بینیم.

۸ . عکس دیجیتال واقعیت جعلی است

همانطور که می‌دانید یک عکس دیجیتال از مجموعه‌ای از پیکسل‌ها تشکیل می‌شود. این پیکسل‌ها به خودی خود هویتی ندارند و تنها نمایش دهنده یک رنگ هستند. از این رو بسیاری بر این باورند که عکس دیجیتال تنها یک واقعیت جعلی هستند و به همین دلیل آنها را رد می‌کنند.

عکس دیجیتال واقعیت جعلی است

در فیلم قضیه متفاوت است. هر فیلم تشکیل شده از بلورهای ریزی است که در اندازه‌های متفاوت و به شکلی نامنظم بر روی سطح فیلم قرار گرفته‌اند. حتی اگر با یک میکروسکوپ هم سطح یک فیلم را نگاه کنید، باز هم به یک شمای کلی از تصویر می‌رسید، اما در عهکس دیجیتال تنها با چند موزاییک طرف خواهید شد.

۹ . دوربین‌های فیلم ارزان‌تر هستند

در حال حاضر اگر بخواهید جدید‌ترین دوربین‌های دیجیتال DSLR را بخرید باید بیش از ۳ هزار دلار هزینه کنید و این مبلغ تنها برای بدنه آنها است. هزینه دیگری هم باید برای خرید لنز در نظر بگیرید. حتی دوربین‌های حد متوسط و یا دست دوم‌ها هم گران هستند.

دوربین‌های فیلم ارزان‌تر هستند

دوربین‌های فیلم اما در بهترین کیفیتشان با کمترین قیمت قابل خرید هستند. هزینه‌ای که برای خرید بهترین دوربین فیلم خواهید کرد کمتر از یک دهم خرید بهترین دوربین DSLR است.

۱۰ . برای متفاوت بودن

در حال حاضر روزانه میلیون‌ها عکس در شبکه‌های اجتماعی و وبسایت‌ها منتشر می‌شود. گرفتن عکسی که از میان این همه عکس قابل توجه باشد نیازمند منحصر بفرد بودن و تفاوت است. یکی از راه‌های ایجاد تفاوت در عکس، ثبت آنها با فیلم است.

برای متفاوت بودن

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

۱۱ . به خاطر ایرادهایش

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

به خاطر ایرادهایش

۱۲ . به دلیل مرموز بودن فیلم

وقتی عکس دیجیتال می‌گیرید، به محض ثبت عکس آن را در نمایشگر دوربین می‌بینید. اما یکی از جذابیت‌های عکاسی فیلم، در نداشتن همین قابلیت است.وقتی با فیلم عکس ثبت می‌کنید باید صبر کنید تا فیلم ظهور شود و چاپ شود تا نتیجه کار مشخص شود.

به دلیل مرموز بودن فیلم

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

 


منابع

۱٫http://fa.wikipedia.org

۲٫ http://forum.avastarco.com

۳٫ http://www.kingit.ir

۴٫ http://farnet.ir