نوشته‌ها

کار با Thread ها در زبان سی شارپ – آشنایی با فضای نام System.Threading و کلاس Thread

تا اینجا متوجه شدیم که چگونه می توان با کمک Delegate ها کدها را در یک Thread جداگانه و به صورت Asynchrnonous اجرا کرد. در ادامه مباحث مرتبط با برنامه نویسی Asynchronous به سراغ فضای نام System.Threading می رویم. این فضای نام شامل یکسری کلاس است که روند نوشتن برنامه Multi-Threaded را آسان می کند. کلاس های زیادی در این فضای نام وجود دارد که هر یک استفاده خاص خودش را دارد. در زیر با توضیح اولیه برخی از کلاس های این فضای نام آشنا می شویم:

  1. Interlocked: از این کلاس برای اجرای عملیات های atomic یا Atomic Operations بر روی متغیرهایی که در بین چندین Thread به اشتراک گذاشته شدند استفاده می شود.
  2. Monitor: از این کلاس برای پیاده سازی Synchronization بر روی اشیاء ای که Thread به آن دسترسی دارند استفاده می شود. در سی شارپ کلمه کلیدی lock در پشت زمینه از مکانیزم Monitor برای Synchronization استفاده می کنید که در بخش های بعدی با این تکنیک بیشتر آشنا می شویم.
  3. Mutex: از این کلاس برای اعمال Synchronization بین AppDomain ها استفاده می شود.
  4. ParameterizedThreadStart: این delegate به thread ها این اجازه را می دهد تا متدهایی پارامتر ورودی دارند را فراخوانی کند.
  5. Semaphor: از این کلاس برای محدود کردن تعداد Thread هایی که می توانند به یک Resource دسترسی داشته باشند استفاده می شود.
  6. Thread: به ازای هر Thread ایجاد شده برای برنامه باید یک کلاس از نوع Thread ایجاد کرد. در حقیقت کلاس Thread نقش اصلی را در ایجاد و استفاده از Thread ها دارد.
  7. ThreadPool: بوسیله این کلاس می توان به Thread-Pool مدیریت شده توسط خود CLR دسترسی داشت.
  8. ThreadPriority: بوسیله این enum می توان درجه اهمیت یک Thread را مشخص می کند.
  9. ThreadStart: از این delegate برای مشخص کردن متدی که در Thread باید اجرا شود استفاده می شود. این delegate بر خلاف ParameterizedThreadStart پارامتری قبول نمیکند.
  10. ThreadState: بوسیله این enum می توان وضعیت جاری یک thread را مشخص کرد.
  11. Timer: بوسیله کلاس Timer می توان مکانیزمی پیاده کرد که کدهای مورد نظر در بازه های زمانی خاص، مثلاً هر ۵ ثانیه یکبار و در یک Thread مجزا اجرا شوند.
  12. TimerCallBack: از این delegate برای مشخص کردن کدی که داخل timer باید اجرا شود استفاده می شود.

کلاس System.Threading.Thread

کلاس Thread اصلی ترین کلاس موجود در فضای نام System.Threading است. از یک کلاس برای دسترسی به Thread هایی که در روند اجرای یک AppDomain ایجاد شده اند استفاده می شود. همچنین بوسیله این کلاس می تواند Thread های جدید را نیز ایجاد کرد. کلاس Thread شامل یکسری متد و خصوصیت است که در این قسمت می خواهیم با آن ها آشنا شویم. ابتدا به سراغ خصوصیت CurrentThread که یک خصوصیت static در کلاس Thread است می رویم. بوسیله این خصوصیت می توان اطلاعات Thread جاری را بدست آورد. برای مثال در صورتی که در متد Main از این خصوصیت استفاده شود می توان به اطلاعات مربوط به Thread اصلی برنامه دسترسی داشت یا اگر برای یک متد Thread جداگانه ای ایجاد شود، در صورت استفاده از این خصوصیت در بدنه متد به اطلاعات Thread ایجاد شده دسترسی خواهیم داشت. در ابتدا با یک مثال می خواهیم اطلاعات Thread اصلی برنامه را بدست آوریم:

var primaryThread = Thread.CurrentThread;
primaryThread.Name = "PrimaryThread";
Console.WriteLine("Thread Name: {0}", primaryThread.Name);
Console.WriteLine("Thread AppDomain: {0}", Thread.GetDomain().FriendlyName);
Console.WriteLine("Thread Context Id: {0}", Thread.CurrentContext.ContextID);
Console.WriteLine("Thread Statred: {0}", primaryThread.IsAlive);
Console.WriteLine("Thread Priority: {0}", primaryThread.Priority);
Console.WriteLine("Thread State: {0}", primaryThread.ThreadState);

دقت کنید در ابتدا با به Thread یک نام دادیم. در صورتی که نامی برای Thread انتخاب نشود خصوصیت Name مقدار خالی بر میگرداند. مهمترین مزیت تخصیص نام برای Thread راحت تر کردن امکان debug کردن کد است. در Visual Studio پنجره ای وجود دارد به نام Threads که می توانید در زمان اجرای برنامه از طریق منوی Debug->Windows->Threads به آن دسترسی داشته باشید. در تصویر زیر نمونه ای از این پنجره را در زمان اجرا مشاهده می کنید:

آشنایی با فضای نام System.Threading و کلاس Thread

اما بریم سراغ موضوع اصلی، یعنی ایجاد Thread و اجرای آن. در ابتدا در مورد مراحل ایجاد یک Thread صحبت کنیم، معمولاً برای ایجاد یک Thread مراحل زیر را باید انجام دهیم:

  1. در ابتدا باید متدی ایجاد کنیم که وظیفه آن انجام کاری است که قرار است در یک Thread جداگانه انجام شود.
  2. در مرحله بعد باید یکی از delegate های ParameterizedThreadStart برای متدهایی که پارامتر ورودی دارند یا ThreadStart برای متدهای بدون پارامتر را انتخاب کرده و یک شئ از آن ایجاد کنیم که به عنوان سازنده متد مورد نظر به آن پاس داده می شود.
  3. از روی کلاس Thread یک شئ جدید ایجاد کرده و به عنوان سازنده شئ ای که از روی delegate های گفته شده در مرحله ۲ ساختیم را به آن ارسال کنیم.
  4. اطلاعات و تنظیمات اولیه مورد نظر برای Thread مانند Name یا Priority را برای آن ست کنیم.
  5. متد Start را در کلاس Thread را برای شروع کار Thread فراخوانی کنیم. با این کار متدی که در مرحله ۲ مشخص کردیم در یک Thread جداگانه اجرا می شود.

دقت کنید در مرحله ۲ می بایست بر اساس signature متدی که قصد اجرای آن در thread جداگانه را داریم، delegate مناسب انتخاب شود. همچنین ParameterizedThreadStart پارامتری که به عنوان ورودی قبول می کند از نوع Object است، یعنی اگر می خواهید چندین پارامتر به آن ارسال کنید می بایست حتماً یک کلاس یا struct ایجاد کرده و آن را به عنوان ورودی به کلاس Start ارسال کنید. با یک مثال ساده که از ThreadStart برای اجرای Thread استفاده می کند شروع می کنیم:

static void Main(string[] args)
{
    ThreadStart threadStart = new ThreadStart(PrintNumbers);
    Thread thread = new Thread(threadStart);
    thread.Name = "PrintNumbersThread";
    thread.Start();
    while (thread.IsAlive)
    {
        Console.WriteLine("Running in primary thread...");
        Thread.Sleep(2000);
    }
    Console.WriteLine("All done.");
    Console.ReadKey();
}

public static void PrintNumbers()
{
    for (int counter = 0; counter < 10; counter++)
    {
        Console.WriteLine("Running from thread: {0}", counter + 1);
        Thread.Sleep(500);
    }
}

در ابتدا متدی تعریف کردیم با نام PrintNumbers که قرار است در یک Thread مجزا اجرا شود. همانطور که مشاهده می کنید این متد نه پارامتر ورودی دارد و نه مقدار خروجی، پس از ThreadStart استفاده می کنیم. بعد از ایجاد شئ از روی ThreadStart و ایجاد Thread، نام Thread را مشخص کرده و متد Start را فراخوانی کردیم. به حلقه while ایجاد شده دقت کنید، در این حلقه بوسیله خصوصیت IsAlive گفتیم تا زمانی که Thread ایجاد شده در حال اجرا است کد داخل while اجرا شود. همچنین بوسیله متد Sleep در متد Main و متد PrintNumbers در عملیات اجرا برای Thread های مربوط به متد تاخیر ایجاد کردیم. بعد اجرای کد بالا خروجی زیر نمایش داده می شود:

Running in primary thread...
Running from thread: 1
Running from thread: 2
Running from thread: 3
Running from thread: 4
Running in primary thread...
Running from thread: 5
Running from thread: 6
Running from thread: 7
Running from thread: 8
Running in primary thread...
Running from thread: 9
Running from thread: 10
All done.

در قدم بعدی فرض کنید که قصد داریم بازه اعدادی که قرار است در خروجی چاپ شود را به عنوان پارامتر ورودی مشخص کنیم، در اینجا ابتدا یک کلاس به صورت زیر تعریف می کنیم:

public class PrintNumberParameters
{
    public int Start { get; set; }
    public int Finish { get; set; }
}

در قدم بعدی کلاس PrintNumbers را به صورت زیر تغییر می دهیم:

public static void PrintNumbers(object data)
{
    PrintNumberParameters parameters = (PrintNumberParameters) data;
    for (int counter = parameters.Start; counter < parameters.Finish; counter++)
    {
        Console.WriteLine("Running from thread: {0}", counter);
        Thread.Sleep(500);
    }
}

همانطور که مشاهده می کنید، پارامتر ورودی PrintNumbers از نوع object است و در بدنه ورودی را به کلاس PrintNumberParameters تبدیل کرده و از آن استفاده کردیم. در مرحله بعد متد Main را باید تغییر داده و به جای ThreadStart از ParameterizedThreadStart استفاده کنیم، همچنین به عنوان پارامتر ورودی برای متد Start شئ ای از PrintNumberParameters ایجاد کرده و با عنوان پارامتر به آن ارسال می کنیم:

ParameterizedThreadStart threadStart = new ParameterizedThreadStart(PrintNumbers);
Thread thread = new Thread(threadStart);
thread.Name = "PrintNumbersThread";
thread.Start(new PrintNumberParameters() {Start = 5, Finish = 13});
while (thread.IsAlive)
{
    Console.WriteLine("Running in primary thread...");
    Thread.Sleep(2000);
}
Console.WriteLine("All done.");
Console.ReadKey();

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

منبع


قسمت اول آموزش-برنامه نویسی 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 به صورت همزمان در سی شارپ

مقدمه

حذف نویز تصاویر _ گروهی از محققان سیستمی را توسعه داده اند که با استفاده از هوش مصنوعی و بدون نیاز به عکس های واضح از منبع، نویز تصاویر را از بین می برد.

شرح خبر

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

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

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

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

 

مرحله ۳: ردیابی در امتداد لبه ها

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

 

#include "stdafx.h"
#include "tripod.h"
#include "tripodDlg.h"

#include "LVServerDefs.h"
#include "math.h"
#include <fstream>
#include <string>
#include <iostream>
#include <stdlib.h>
#include <stdio.h>


#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

using namespace std;

/////////////////////////////////////////////////////////////////////////////
// CAboutDlg dialog used for App About

class CAboutDlg : public CDialog
{
public:
	CAboutDlg();

// Dialog Data
	//{{AFX_DATA(CAboutDlg)
	enum { IDD = IDD_ABOUTBOX };
	//}}AFX_DATA

	// ClassWizard generated virtual function overrides
	//{{AFX_VIRTUAL(CAboutDlg)
	protected:
	virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support
	//}}AFX_VIRTUAL

// Implementation
protected:
	//{{AFX_MSG(CAboutDlg)
	//}}AFX_MSG
	DECLARE_MESSAGE_MAP()
};

CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
{
	//{{AFX_DATA_INIT(CAboutDlg)
	//}}AFX_DATA_INIT
}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
	//{{AFX_DATA_MAP(CAboutDlg)
	//}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
	//{{AFX_MSG_MAP(CAboutDlg)
		// No message handlers
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CTripodDlg dialog

CTripodDlg::CTripodDlg(CWnd* pParent /*=NULL*/)
	: CDialog(CTripodDlg::IDD, pParent)
{
	//{{AFX_DATA_INIT(CTripodDlg)
		// NOTE: the ClassWizard will add member initialization here
	//}}AFX_DATA_INIT
	// Note that LoadIcon does not require a subsequent DestroyIcon in Win32
	m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);

	//////////////// Set destination BMP to NULL first 
	m_destinationBitmapInfoHeader = NULL;

}

////////////////////// Additional generic functions

static unsigned PixelBytes(int w, int bpp)
{
    return (w * bpp + 7) / 8;
}

static unsigned DibRowSize(int w, int bpp)
{
    return (w * bpp + 31) / 32 * 4;
}

static unsigned DibRowSize(LPBITMAPINFOHEADER pbi)
{
    return DibRowSize(pbi->biWidth, pbi->biBitCount);
}

static unsigned DibRowPadding(int w, int bpp)
{
    return DibRowSize(w, bpp) - PixelBytes(w, bpp);
}

static unsigned DibRowPadding(LPBITMAPINFOHEADER pbi)
{
    return DibRowPadding(pbi->biWidth, pbi->biBitCount);
}

static unsigned DibImageSize(int w, int h, int bpp)
{
    return h * DibRowSize(w, bpp);
}

static size_t DibSize(int w, int h, int bpp)
{
    return sizeof (BITMAPINFOHEADER) + DibImageSize(w, h, bpp);
}

/////////////////////// end of generic functions


void CTripodDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
	//{{AFX_DATA_MAP(CTripodDlg)
	DDX_Control(pDX, IDC_PROCESSEDVIEW, m_cVideoProcessedView);
	DDX_Control(pDX, IDC_UNPROCESSEDVIEW, m_cVideoUnprocessedView);
	//}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CTripodDlg, CDialog)
	//{{AFX_MSG_MAP(CTripodDlg)
	ON_WM_SYSCOMMAND()
	ON_WM_PAINT()
	ON_WM_QUERYDRAGICON()
	ON_BN_CLICKED(IDEXIT, OnExit)
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CTripodDlg message handlers

BOOL CTripodDlg::OnInitDialog()
{
	CDialog::OnInitDialog();

	// Add "About..." menu item to system menu.

	// IDM_ABOUTBOX must be in the system command range.
	ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
	ASSERT(IDM_ABOUTBOX < 0xF000);

	CMenu* pSysMenu = GetSystemMenu(FALSE);
	if (pSysMenu != NULL)
	{
		CString strAboutMenu;
		strAboutMenu.LoadString(IDS_ABOUTBOX);
		if (!strAboutMenu.IsEmpty())
		{
			pSysMenu->AppendMenu(MF_SEPARATOR);
			pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
		}
	}

	// Set the icon for this dialog.  The framework does this automatically
	//  when the application's main window is not a dialog
	SetIcon(m_hIcon, TRUE);			// Set big icon
	SetIcon(m_hIcon, FALSE);		// Set small icon
	
	// TODO: Add extra initialization here

	// For Unprocessed view videoportal (top one)
	char sRegUnprocessedView[] = "HKEY_LOCAL_MACHINE\\Software\\UnprocessedView";
	m_cVideoUnprocessedView.PrepareControl("UnprocessedView", sRegUnprocessedView, 0 );	
	m_cVideoUnprocessedView.EnableUIElements(UIELEMENT_STATUSBAR,0,TRUE);
	m_cVideoUnprocessedView.ConnectCamera2();
	m_cVideoUnprocessedView.SetEnablePreview(TRUE);

	// For binary view videoportal (bottom one)
	char sRegProcessedView[] = "HKEY_LOCAL_MACHINE\\Software\\ProcessedView";
	m_cVideoProcessedView.PrepareControl("ProcessedView", sRegProcessedView, 0 );	
	m_cVideoProcessedView.EnableUIElements(UIELEMENT_STATUSBAR,0,TRUE);
	m_cVideoProcessedView.ConnectCamera2();
	m_cVideoProcessedView.SetEnablePreview(TRUE);

	// Initialize the size of binary videoportal
	m_cVideoProcessedView.SetPreviewMaxHeight(240);
	m_cVideoProcessedView.SetPreviewMaxWidth(320);

	// Uncomment if you wish to fix the live videoportal's size
	// m_cVideoUnprocessedView.SetPreviewMaxHeight(240);
	// m_cVideoUnprocessedView.SetPreviewMaxWidth(320);

	// Find the screen coodinates of the binary videoportal
	m_cVideoProcessedView.GetWindowRect(m_rectForProcessedView);
	ScreenToClient(m_rectForProcessedView);
	allocateDib(CSize(320, 240));

	// Start grabbing frame data for Procssed videoportal (bottom one)
	m_cVideoProcessedView.StartVideoHook(0);

	return TRUE;  // return TRUE  unless you set the focus to a control
}

void CTripodDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
	if ((nID & 0xFFF0) == IDM_ABOUTBOX)
	{
		CAboutDlg dlgAbout;
		dlgAbout.DoModal();
	}
	else
	{
		CDialog::OnSysCommand(nID, lParam);
	}
}

// If you add a minimize button to your dialog, you will need the code below
//  to draw the icon.  For MFC applications using the document/view model,
//  this is automatically done for you by the framework.

void CTripodDlg::OnPaint() 
{
	if (IsIconic())
	{
		CPaintDC dc(this); // device context for painting

		SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);

		// Center icon in client rectangle
		int cxIcon = GetSystemMetrics(SM_CXICON);
		int cyIcon = GetSystemMetrics(SM_CYICON);
		CRect rect;
		GetClientRect(&rect);
		int x = (rect.Width() - cxIcon + 1) / 2;
		int y = (rect.Height() - cyIcon + 1) / 2;

		// Draw the icon
		dc.DrawIcon(x, y, m_hIcon);
	}
	else
	{
		CDialog::OnPaint();
	}
}

// The system calls this to obtain the cursor to display while the user drags
//  the minimized window.
HCURSOR CTripodDlg::OnQueryDragIcon()
{
	return (HCURSOR) m_hIcon;
}

void CTripodDlg::OnExit() 
{
	// TODO: Add your control notification handler code here

	// Kill live view videoportal (top one)
	m_cVideoUnprocessedView.StopVideoHook(0);
    m_cVideoUnprocessedView.DisconnectCamera();	
	
	// Kill binary view videoportal (bottom one)
	m_cVideoProcessedView.StopVideoHook(0);
    m_cVideoProcessedView.DisconnectCamera();	

	// Kill program
	DestroyWindow();	

	

}

BEGIN_EVENTSINK_MAP(CTripodDlg, CDialog)
    //{{AFX_EVENTSINK_MAP(CTripodDlg)
	ON_EVENT(CTripodDlg, IDC_PROCESSEDVIEW, 1 /* PortalNotification */, OnPortalNotificationProcessedview, VTS_I4 VTS_I4 VTS_I4 VTS_I4)
	//}}AFX_EVENTSINK_MAP
END_EVENTSINK_MAP()

void CTripodDlg::OnPortalNotificationProcessedview(long lMsg, long lParam1, long lParam2, long lParam3) 
{
	// TODO: Add your control notification handler code here
	
	// This function is called at the camera's frame rate
    
#define NOTIFICATIONMSG_VIDEOHOOK	۱۰

	// Declare some useful variables
	// QCSDKMFC.pdf (Quickcam MFC documentation) p. 103 explains the variables lParam1, lParam2, lParam3 too 
	
	LPBITMAPINFOHEADER lpBitmapInfoHeader; // Frame's info header contains info like width and height
	LPBYTE lpBitmapPixelData; // This pointer-to-long will point to the start of the frame's pixel data
    unsigned long lTimeStamp; // Time when frame was grabbed

	switch(lMsg) {
		case NOTIFICATIONMSG_VIDEOHOOK:
			{
				lpBitmapInfoHeader = (LPBITMAPINFOHEADER) lParam1; 
				lpBitmapPixelData = (LPBYTE) lParam2;
				lTimeStamp = (unsigned long) lParam3;

				grayScaleTheFrameData(lpBitmapInfoHeader, lpBitmapPixelData);
				doMyImageProcessing(lpBitmapInfoHeader); // Place where you'd add your image processing code
				displayMyResults(lpBitmapInfoHeader);

			}
			break;

		default:
			break;
	}	
}

void CTripodDlg::allocateDib(CSize sz)
{
	// Purpose: allocate information for a device independent bitmap (DIB)
	// Called from OnInitVideo

	if(m_destinationBitmapInfoHeader) {
		free(m_destinationBitmapInfoHeader);
		m_destinationBitmapInfoHeader = NULL;
	}

	if(sz.cx | sz.cy) {
		m_destinationBitmapInfoHeader = (LPBITMAPINFOHEADER)malloc(DibSize(sz.cx, sz.cy, 24));
		ASSERT(m_destinationBitmapInfoHeader);
		m_destinationBitmapInfoHeader->biSize = sizeof(BITMAPINFOHEADER);
		m_destinationBitmapInfoHeader->biWidth = sz.cx;
		m_destinationBitmapInfoHeader->biHeight = sz.cy;
		m_destinationBitmapInfoHeader->biPlanes = 1;
		m_destinationBitmapInfoHeader->biBitCount = 24;
		m_destinationBitmapInfoHeader->biCompression = 0;
		m_destinationBitmapInfoHeader->biSizeImage = DibImageSize(sz.cx, sz.cy, 24);
		m_destinationBitmapInfoHeader->biXPelsPerMeter = 0;
		m_destinationBitmapInfoHeader->biYPelsPerMeter = 0;
		m_destinationBitmapInfoHeader->biClrImportant = 0;
		m_destinationBitmapInfoHeader->biClrUsed = 0;
	}
}

void CTripodDlg::displayMyResults(LPBITMAPINFOHEADER lpThisBitmapInfoHeader)
{
	// displayMyResults: Displays results of doMyImageProcessing() in the videoport
	// Notes: StretchDIBits stretches a device-independent bitmap to the appropriate size

	CDC				*pDC;	// Device context to display bitmap data
	
	pDC = GetDC();	
	int nOldMode = SetStretchBltMode(pDC->GetSafeHdc(),COLORONCOLOR);

	StretchDIBits( 
		pDC->GetSafeHdc(),
		m_rectForProcessedView.left,				// videoportal left-most coordinate
		m_rectForProcessedView.top,					// videoportal top-most coordinate
		m_rectForProcessedView.Width(),				// videoportal width
		m_rectForProcessedView.Height(),			// videoportal height
		۰,											// Row position to display bitmap in videoportal
		۰,											// Col position to display bitmap in videoportal
		lpThisBitmapInfoHeader->biWidth,			// m_destinationBmp's number of columns
		lpThisBitmapInfoHeader->biHeight,			// m_destinationBmp's number of rows
		m_destinationBmp,							// The bitmap to display; use the one resulting from doMyImageProcessing
		(BITMAPINFO*)m_destinationBitmapInfoHeader, // The bitmap's header info e.g. width, height, number of bits etc
		DIB_RGB_COLORS,								// Use default 24-bit color table
		SRCCOPY										// Just display
	);
 
	SetStretchBltMode(pDC->GetSafeHdc(),nOldMode);

	ReleaseDC(pDC);

	// Note: 04/24/02 - Added the following:
	// Christopher Wagner cwagner@fas.harvard.edu noticed that memory wasn't being freed

	// Recall OnPortalNotificationProcessedview, which gets called everytime
	// a frame of data arrives, performs 3 steps:
	// (۱) grayScaleTheFrameData - which mallocs m_destinationBmp
	// (۲) doMyImageProcesing
	// (۳) displayMyResults - which we're in now
	// Since we're finished with the memory we malloc'ed for m_destinationBmp
	// we should free it: 
	
	free(m_destinationBmp);

	// End of adds
}

void CTripodDlg::grayScaleTheFrameData(LPBITMAPINFOHEADER lpThisBitmapInfoHeader, LPBYTE lpThisBitmapPixelData)
{

	// grayScaleTheFrameData: Called by CTripodDlg::OnPortalNotificationBinaryview
	// Task: Read current frame pixel data and computes a grayscale version

	unsigned int	W, H;			  // Width and Height of current frame [pixels]
	BYTE            *sourceBmp;		  // Pointer to current frame of data
	unsigned int    row, col;
	unsigned long   i;
	BYTE			grayValue;

	BYTE			redValue;
	BYTE			greenValue;
	BYTE			blueValue;

    W = lpThisBitmapInfoHeader->biWidth;  // biWidth: number of columns
    H = lpThisBitmapInfoHeader->biHeight; // biHeight: number of rows

	// Store pixel data in row-column vector format
	// Recall that each pixel requires 3 bytes (red, blue and green bytes)
	// m_destinationBmp is a protected member and declared in binarizeDlg.h

	m_destinationBmp = (BYTE*)malloc(H*3*W*sizeof(BYTE));

	// Point to the current frame's pixel data
	sourceBmp = lpThisBitmapPixelData;

	for (row = 0; row < H; row++) {
		for (col = 0; col < W; col++) {

			// Recall each pixel is composed of 3 bytes
			i = (unsigned long)(row*3*W + 3*col);
        
			// The source pixel has a blue, green andred value:
			blueValue  = *(sourceBmp + i);
			greenValue = *(sourceBmp + i + 1);
			redValue   = *(sourceBmp + i + 2);

			// A standard equation for computing a grayscale value based on RGB values
			grayValue = (BYTE)(0.299*redValue + 0.587*greenValue + 0.114*blueValue);

			// The destination BMP will be a grayscale version of the source BMP
			*(m_destinationBmp + i)     = grayValue;
			*(m_destinationBmp + i + 1) = grayValue;
			*(m_destinationBmp + i + 2) = grayValue;
			
		}
	}
}


void CTripodDlg::doMyImageProcessing(LPBITMAPINFOHEADER lpThisBitmapInfoHeader)
{
	// doMyImageProcessing:  This is where you'd write your own image processing code
	// Task: Read a pixel's grayscale value and process accordingly

	unsigned int	W, H;			// Width and Height of current frame [pixels]
	unsigned int    row, col;		// Pixel's row and col positions
	unsigned long   i;				// Dummy variable for row-column vector
	int	    upperThreshold = 60;	// Gradient strength nessicary to start edge
	int		lowerThreshold = 30;	// Minimum gradient strength to continue edge
	unsigned long iOffset;			// Variable to offset row-column vector during sobel mask
	int rowOffset;					// Row offset from the current pixel
	int colOffset;					// Col offset from the current pixel
	int rowTotal = 0;				// Row position of offset pixel
	int colTotal = 0;				// Col position of offset pixel
	int Gx;							// Sum of Sobel mask products values in the x direction
	int Gy;							// Sum of Sobel mask products values in the y direction
	float thisAngle;				// Gradient direction based on Gx and Gy
	int newAngle;					// Approximation of the gradient direction
	bool edgeEnd;					// Stores whether or not the edge is at the edge of the possible image
	int GxMask[3][3];				// Sobel mask in the x direction
	int GyMask[3][3];				// Sobel mask in the y direction
	int newPixel;					// Sum pixel values for gaussian
	int gaussianMask[5][5];			// Gaussian mask

	W = lpThisBitmapInfoHeader->biWidth;  // biWidth: number of columns
    H = lpThisBitmapInfoHeader->biHeight; // biHeight: number of rows
	
	for (row = 0; row < H; row++) {
		for (col = 0; col < W; col++) {
			edgeDir[row][col] = 0;
		}
	}

	/* Declare Sobel masks */
	GxMask[0][0] = -1; GxMask[0][1] = 0; GxMask[0][2] = 1;
	GxMask[1][0] = -2; GxMask[1][1] = 0; GxMask[1][2] = 2;
	GxMask[2][0] = -1; GxMask[2][1] = 0; GxMask[2][2] = 1;
	
	GyMask[0][0] =  1; GyMask[0][1] =  2; GyMask[0][2] =  1;
	GyMask[1][0] =  0; GyMask[1][1] =  0; GyMask[1][2] =  0;
	GyMask[2][0] = -1; GyMask[2][1] = -2; GyMask[2][2] = -1;

	/* Declare Gaussian mask */
	gaussianMask[0][0] = 2;		gaussianMask[0][1] = 4;		gaussianMask[0][2] = 5;		gaussianMask[0][3] = 4;		gaussianMask[0][4] = 2;	
	gaussianMask[1][0] = 4;		gaussianMask[1][1] = 9;		gaussianMask[1][2] = 12;	gaussianMask[1][3] = 9;		gaussianMask[1][4] = 4;	
	gaussianMask[2][0] = 5;		gaussianMask[2][1] = 12;	gaussianMask[2][2] = 15;	gaussianMask[2][3] = 12;	gaussianMask[2][4] = 2;	
	gaussianMask[3][0] = 4;		gaussianMask[3][1] = 9;		gaussianMask[3][2] = 12;	gaussianMask[3][3] = 9;		gaussianMask[3][4] = 4;	
	gaussianMask[4][0] = 2;		gaussianMask[4][1] = 4;		gaussianMask[4][2] = 5;		gaussianMask[4][3] = 4;		gaussianMask[4][4] = 2;	
	

	/* Gaussian Blur */
	for (row = 2; row < H-2; row++) {
		for (col = 2; col < W-2; col++) {
			newPixel = 0;
			for (rowOffset=-2; rowOffset<=2; rowOffset++) {
				for (colOffset=-2; colOffset<=2; colOffset++) {
					rowTotal = row + rowOffset;
					colTotal = col + colOffset;
					iOffset = (unsigned long)(rowTotal*3*W + colTotal*3);
					newPixel += (*(m_destinationBmp + iOffset)) * gaussianMask[2 + rowOffset][2 + colOffset];
				}
			}
			i = (unsigned long)(row*3*W + col*3);
			*(m_destinationBmp + i) = newPixel / 159;
		}
	}

	/* Determine edge directions and gradient strengths */
	for (row = 1; row < H-1; row++) {
		for (col = 1; col < W-1; col++) {
			i = (unsigned long)(row*3*W + 3*col);
			Gx = 0;
			Gy = 0;
			/* Calculate the sum of the Sobel mask times the nine surrounding pixels in the x and y direction */
			for (rowOffset=-1; rowOffset<=1; rowOffset++) {
				for (colOffset=-1; colOffset<=1; colOffset++) {
					rowTotal = row + rowOffset;
					colTotal = col + colOffset;
					iOffset = (unsigned long)(rowTotal*3*W + colTotal*3);
					Gx = Gx + (*(m_destinationBmp + iOffset) * GxMask[rowOffset + 1][colOffset + 1]);
					Gy = Gy + (*(m_destinationBmp + iOffset) * GyMask[rowOffset + 1][colOffset + 1]);
				}
			}

			gradient[row][col] = sqrt(pow(Gx,2.0) + pow(Gy,2.0));	// Calculate gradient strength			
			thisAngle = (atan2(Gx,Gy)/3.14159) * 180.0;		// Calculate actual direction of edge
			
			/* Convert actual edge direction to approximate value */
			if ( ( (thisAngle < 22.5) && (thisAngle > -22.5) ) || (thisAngle > 157.5) || (thisAngle < -157.5) )
				newAngle = 0;
			if ( ( (thisAngle > 22.5) && (thisAngle < 67.5) ) || ( (thisAngle < -112.5) && (thisAngle > -157.5) ) )
				newAngle = 45;
			if ( ( (thisAngle > 67.5) && (thisAngle < 112.5) ) || ( (thisAngle < -67.5) && (thisAngle > -112.5) ) )
				newAngle = 90;
			if ( ( (thisAngle > 112.5) && (thisAngle < 157.5) ) || ( (thisAngle < -22.5) && (thisAngle > -67.5) ) )
				newAngle = 135;
				
			edgeDir[row][col] = newAngle;		// Store the approximate edge direction of each pixel in one array
		}
	}

	/* Trace along all the edges in the image */
	for (row = 1; row < H - 1; row++) {
		for (col = 1; col < W - 1; col++) {
			edgeEnd = false;
			if (gradient[row][col] > upperThreshold) {		// Check to see if current pixel has a high enough gradient strength to be part of an edge
				/* Switch based on current pixel's edge direction */
				switch (edgeDir[row][col]){		
					case 0:
						findEdge(0, 1, row, col, 0, lowerThreshold);
						break;
					case 45:
						findEdge(1, 1, row, col, 45, lowerThreshold);
						break;
					case 90:
						findEdge(1, 0, row, col, 90, lowerThreshold);
						break;
					case 135:
						findEdge(1, -1, row, col, 135, lowerThreshold);
						break;
					default :
						i = (unsigned long)(row*3*W + 3*col);
						*(m_destinationBmp + i) = 
						*(m_destinationBmp + i + 1) = 
						*(m_destinationBmp + i + 2) = 0;
						break;
					}
				}
			else {
				i = (unsigned long)(row*3*W + 3*col);
					*(m_destinationBmp + i) = 
					*(m_destinationBmp + i + 1) = 
					*(m_destinationBmp + i + 2) = 0;
			}	
		}
	}
	
	/* Suppress any pixels not changed by the edge tracing */
	for (row = 0; row < H; row++) {
		for (col = 0; col < W; col++) {	
			// Recall each pixel is composed of 3 bytes
			i = (unsigned long)(row*3*W + 3*col);
			// If a pixel's grayValue is not black or white make it black
			if( ((*(m_destinationBmp + i) != 255) && (*(m_destinationBmp + i) != 0)) || ((*(m_destinationBmp + i + 1) != 255) && (*(m_destinationBmp + i + 1) != 0)) || ((*(m_destinationBmp + i + 2) != 255) && (*(m_destinationBmp + i + 2) != 0)) ) 
				*(m_destinationBmp + i) = 
				*(m_destinationBmp + i + 1) = 
				*(m_destinationBmp + i + 2) = 0; // Make pixel black
		}
	}

	/* Non-maximum Suppression */
	for (row = 1; row < H - 1; row++) {
		for (col = 1; col < W - 1; col++) {
			i = (unsigned long)(row*3*W + 3*col);
			if (*(m_destinationBmp + i) == 255) {		// Check to see if current pixel is an edge
				/* Switch based on current pixel's edge direction */
				switch (edgeDir[row][col]) {		
					case 0:
						suppressNonMax( 1, 0, row, col, 0, lowerThreshold);
						break;
					case 45:
						suppressNonMax( 1, -1, row, col, 45, lowerThreshold);
						break;
					case 90:
						suppressNonMax( 0, 1, row, col, 90, lowerThreshold);
						break;
					case 135:
						suppressNonMax( 1, 1, row, col, 135, lowerThreshold);
						break;
					default :
						break;
				}
			}	
		}
	}
	
}

void CTripodDlg::findEdge(int rowShift, int colShift, int row, int col, int dir, int lowerThreshold)
{
	int W = 320;
	int H = 240;
	int newRow;
	int newCol;
	unsigned long i;
	bool edgeEnd = false;

	/* Find the row and column values for the next possible pixel on the edge */
	if (colShift < 0) {
		if (col > 0)
			newCol = col + colShift;
		else
			edgeEnd = true;
	} else if (col < W - 1) {
		newCol = col + colShift;
	} else
		edgeEnd = true;		// If the next pixel would be off image, don't do the while loop
	if (rowShift < 0) {
		if (row > 0)
			newRow = row + rowShift;
		else
			edgeEnd = true;
	} else if (row < H - 1) {
		newRow = row + rowShift;
	} else
		edgeEnd = true;	
		
	/* Determine edge directions and gradient strengths */
	while ( (edgeDir[newRow][newCol]==dir) && !edgeEnd && (gradient[newRow][newCol] > lowerThreshold) ) {
		/* Set the new pixel as white to show it is an edge */
		i = (unsigned long)(newRow*3*W + 3*newCol);
		*(m_destinationBmp + i) =
		*(m_destinationBmp + i + 1) =
		*(m_destinationBmp + i + 2) = 255;
		if (colShift < 0) {
			if (newCol > 0)
				newCol = newCol + colShift;
			else
				edgeEnd = true;	
		} else if (newCol < W - 1) {
			newCol = newCol + colShift;
		} else
			edgeEnd = true;	
		if (rowShift < 0) {
			if (newRow > 0)
				newRow = newRow + rowShift;
			else
				edgeEnd = true;
		} else if (newRow < H - 1) {
			newRow = newRow + rowShift;
		} else
			edgeEnd = true;	
	}	
}

void CTripodDlg::suppressNonMax(int rowShift, int colShift, int row, int col, int dir, int lowerThreshold)
{
	int W = 320;
	int H = 240;
	int newRow = 0;
	int newCol = 0;
	unsigned long i;
	bool edgeEnd = false;
	float nonMax[320][3];			// Temporarily stores gradients and positions of pixels in parallel edges
	int pixelCount = 0;					// Stores the number of pixels in parallel edges
	int count;						// A for loop counter
	int max[3];						// Maximum point in a wide edge
	
	if (colShift < 0) {
		if (col > 0)
			newCol = col + colShift;
		else
			edgeEnd = true;
	} else if (col < W - 1) {
		newCol = col + colShift;
	} else
		edgeEnd = true;		// If the next pixel would be off image, don't do the while loop
	if (rowShift < 0) {
		if (row > 0)
			newRow = row + rowShift;
		else
			edgeEnd = true;
	} else if (row < H - 1) {
		newRow = row + rowShift;
	} else
		edgeEnd = true;	
	i = (unsigned long)(newRow*3*W + 3*newCol);
	/* Find non-maximum parallel edges tracing up */
	while ((edgeDir[newRow][newCol] == dir) && !edgeEnd && (*(m_destinationBmp + i) == 255)) {
		if (colShift < 0) {
			if (newCol > 0)
				newCol = newCol + colShift;
			else
				edgeEnd = true;	
		} else if (newCol < W - 1) {
			newCol = newCol + colShift;
		} else
			edgeEnd = true;	
		if (rowShift < 0) {
			if (newRow > 0)
				newRow = newRow + rowShift;
			else
				edgeEnd = true;
		} else if (newRow < H - 1) {
			newRow = newRow + rowShift;
		} else
			edgeEnd = true;	
		nonMax[pixelCount][0] = newRow;
		nonMax[pixelCount][1] = newCol;
		nonMax[pixelCount][2] = gradient[newRow][newCol];
		pixelCount++;
		i = (unsigned long)(newRow*3*W + 3*newCol);
	}

	/* Find non-maximum parallel edges tracing down */
	edgeEnd = false;
	colShift *= -1;
	rowShift *= -1;
	if (colShift < 0) {
		if (col > 0)
			newCol = col + colShift;
		else
			edgeEnd = true;
	} else if (col < W - 1) {
		newCol = col + colShift;
	} else
		edgeEnd = true;	
	if (rowShift < 0) {
		if (row > 0)
			newRow = row + rowShift;
		else
			edgeEnd = true;
	} else if (row < H - 1) {
		newRow = row + rowShift;
	} else
		edgeEnd = true;	
	i = (unsigned long)(newRow*3*W + 3*newCol);
	while ((edgeDir[newRow][newCol] == dir) && !edgeEnd && (*(m_destinationBmp + i) == 255)) {
		if (colShift < 0) {
			if (newCol > 0)
				newCol = newCol + colShift;
			else
				edgeEnd = true;	
		} else if (newCol < W - 1) {
			newCol = newCol + colShift;
		} else
			edgeEnd = true;	
		if (rowShift < 0) {
			if (newRow > 0)
				newRow = newRow + rowShift;
			else
				edgeEnd = true;
		} else if (newRow < H - 1) {
			newRow = newRow + rowShift;
		} else
			edgeEnd = true;	
		nonMax[pixelCount][0] = newRow;
		nonMax[pixelCount][1] = newCol;
		nonMax[pixelCount][2] = gradient[newRow][newCol];
		pixelCount++;
		i = (unsigned long)(newRow*3*W + 3*newCol);
	}

	/* Suppress non-maximum edges */
	max[0] = 0;
	max[1] = 0;
	max[2] = 0;
	for (count = 0; count < pixelCount; count++) {
		if (nonMax[count][2] > max[2]) {
			max[0] = nonMax[count][0];
			max[1] = nonMax[count][1];
			max[2] = nonMax[count][2];
		}
	}
	for (count = 0; count < pixelCount; count++) {
		i = (unsigned long)(nonMax[count][0]*3*W + 3*nonMax[count][1]);
		*(m_destinationBmp + i) = 
		*(m_destinationBmp + i + 1) = 
		*(m_destinationBmp + i + 2) = 0;
	}
}

الگوریتم Canny در سی پلاس پلاس قسمت ۱
الگوریتم Canny در سی پلاس پلاس قسمت ۲
الگوریتم Canny در سی پلاس پلاس قسمت ۳
الگوریتم Canny در سی پلاس پلاس قسمت ۴

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

 

سی شارپ (به انگلیسی: #C )یک زبان برنامه نویسی همگردان، سطح بالا، شیءگرا، ساخت یافته، رویداد محور، تابعی، دستوری و جنریک است که توسط شرکت مایکروسافت در سال ۲۰۰۰ میلادی از خانوادهٔ زبان‌های چارچوب دات‌نت معرفی شد. زبان #C همچنین از خانواده زبان‌های برنامه‌نویسی سی نیز است.

زبان #C، یک زبان برنامه‌نویسی چند الگویی و منظم شده مدل‌های تابعی، اَمری، عمومی، شیءگرا و جز گرا و در بستر چارچوب دات نتمی‌باشد. این زبان توسط شرکت مایکروسافت و جزئی از دات نت به وجود آمد و بعداً استانداردهای ECMA و ISO را نیز دربر گرفت. #C یکی از ۴۴ زبان برنامه‌نویسی است که توسط زمان اجرای زبان مشترک از چارچوب دات‌نت پشتیبانی می‌شوند و در همه جا به وسیلهمایکروسافت ویژوال استودیو شناخته می‌شود.

زبان #C با قدرت و در عین حال سطح بالایی خود توانسته توجه بسیاری از برنامه نویسان را به خود جلب کند.

این زبان برپایه سادگی، مدرن بودن، همه منظوره و شئ گرا بودن ساخته شد. آندرس هجلزبرگ، طراح زبان برنامه‌نویسی دلفی، سرپرستی تیم طراحان زبان #C را بر عهده داشت. این زبان دارای دستوری شیءگرا مشابه ++C است و به شدت از زبان‌های جاوا و دلفینیازمندمدرکتأثیر پذیرفته‌است. در ابتدا نام این زبان COOL بود که مخفف C like Object Oriented Language بود، هر چند در ژوئیه ۲۰۰۰م، زمانی کهمایکروسافت پروژه را عمومی اعلام کرد، اسم آن به #C تغییر پیدا کرد.

اهداف طراحی زبان

  • استاندارد ECMA این اهداف طراحی زبان را برای #C برآورده می‌سازد:
  • #C یک زبان برنامه‌سازی ساده، مدرن، برای اهداف عمومی و شیءگرا است.
  • به دلیل اهمیت داشتن موضوع نیرومندی و دوام و بهره‌وری برنامه‌نویس، زبان دارای چک‌کننده Strong Type، چک‌کننده مرزهای آرایه، تشخیص حالت‌هایی که یک متغیر مقداردهی اولیه نشده‌است، قابلیت انتقال کدها و Garbage Collection خودکار است.
  • این زبان برای استفاده در اجزای توسعه نرم‌افزار برای دستیابی به مزایای سامانه‌های توزیعی در نظر گرفته شده‌است.
  • قابلیت انتقال برنامه‌نویس بسیار مهم است، خصوصاً برای آن دسته از برنامه‌نویسانی که با زبان‌های C و C++ آشنا هستند.
  • پشتیبانی از این زبان برای بین‌المللی شدن بسیار مهم است.
  • زبان #C برای نوشتن برنامه‌ها برای سامانه‌های تعبیه شده و میزبان در نظر گرفته شده‌است، سیستم‌عامل‌های پیچیده بسیار بزرگ گرفته تا توابع اختصاصی بسیار کوچک.
  • هر چند برنامه‌های نوشته شده با #C طوری هستند که از لحاظ حافظه و پردازنده مورد نیاز مقرون به صرفه باشند، ولی خود زبان از لحاظ اندازه و کارایی به خوبی زبان‌های C و اسمبلی نیست.

تاریخچه #C

در سال ۱۹۹۹م، شرکت سان مایکروسیستمز اجازه استفاده از زبان برنامه‌نویسی جاوا را در اختیار شرکت مایکروسافت قرار داد تا در سیستم‌عامل خود از آن استفاده کند. جاوا در اصل به هیچ پلت فرم یا سیستم‌عاملی وابسته نبود، ولی مایکروسافت برخی از مفاد قرار داد را زیر پا گذاشت و قابلیت مستقل از سیستم‌عامل بودن جاوا را از آن برداشت. شرکت سان پرونده‌ای علیه مایکروسافتدرست کرد و مایکروسافت مجبور شد تا زبان شیءگرای جدیدی با کامپایل جدید که به ++C شبیه بود را درست کند. در طول ساخت دات نت، کلاس‌های کتابخانه‌ای با زبان و کامپایلر SMC نوشته شدند. در سال ۱۹۹۹ آندرس هلزبرگ گروهی را برای طراحی زبانی جدید تشکیل داد که در آن زمان نامش Cool بود و همانند C بود با خواص شیءگرایی. مایکروسافت در نظر داشت اسم این زبان را تا آخر COOL قرار دهد، ولی به دلیل مناسب نبودن برای اهداف تجاری این کار را نکرد. در ارائه و معرفی رسمی چارچوب دات‌نت در PDC در سال ۲۰۰۰ این زبان به #C تغییر نام یافت و کتابخانه کلاس‌ها و runtime در ای‌اس‌پی‌دات‌نت به #C منتقل شدند. مدیر و سرپرست طراحان در مایکروسافت آندرس هلزبرگ بود که تجربه قبلی او در طراحی Framework و زبان‌های برنامه سازی++Borland، دلفی، Turbo Pascal، ویژوال سی++ به آسانی در دستورالعمل‌های #C قابل رویت است و به همان خوبی در هسته CLR.

ویژگی‌های #C

برخی از تفاوت‌های زبان #C با زبان‌های C و ++C عبارتند از:

  • هیچ تابع یا متغیر سراسری(Global) وجود ندارد، تمام متدها و اعضا بایستی در داخل کلاس‌ها تعریف شوند. این امر ممکن است، هر چند برای استفاده از متغیرها و توابع عمومی باید از متدها و متغیرها در کلاس‌های عمومی استفاده کرد.
  • متغیرهای عمومی، بر خلاف زبان‌های C و ++C، نمی‌توانند بلاک‌های پیوستی را در بر بگیرند.
  • #C دارای یک نوع داده بولی است (bool). برخی از عبارت‌ها مانند while و if که شرطی هستند، نیازمند یک عبارت نوع بولی هستند. همان‌طور که ++C نیز دارای نوع داده بولی است، این نوع داده به راحتی می‌تواند به یا از Integerها تبدیل شود، و عبارتی مانند (if(a نیازمند این امر است که a از یک نوع قابل تبدیل به bool یا اشاره گر باشد. کامپایلر #C برنامه‌نویس را در این شرایط مجبور به استفاده از عباراتی می‌کند که به درستی یک مقدار bool را برمی‌گردانند؛ بنابراین دستوری مانند (if(a = b باعث بروز خطا می‌شوند. (به جای = بایستی از == استفاده شود)
  • در سی شارپ، اشاره گرهای به حافظه بایستی فقط در داخل بلوکهای unsafe استفاده شوند و برنامه در این حالت برای اجرا نیاز به اجازه از کاربر دارد. بیشتر دسترسی شی از طریق شی امن است که یا همیشه در حال اشاره به شی صحیح موجود است یا یک مقدار Null دارد. اشاره گری به شی به درد نخور یا بلاک حافظه رندم غیرممکن است. اشاره گر نا امن می‌تواند به نمونه‌ای از value-type، آرایه، رشته یا بلاکی که حافظه به آن داده شده‌است اشاره نماید. کدی که به عنوان نا امن علامت نخورده باشد، هنوز می‌تواند اشاره گرها را از سامانه بازیابی یا در آن ذخیره کند ولی نمی‌تواند مرجع جدیدی به آن‌ها اختصاص دهد.
  • حافظه ساماندهی شده نمی‌تواند صریحاً آزاد شود، ولی به‌طور خودکار به عنوان به درد نخور تلقی می‌شود. انتخاب آدرس‌های به درد نخور حافظه نفوذ ناپذیر است. هم چنین #C با استفاده از عبارات، پشتیبانی مستقیمی از پایان اجباری می‌کند (پشتیبانی از اصطلاح Resource Acquisition Is Initialization).
  • وراثت چندگانه از کلاس‌ها در این زبان پشتیبانی نمی‌شود. البته یک کلاس امکان ارث بری از تعداد نامحدود واسط‌ها را دارد. پشتیبانی نکردن از وراثت چندگانه به دلیل اهداف معماری این زبان در CLI و برای جلوگیری از پیچیدگی است. در عوض می‌توان از اینترفیس‌های مختلف استفاده کرد. یعنی برای یک کلاس که احتمالاً فرزند کلاسی دیگر است (ارث برده) می‌توان چندین اینترفیس را پیاده‌سازی (Implement) نمود.
  • #C بسیار typesafe تر از ++C است. تنها تبدیلات ضمنی مثل تبدیل نوع داده کوچکتر به بزرگتر یا تبدیل نوع مشتق شده به نوع پایه به‌طور پیش‌فرض و بدون خطا صورت می‌پذیرد. هیچ تبدیل ضمنی ای میانBooleanها و Integerها وجود ندارد و هر تبدیل user-defined بایستی به صراحت با یکی از کلمات explicit یا implicit نشانه گذاری شود. تبدیل b به a در حالتی که a یک Integer و b یک double باشد در زبان C++ مجاز است اما در #C به یک خطای زمان کامپایل منجر می‌شود (بایستی به صورت explicit تعریف شود)
  • اعضای Enumeration در داخل محدوده شخصی خود قرار دارند.
  • #C قابلیت syntactic sugar را برای توابع متداول، اکسسورها و ماجول‌های کسول شده در یک کلاس به صورت ویژگی‌ها قرار داده‌است.

اکسسورها که خاصیت نیز گفته می‌شوند در زبان #C قادر به کنترل دسترسی اعضا و معتبرسازی داده‌ها هستند.

  • تمام انواع بازتابی(Reflection) و بازیابی(Recovery) قابل استفاده‌است.
  • در حال حاضر (۳ ژوئن ۲۰۰۸) دارای ۷۷ کلمۀ رزرو شده‌ ( کلمۀ کلیدی ) است.

ساختمان داده (ساختار و ذخیره‌سازی داده) در #C

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

رشته‌ها

در C یا ++C ساختار رشته به صورت ارایه‌ای از نوع char بود که امکان اضافه کردن به رشته را محدود می‌کرد به دلیل ثابت بودن طول در آغاز تعریف ولی در #C دو نوع متفاوت رشته وجود دارد؛ که یکی به صورت آرایه‌ای با طول ثابت ۲۵۶(در عمل ۲۵۵)موجوداست (به صورت پیش فرض) و در صورتی که با کمبود جا روبرو شود فضای جدید (بزرگتر) یافته و به ان انتقال می‌دهد؛ ولی در نوع دوم رشته‌ها از لیست پیوندی استفاده می‌شود.

سامانه یکپارچه شده

#C دارای یک سامانه نوع یکپارچه‌است که به آن CTS می‌گویند. این بدان معناست که تمام انواع، شامل موارد اصلی مانند Integerها، مشتق شده از System.Object هستند. به عنوان مثال، هر نوع یک متد به نام ToString() را به ارث می‌برد. بخاطر کارایی، انواع اولیه (و انواع مقداری) به‌طور داخلی فضایی برای آن‌ها بر روی پشته در نظر گرفته می‌شود.

انواع داده

CTS داده‌ها را به دو نوع تقسیم می‌کند:

  • نوع مقداری (Value Type)
  • نوع مرجعی (Refrence Type)

انواع داده‌ای توده ساده‌ای از داده می‌باشند. نمونه‌های انواع داده‌ای نه هویت مرجعی دارند و نه مفاهیم مقایسه مراجع را. برای مقایسه برابری یا عدم برابری انواع داده‌ای، خود مقدار داده‌ها را با یکدیگر مقایسه می‌کنیم مگر اینکه عملگرهای مشابه دوباره تعریف شده باشند. مقادیر داده‌های مرجعی همیشه یک مقدار پیش‌فرض دارند و همیشه می‌توانند ایجاد یا کپی شوند. یکی دیگر از محدودیت‌های انواع داده‌ای این ات که آن‌ها نمی‌توانند از یکدیگر مشتق شوند (ولی می‌توانند اشتراکاتی داشته باشند) و هم چنین نمی‌توانند در سازنده مقدار دهی اولیه شوند. مثالی از انواع داده‌ای، بعضی از انواع اولیه مانند int و float و char و System.DateTime می‌باشند. در مقابل، انواع مرجعی مفهوم تعریف مرجعی را دارند (که در آن هر نمونه از نوع مرجع، به‌طور ذاتی از دیگر نمونه‌ها جدا می‌شود، حتی اگر داده هر دو نمونه یکی باشد). این دقیقاً نمونه مشابه مقایسه تساوی یا عدم تساوی داده‌های مرجعی است، که در آن آزمایش برای مرجع‌ها از داده‌ای‌ها سریع تر است. در کل نه همیشه امکان تعریف نمونه مرجعی وجود دارد و نه امکان کپی یا نمایش مقادیر مقایسه دو نمونه؛ ولی به هر حال انواع مرجعی خاص می‌توانند این اعمال را از طریق سازنده‌های عمومی یا اجرای واسط‌های مشابه (مثل ICloneable یا IComparable) انجام دهند. نمونه‌هایی از انواع مرجعی، اشیاء، System.String و Sysmet.Array می‌باشند. هر دو نوع داده قابلیت انعطاف توسط تعریف به وسیله کاربر را دارند. در واقع وقتی ما نوع داده‌ای را به تابع ای ارسال می‌کنیم، آدرس داده نیز فرستاده می‌شود. البته این امر پیش‌فرض است ولی برای داده‌های مثل آرایه، رشته‌ای، آدرس فرستاده می‌شود و ارسال از نوع مرجع می‌شود

Boxing و UnBoxing

Boxing عمل تبدیل مقدار نوع داده‌ای به نوع مرجع مشابه آن می‌باشد.

مثال:

int foo = 42;// Value type...
object bar = foo;//foo is boxed to bar.

UnBoxing عمل تبدیل نوع مرجع به نوع داده‌ای می‌باشد. مثال:

int foo = 42;// Value type.
object bar = foo;// foo is boxed to bar.
int foo2 = (int)bar;// Unboxed back to value type.

#C به برنامه‌نویس با استفاده از کلمه کلیدی Struct اجازه می‌دهد تا انواع مقداری User-defined را ایجاد کند. از دیدگاه برنامه‌نویسی، آن‌ها کلاس‌های سبک وزن به نظر می‌رسند. برخلاف کلاس‌ها (که بر روی heap قرار می‌گیرند) و شبیه به انواع اولیه استاندارد مانند انواع مقداری Structها نیز بر روی پشته قرار می‌گیرند. آن‌ها همچنین می‌توانند قسمتی از یک شئ باشند، یا در یک آرایه مرتب شوند، بدون حافظه غیر مستقیمی که به‌طور معمول برای انواع کلاس تخصیص می‌یابد.

ویژگی‌های جدید در سی شارپ ۲٫۰

ویژگی‌های جدید در #C چارچوب دات‌نت SDK ۲٫۰ (مطابق با سومین ویرایش استاندارد ECMA-۳۳۴):

کلاسهای partial

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

file.cs:

public partial class MyClass
{
    public MyClass()
    {
// implementation
    }
}

file2.cs:

public partial class MyClass
{
    public void SomeMethod()
    {
// implementation
    }
}

Genericها

genericها یا نوع‌های پارامتری شده یا چندریختی‌های پارامتری یک ویژگی جدید چارچوب دات‌نت ۲٫۰ است که به وسیله #C پشتیبانی می‌شود. برخلاف Templateهای سی پلاس پلاس، در این انواع به جای اینکه نمونه‌سازی توسط کامپایلر انجام شود، در زمان اجرا صورت می‌گیرد، بنابراین می‌توانند چند زبانه باشند در حالی که ++C نمی‌تواند. آن‌ها دارای ویژگی‌هایی هستند که به‌طور مستقیم توسط Templateهای ++C پشتیبانی نمی‌شوند مانند نوع محدودیت‌ها در پارامترهای Generic با استفاده از رابط‌ها(Interface). سی شارپ از پارامترهای‌های Generic بدون نوع پشتیبانی نمی‌کند. بر خلاف genericهای جاوا، generic‌های دات نت برای پارامتری کردن انواع داده‌ای در اشیاء ماشین مجازی CLI، از مفاهیم شیءگرایی استفاده می‌کنند که اجازه بهینه‌سازی و حفاظت انواع اطلاعات را می‌دهد.

کلاس‌های static

کلاس‌ها به صورت Static قابل تعریف نیستند مگر اینکه تمام اعضای آن‌ها Static باشند؛ که این امر بسیار شبیه به مفهوم مدل در زبانهای رویه‌ای است. (زبان رویه‌ای: یک زبان برنامه‌نویسی که در آن عنصر اصلی برنامه‌نویسی یک زیربرنامه‌است. مانند زبان‌های C، پاسکال و…)

یک شکل جدید از تکرارکننده با استفاده از سازنده توابع

یک شکل جدید از iterator(تکرارکننده)، با استفاده از ساختار yield return بسیار شبیه به yield زبان Python.

// Method that takes an iterable input (possibly an array) and returns all even numbers.
public static IEnumerable&lt;int&gt; GetEven(IEnumerable&lt;int&gt; numbers)
{
    foreach (int i in numbers)
    {
        if (i % 2 == 0) yield return i;
    }
}

Delegateهای ناشناس

Delegate یک شی می‌باشد که حاوی یک یا چند اشاره گر به توابع می‌باشد؛ که با Invoke کردن آن تمامی توابع اشاره شده داخل آن اجرا می‌شوند.

Delegateهای ناشناس که عملکردهای محدودی را در #C به وجود می‌آورند. کد کنار بدنه Deletage ناشناس، دسترسی کامل برای خواندن یا نوشتن در متغیرهای عمومی، پارامترهای توابع و اعضای کلاسهای دارای محدوده Deletage را دارد ولی پارامترهای out و ref را پشتیبانی نمی‌کند. برای مثال:

int SumOfArrayElements(int[] array)
{
    int sum = 0;
    Array.ForEach(       array,
        delegate(int x)
        {
            sum += x;
        }
  );
    return sum;
}

Delegate covariance and contravariance

تبدیل گروه‌های متد به نوع Deletage در برگشت دارای covariant و در انواع پارامترها دارای contravariant هستند.

اکسسورهای یک خاصیت(get و set) می‌توانند دارای سطح دسترسی متفاوتی باشند

مثال:

string status = string.Empty;
public string Status
}
    get { return status; }// anyone can get value of this property,
    protected set { status = value; }// but only derived classes can change
it
}

نکته مهم: سطح دسترسی خاصیت نمی‌تواند بالاتر از سطح دسترسی اکسسورها باشد. به عنوان مثال private بودن خاصیت و public بودن اکسسور باعث بروز خطا می‌شود.

نوع داده Nullable

نوع داده Nullable (که با یک علامت سؤال قابل تشخیص است: int? i = null;)اجازه تخصیص مقدار null را برای انواع داده‌ای می‌دهد. این امر باعث بهبودی فعل و انفعال با پایگاه داده SQL می‌شود. در این حالت نوع ستونی INTEGER NULL در SQL به‌طور مستقیم به int? در سی شارپ تبدیل می‌شود.

داده‌های Nullable در آخرین لحظات اوت ۲۰۰۵ اضافه شدند چند هفته مانده به اتمام کار اداری و برای بهبود زبان. متغیر Null در حقیقت خالی نیست، بلکه نمونه‌ای است از struct Nullable<T> با ویژگی HasValue مساوی false. وقتی در برنامه قرار می‌گیرد، خود به خود نمونه خالی در آن قرار می‌گیرد، نه مقدار خود آن، در نتیجه اشاره گر مقصد همیشه غیر Null می‌باشد، حتی برای مقادیر Null. کد زیر نضص بالا را مشخص می‌کند:

int? i = null;
object o = i;
if (o == null)
    Console.WriteLine("Correct behaviour - runtime version from September 2005 or later");
else
    Console.WriteLine("Incorrect behaviour - pre-release runtime (from before September 2005)»);

وقتی درون شی ای کپی می‌شود، نمونه Nullable به صورت تشریفاتی در آن قرار می‌گیرد و در نتیجه مقادیر و منابع Null با هم برابر می‌شوند. در گذشته این خاصیت دارای مجادله بود تا زمانی که علاوه بر چارچوب دات‌نت ۲، به هسته CLR نیز مجهز شد و همه تکنولوژی‌ها نظیر سی شارپ، ویژوال بیسیک، مایکروسافت اس‌کیوال سرور ۲۰۰۵ و مایکروسافت ویژوال استودیو ۲۰۰۵ را شامل شد.

Coalesce Operator

مقدار اولین عملوندی که null نباشد را برمی‌گرداند. (یا null، برای زمانی که تمام عملوندها null باشند)

object nullObj = null;
object obj = new Object();
return nullObj ?? obj;// returns obj

کاربرد اصلی این عملگر در قرار دادن یک مقدار nullable در یک مقدار non-nullable با استفاده از یک دستورالعمل ساده‌است.

int? i = null;
int j = i ?? 0; /* Unless i is null, initialize j to i. Else (if i is null), initialize j to 0. */

 

منبع

آشنایی با #C قسمت ۱
آشنایی با #C قسمت ۲
آشنایی با #C قسمت ۳

انواع سامانه‌های توصیه‌گر

سامانه‌های توصیه‌گر به طور کلی به سه دسته تقسیم می‌شوند؛ در رایج‌ترین تقسیم‌بندی، آنها را به سه گروه ۱. محتوا محور ۲. دانش محور و ۳. صافی سازی تجمعی، تقسیم می‌کنند، که البته گونه چهارمی تحت عنوان Hybrid RS هم برای آنها قائل می‌شوند.

یک رویکرد به سیستم‌های توصیه‌گر، استفاده از الگوریتم‌های CF یا صافی سازی تجمعی است. در این رویکرد به جای استفاده از محتوای (Content) اقلام، از نظرات و رتبه‌بندی‌های انجام شده توسط کاربران برای ارائه پیشنهاد، استفاده می‌شود. مشکل اصلی استفاده از این رویکرد، مشکل شروع سرد (Cold Start problem)[۲] می‌باشد که برای کاربران جدید بروز می‌کند که در سیستم ثبت نام می‌کنند و سیستم هیچ اطلاعاتی از نظرات یا علایق کاربر ندارد (New User problem). در چنین شرایطی، سیستم‌ها معمولاً از یادگیری فعال (Active Learning)[۳] یا استفاده از ویژگی‌های شخصیتی کاربر،[۴] برای حل مشکل استفاده می‌کنند.

در روش محتوا محور، اقلام پیشنهادی، به این دلیل که با اقلامی که کاربر فعال (کاربری که قرار است به او توصیه کنیم) نسبت به آنها ابراز علاقه کرده‌است شباهت‌هایی دارند، به کاربر توصیه می‌شوند ولی در CF، لیست اقلام پیشنهادی، بر اساس این اصل که، کاربرانی، مشابه کاربر فعال، از آنها رضایت داشته‌اند تهیه می‌شود. از این رو واضح است که در روش محتوامحور، تمرکز بر روی یافتن شباهت بین اقلام بوده، در حالی که در CF، تمرکز روی یافتن شباهت بین کاربران است؛ بدین ترتیب که پیشنهادات در CF، بر اساس تشابه رفتاری کاربرفعال با کاربران دیگر صورت می‌گیرد و نه بر اساس تشابه ویژگی کالاهای پیشنهادی با ویژگی‌های کالاهای مورد علاقه وی (کاربر فعال). رویکرد محتوا محور یکی از روشهای مؤثر برای حلی نوعی از مشکل شروع سرد می‌باشد که برای کالاهای (آیتم‌های) جدید رخ می‌دهد (New Item problem)[۵] که به تازگی به لیست سیستم اضافه شده‌اند و هیچ کاربری در مورد آنها نظری نداده است. در چنین حالتی رویکرد صافی سازی تجمعی نمی‌تواند این کالاها را به کاربران توصیه کند.

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

گونه چهارم سیستم‌های ترکیبی هستند. طراحان این نوع سیستم‌ها دو یا چند گونه از انواع سه‌گانه مذکور را غالباً به دو منظور با هم ترکیب می‌کنند؛ ۱- افزایش عملکرد سیستم ۲- کاهش اثر نقاط ضعفی که آن سیستم‌ها وقتی به تنهایی به کار گرفته شوند، دارند. از میان سه روش موجود (CF و CB و KB)، غالباً روش CF یک پای ثابت این ترکیبات است.

منبع


سیستم توصیه گر (Recommender Systems) چیست ؟

 

سیستم توصیه گر

 

 

سیستم توصیه گر

 

سیستم توصیه گر (Recommender System) قسمت ۱
سیستم توصیه گر (Recommender System) قسمت ۲
سیستم توصیه گر (Recommender System) قسمت ۳

واتسون، کابوس آزمون تورینگ!

واتسون را که به‌طور حتم به خاطر می‌آورید؟ ماشین ساخت آی‌بی‌ام که در سال ۲۰۱۱ با شکست دادن رقبای انسانی خبره در بازی Jeopardy سر‌و‌صدای زیادی به پا کرد. این ماشین به عنوان یکی از مدرن‌ترین نمونه‌های ماشین‌های هوشمند امروزی می‌تواند مورد بسیار خوبی برای بررسی وضعیت آزمون تورینگ در جهان امروز به شمار می‌رود.

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

حتی واتسون نیز که انسان را در Jeopardy شکست داد، به این رؤیا چندان نزدیک نشده است. با این حال، در عرض ۱۵ سال گذشته پیشرفت‌های خیره‌کننده‌ای در جنبه‌های کاربردی هوش مصنوعی صورت گرفته است. راهبردهای آماری برای شبیه‌سازی بعضی جنبه‌های آنالیز انسانی توانسته سیستم‌هايی کاربردی را در اختیار مردم قرار دهد که در هر زمینه‌ای، از دیپ بلو گرفته تا نت فلیکس، آمازون و گوگل زندگی ما را قبضه کرده‌اند.» بیکر سپس در مورد واتسون می‌گوید: «موضوع جدید درباره واتسون، راهبرد تماماً عمل‌گرای آن است. این ماشین روش‌های مختلفی را برای پاسخ به یک پرسش امتحان می‌کند. در اینجا راهبرد درست یا نادرست وجود ندارد بلکه واتسون در طول زمان یاد می‌گیرد تا چه زمانی به کدام روش تکیه کند. به نوعی می‌توان گفت در جنگ بین راهبردهای مختلف در هوش مصنوعی واتسون به مانند یک ندانم‌گرا (آگنوستیسیست) عمل می‌کند. جنبه جدید دیگر، توانایی بالای این ماشین در فهم زبان انگلیسی است. توانایی‌ای که به عقیده من از تمرین داده شدن این سیستم با دیتاست‌های عظیم ناشی شده است و با وجود خیره‌کننده بودن، یک روش جدید و بکر به شمار نمی‌‌‌آید.» بیکر سپس به توضیح مقایسه واتسون با مغز انسان پرداخته و می‌گوید: «تیم آی‌بی‌ام در هنگام برنامه‌ریزی واتسون توجه چنداني به ساختار مغز انسان نداشته است.

به عقیده من واتسون محصول واقعی مهندسی است‌: استفاده از فناوری‌های موجود برای ساخت ماشینی با ویژگی‌های مشخص در زمانی خاص.» بیکر سپس اضافه می‌کند: «با این حال من شباهت‌هایی را در شیوه تفکر واتسون و انسان‌ها مشاهده می‌کنم که البته به این دليل نیست که ما از یک طراحی یکسان سود می‌بریم، بلکه ما در واقع به دنبال حل مسائلی یکسان هستیم. به عنوان مثال، برخلاف بسیاری از کامپیوترها واتسون برای عدم قطعیت برنامه‌ریزی شده است. این سیستم هیچ‌گاه از جوابی که می‌دهد صد درصد مطمئن نیست بلکه در مورد آن شک و تردید دارد. این برای ماشینی که قرار است با زبان انسان‌ها ارتباط برقرار کند، راهبردی هوشمندانه به شمار می‌رود.»

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

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

این همان چیزی است که مدیران Diapers.com را به سمت اداره کل سیستم انبار به دست روبات‌ها سوق می‌دهد. یا فدکس را قانع می‌کند که می‌توان تعداد اپراتورهای شرکت را به حداقل رسانده و از ماشین‌هایی برای خدمات‌رساني به تماس‌های مشتریان استفاده کرد. چندین دهه قبل، انسان‌ها می‌ترسیدند روزی ماشین‌ها آن قدر هوشمند شوند که کنترل انسان‌ها را در دست گیرند. با این حال نزدیک به سی سال قبل و با شروع زمستان هوش مصنوعی، این ترس معنای سنتی خود را از دست داد و هم‌اکنون توانسته به شیوه‌ای جدید خود را وارد زندگی انسان‌ها کند. حقیقت تلخی در پس‌زمینه ماشین‌هایی مانند واتسون وجود دارد‌: بسیاری از کارهایی که تصور می‌کنیم هوشمندی انسان‌محورمان، ما را قادر به انجامشان می‌سازد در واقع آن‌قدر‌ها هم از نظر ماشین‌ها کار پیچیده‌ای به شمار نمی‌آید. اگر باور ندارید، می‌توانید از مبادله‌گران سنتی بورس در حوالی وال‌استریت سراغی بگیرید.

آينده

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

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

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


نظریه و آزمون تورینگ (هوش مصنوعی)

 

نظریه و آزمون تورینگ

بیش از نیم قرن پیش، هنگامی که هنوز هیچ تراشه‌ سیلیکونی‌ای ساخته نشده بود، آلن تورینگ یکی از بحث ‌بر‌انگیزترین پرسش‌های فلسفی تاریخ را پرسید. او گفت آیا ماشین می‌تواند فکر کند و اندکی بعد کوشید به پیروی از این قاعده که هر ادعای علمی باید از بوته آزمایش سربلند بیرون بیاید، پرسش فلسفی خود را با یک آزمایش ساده و در عین حال پیچیده جایگزین کند. او پرسید آیا یک ماشین یک کامپیوتر می‌تواند بازی تقلید را با موفقیت پشت سر بگذارد.
آیا ماشین می‌تواند از انسان چنان تقلید کند که در یک آزمون محاوره‌ای نتوانیم تفاوت انسان و ماشین را تشخیص دهیم. او در سال ۱۹۵۰ براساس محاسباتی تخمین زد که ۵۰ سال بعد کامپیوتری با یک میلیارد بیت حافظه خواهد توانست به موفقیت‌هایی در این زمینه دست پیدا کند. اکنون که در نیمه سال ۲۰۰۸ میلادی هستیم، حتی هشت سال بیشتر از زمانی که او لازم دانسته بود، هنوز هیچ ماشینی نتوانسته است از بوته آزمون تورینگ با موفقیت خارج شود. در سال ۲۰۰۰ مفهوم هوش مصنوعی برای هیچ‌کس غیر قابل باور نبوددر این مقاله نگاهی داریم به سیر تحولاتی که پس از این پرسش تاریخی در دنیای علم و مهندسی به‌وقوع پیوستند. یکی از جالب‌ترین و هیجان‌انگیزترین پرسش‌هایی که تاکنون تاریخ فلسفه به خود دیده این پرسش است که آلن تورینگ فیلسوف و ریاضیدان انگلیسی در سال ۱۹۵۰ طی مقاله‌ای به نام: Computing Machinery and Intelligence یا {ماشین محاسباتی و هوشمند} مطرح کرد او پرسید آیا ماشین می‌تواند فکر کند. خود تورینگ نتوانست پاسخ قطعی این پرسش را پیدا کند. اما برای یافتن پاسخ مناسب در آینده یک راهبرد خلاقانه پیشنهاد کرد.

او آزمونی طراحی کرد که خود، آن را بازی تقلید نامید. او آزمون بازی تقلید را چنین شرح داد: یک پرسشگر- یک انسان- همزمان در حال گفت‌وگو با دو نفر است. هر یک از این دو نفر در اتاق‌های جداگانه قرار گرفته‌اند و پرسشگر نمی‌تواند هیچیک از آنها را ببیند یکی از این دو نفر انسان است و دیگری یک ماشین یعنی یک کامپیوتر. پرسشگر باید با این دو نفر شروع به گفت‌وگو کند و بکوشد بفهمد کدا‌میک از این دو، انسان است و کدامیک ماشین. اگر کامپیوتر بتواند طوری جواب دهد که پرسشگر نتواند انسان را از ماشین تمیز دهد آنگاه می‌توان ادعا کرد که این ماشین هوشمند است. تورینگ برای آسان‌کردن شرایط این آزمون و پرهیز از پیچیدگی‌های اضافی آن را به محاوره‌ای متنی و روی کاغذ محدود کرد تا مجبور به درگیر شدن با مسائل انحرافی مانند تبدیل متن به گفتار شفاهی و تنظیم تن صدا و لهجه نباشیم.او همچنین بر اساس یک سری محاسبات پیش‌بینی کرد که ۵۰ سال بعد یعنی در سال ۲۰۰۰ انسان قادر خواهد بود کامپیوترهایی بسازد که در یک گفت‌وگوی پنج‌ دقیقه‌ای، فقط ۷۰درصد پرسشگرها بتوانند کشف کنند که در حال گفت‌وگو با یک انسان هستند یا یک ماشین. او برخورداری از یک میلیارد بیت حافظه (۱۲۵ میلیون بایت- حدود ۱۲۰ مگابایت) را یکی از مشخصه‌های اصلی این کامپیوتر دانست.تورینگ همچنین در این مقاله یک سری استدلال‌های مخالف با نظریه و آزمون خود را مطرح کرد و کوشید به آنها پاسخ دهد، تصور اینکه ماشین‌های هوشمندی ساخته شوند که بتوانند فکر کنند وحشتناک است. تورینگ در پاسخ می‌گوید این نکته‌ای انحرافی است، زیرا بحث اصلی او بایدها و نبایدها نیست بلکه بحث درباره ممکن‌هاست.

دیگر اینکه، ادعا می‌شود محدودیت‌هایی درباره نوع پرسش‌هایی که می‌توان از کامپیوتر پرسید وجود دارد، زیرا کامپیوتر از منطق خاصی پیروی می‌کند. اما تورینگ در پاسخ می‌گوید:‌ خود انسان هنگام گفت‌وگو پرغلط ظاهر می‌شود و نمی‌توان گفتار هر انسانی را لزوما منطقی کرد. او پیش‌بینی کرد که منشأ اصلی هوشمندی ماشین فرضی او، حافظه بسیار زیاد و سریعی است که یک کامپیوتر می‌تواند داشته باشد. بنابراین از نگاه تورینگ، ماشین همچون کامپیوتر Deep Blue که کاسپاروف، قهرمان شطرنج را شکست داد، می‌تواند یک ماشین هوشمند تلقی شود. در عین حال تورینگ این نظر را که {آزمون مورد بحث معتبر نیست، زیرا انسان دارای احساسات است و مثلا موسیقی دراماتیک می‌سازد} رد کرد و گفت: هنوز هیچ سند قابل قبولی وجود ندارد که ثابت کند فقط ما انسان‌ها دارای احساسات هستیم، زیرا مشخص نیست مفهوم دقیق این واژه به لحاظ علمی چیست.

در سال ۱۹۵۶ جان مک‌ کارتی، یکی از نظریه‌پردازان پیشگام این نظریه در آن زمان، اصطلاح (هوشمند مصنوعی) را برای اولین‌بار در نخستین کنفرانسی که به این موضوع اختصاص یافته بود، به کار برد. او همچنین زبان‌ برنامه‌نویس Lisp را ابداع کرد که در همین زمینه کاربرد دارد. دانشمندان بعدا این تاریخ را به عنوان تاریخ تولد علم هوش مصنوعی انتخاب کردند. تقریبا در همان زمان جان فون نیومان نظریه بازی‌ها را معرفی کرد. این نظریه نقش موثری در پیشبرد جنبه‌های نظری و علمی هوش مصنوعی داشت. چند سال بعد، در سال ۱۹۶۸ آرتور سرکلارک، در رمان معروف خود، یعنی اودیسه فضایی ۲۰۰۱ اصطلاح (آزمون تورینگ) را به جای (بازی‌ تقلید)‌ سر زبان‌ها انداخت. از زمانی که تورینگ این فرضیه را مطرح کرده است، هزاران دانشمند با هدف ساختن ماشینی که بتواند آزمون تورینگ را با موفقیت تمام کند، دست به کار شده‌اند. اما هنوز کسی موفق نشده است چنین ماشینی بسازد و پیش‌بینی تورینگ هم درست از آب در نیامده است.

● چالش‌های بنیادین هوش مصنوعی 

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

 

● شاخه‌های علم هوش مصنوعی 

امروزه دانش مدرن هوش مصنوعی به دو دسته تقسیم می‌شود:‌چ
ا) هوش مصنوعی سمبلیک یا نمادین Symbolic Ai
۲) هوش غیر سمبلیک یا پیوندگرا Connection Ai
هوش مصنوعی سمبلیک از رهیافتی مبتنی بر محاسبات آماری پیروی می‌کند و اغلب تحت عنوان «یادگیری ماشین» یا Machune Learning طبقه‌بندی می‌شود. هوش سمبلیک می‌کوشد سیستم و قواعد آن را در قالب سمبل‌ها بیان کند و با نگاشت اطلاعات به سمبل‌ها و قوانین به حل مسئله بپردازد. در میان معروف‌ترین شاخه‌های هوش مصنوعی سمبلیک می‌توان به سیستم‌های خبره (Expert Systems) و شبکه‌های Bayesian اشاره کرد. اما هوش پیوندگرا متکی بر یک منطق استقرایی است و از رهیافت «آموزش/ بهبود سیستم از طریق تکرار» بهره می‌گیرد. این آموزش‌ها نه بر اساس نتایج و تحلیل‌های دقیق آماری، بلکه مبتنی بر شیوه آزمون و خطا و «یادگیری از راه تجربه»‌ است. در هوش مصنوعی پیوندگرا، قواعد از ابتدا در اختیار سیستم قرار نمی‌گیرد، بلکه سیستم از طریق تجربه، خودش قوانین را استخراج می‌کند.
متدهای ایجاد شبکه‌های عصبی (Network Neural) و نیز به کارگیری منطق فازی (Fuzzy Logic) در این دسته قرار می‌گیرد.برای درک بهتر تفاوت میان دو شیوه به یک مثال توجه کنید. فرض کنید می‌خواهیم یک سیستم OCR بسازیم. سیستم OCR نرم‌افزاری است که پس از اسکن کردن یک تکه نوشته روی کاغذ می‌تواند متن روی آن را استخراج کند و به کاراکترهای متنی تبدیل نماید. بدیهی است که چنین نرم‌افزاری به نوعی هوشمندی نیاز دارد. این هوشمندی را با دو رهیافت متفاوت می‌توان فراهم کرد. اگر از روش سمبلیک استفاده کنیم، قاعدتا باید الگوی هندسی تمام حروف و اعداد را در حالت‌های مختلف در بانک اطلاعاتی سیستم تعریف کنیم و سپس متن اسکن‌شده را با این الگوها مقایسه کنیم تا بتوانیم متن را استخراج نماییم.روش دوم یا متد «پیوندگرا» این است که سیستم هوشمند سمبلیک درست کنیم و متن‌های متعددی را یک به یک به آن بدهیم تا آرام‌آرام آموزش ببیند و سیستم را بهینه کند.
در اینجا سیستم هوشمند می‌تواند مثلا یک شبکه عصبی یا مدل مخفی مارکوف باشد. در این شیوه سمبل‌ها پایه هوشمندی نیستند، بلکه فعالیت‌های سلسله اعصاب یک شبکه و چگونگی پیوند میان آنها مبنای هوشمندی را تشکیل می‌دهند. در طول دهه ۱۹۶۰ و ۱۹۷۰ به دنبال ابداع اولین برنامه نرم‌افزاری موفق در گروه سیستم‌های مبتنی بر دانش (Knowledge- based) توسط جوئل موزس، سیستم‌های هوش سمبلیک به یک جریان مهم تبدیل شد. ایده و مدل‌های شبکه‌های عصبی ابتدا در دهه ۱۹۴۰ توسط «Walter pittsWarren McCulloch» معرفی شد.

سپس در دهه ۱۹۵۰ کارهای روزنبالت (Rosenblatt) در مورد شبکه‌های دو لایه مورد توجه قرار گرفت. در دهه ۱۹۴۷ الگوریتم backpropagation توسط Werbos معرفی شد ولی متدولوژی شبکه‌های عصبی عمدتا از دهه ۱۹۸۰ به این سو رشد زیادی کرد و مورد استقبال دانشمندان قرار گرفت. منطق‌ فازی ابتدا توسط پروفسور لطفی‌زاده،‌ در سال ۱۹۶۵ معرفی شد و از آن زمان به بعد توسط خود او و دیگر دانشمندان دنبال شد.در دهه ۱۹۸۰ تلاش‌های دانشمندان ژاپنی برای کاربردی کردن منطق فازی به ترویج و معرفی منطق فازی کمک زیادی کرد. مثلا طراحی و شبیه‌سازی سیستم کنترل فازی برای راه‌آهن Sendiaتوسط دو دانشمند به نام‌های Yasunobo و Miyamoto در سال ۱۹۸۵، نمایش کاربرد سیستم‌های کنترل فازی از طریق چند تراشه‌ مبتنی بر منطق فازی در آزمون «پاندول معکوس» توسطTakeshi Yamakawa در همایش بین‌المللی پژوهشگران منطق فازی در توکیو در سال ۱۹۸۷ و نیز استفاده از سیستم‌های فازی در شبکه مونوریل توکیو و نیز معرفی سیستم ترمز ABS مبتنی بر کنترل‌های فازی توسط اتومبیل‌‌سازی هوندا در همین دهه تاثیر زیادی در توجه مجدد دانشمندان جهان به این حوزه از علم داشت.

 

● فراتر از هوشمندی ماشین

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

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


/fa.wikipedia.org

http://www.meta4u.com

http://www.meta4u.com

 

 

روش جستجوی تکاملی

روش‌های جستجوی ناآگاهانه، آگاهانه و فراابتکاری (به انگلیسی: Metaheuristic) برای حل مسائل هوش مصنوعی بسیار کارآمد می‌باشند. در مورد مسائل بهینه‌سازی اغلب روش‌های آگاهانه و ناآگاهانه جوابگوی نیاز نخواهند بود چرا که بیشتر مسائل بهینه‌سازی در رده مسائل NP قرار دارند؛ بنابراین نیاز به روش جستجوی دیگری وجود دارد که بتواند در فضای حالت مسائل NP به طرف جواببهینه سراسری حرکت کند. بدین منظور روش‌های جستجوی فراابتکاری مطرح می‌شوند، این روش‌های جستجو می‌توانند به سمت بهینگی‌های سراسری مسئله حرکت کنند. در کنار روش‌های جستجوی فراابتکاری، روش‌های جستجوی دیگری نیز وجود دارند که به روش‌های تکاملی معروفند.

نظریه تکامل

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

انواع مختلف الگوریتم‌های تکاملی

انواع مختلف الگوریتم‌های تکاملی که تا بحال مطرح شده‌اند، به شرح زیر می‌باشد:

الگوریتم ژنتیک

الگوریتم تکاملی

Evolutionary Strategies

Evolutionary Programming

Differential Evolution

Cultural Evolution

هم‌فرگشتی

کدگذاری و نحوه نمایش

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

حل مسائل معروف با استفاده از الگوریتم ژنتیک

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

فلو چارت این الگوریتم

شمای کلی سودوکوی الگوریتم

منبع

 

برای استفاده از یک الگوریتم ژنتیک، جمعیتی از افراد در داخل یک فضای جستجو حفظ می شود. هر کدام از این افراد در واقع یک راه حل ممکن برای مسئله مطرح شده هستند. هر عضو با برداری با طول محدود از اجزا یا متغیر ها و با کمک حروف الفبای خاصی کدگذاری می شود. این حروف الفبای خاص معمولا الفبای باینری  هستند. برای ادامه دادن شباهت این روش با اصول ژنتیکی، هر کدام از اعضا به عنوان کروموزوم تعبیر می شوند و متغیر ها مشابه ژن ها در نظر گرفته می شوند. پس یک کروموزوم ( یک جواب برای مسئله)، از چند ژن (متغیر ها) تشکیل شده است. برای نشان دادن توانایی های هر یک از اعضا برای رقابت با دیگر اعضای جمعیت، به هر کدام از جواب های ممکن یک امتیاز برازندگی اختصاص داده می شود.
عضوی که دارای امتیاز برازندگی بهینه ( یا به طور کلی تر نزدیک به بهینه) است دلخواه ما می باشد. هدف الگوریتم ژنتیک، استفاده از “پرورش” انتخابی جواب ها، برای ایجاد فرزندانی بهتر از والدین به کمک ترکیب کردن اطلاعات کروموزوم ها است.

الگوریتم ژنتیک قسمت ۱
الگوریتم ژنتیک قسمت ۲

برنامه نویسی Parallel در سی شارپ و آشنایی با کلاس Task در سی شارپ

در قسمت قبل گفتیم که بوسیله کلاس Parallel و متدهای For و ForEach عملیات پردازش بر روی مجموعه ها را به صورت Parallel انجام دهیم. اما بحث Parallel Programming به همین جا ختم نمی شود و راه های دیگری نیز برای برنامه نویسی Parallel وجود دارد. یکی از این روش ها استفاده از کلاس Task است که این کلاس نیز در فضای نام System.Threading.Tasks قرار دارد. حالت های مختلفی برای استفاده از این کلاس وجود دارد که ساده ترین آن استفاده از خصوصیت Factory و متد StartNew است که در زیر نمونه ای از نحوه ایجاد یک Task را مشاهده می کنید:

 
Task.Factory.StartNew(()  = &gt;
{
    Console.WriteLine("Task Started in Thread {0}", Thread.CurrentThread.ManagedThreadId);
    for (int i = 1; i  &lt; =  100; i++)
    {
        Console.WriteLine(i);
        Thread.Sleep(500);
    }
});

بوسیله کد بالا، یک Task جدید ایجاد شده که اعداد ۱ تا ۱۰۰ را در یک thread جداگانه در خروجی چاپ می شود. دقت کنید که بعد از اجرای برنامه شناسه Thread ای که Task در آن اجرا می شود با شناسه Thread اصلی برنامه متفاوت است. راهکار بعدی ایجاد یک شئ از روی کلاس Task و ارجرای آن است، در کد زیر Task بالا را به صورت ایجاد شئ ایجاد می کنیم:

 
Task task = new Task(()  = &gt;
{
    Console.WriteLine("Task Started in Thread {0}", Thread.CurrentThread.ManagedThreadId);
    for (int i = 1; i  &lt; = 100; i++)
    {
        Console.WriteLine(i);
        Thread.Sleep(500);
    }
});
 
task.Start();
Console.ReadKey();

زمانی که Task جدیدی ایجاد می کنید بوسیله متد Start که برای کلاس Task تعریف شده است می توانید عملیات اجرای Task را شروع کنید. یکی از خصوصیت های تعریف شده در کلاس Task، خصوصیت IsCompleted است که بوسیله آن می توان تشخیص داد که Task در حال اجراست یا خیر:

 
Task task = new Task(()  = &gt;
{
    Console.WriteLine("Task Started in Thread {0}", Thread.CurrentThread.ManagedThreadId);
    for (int i = 1; i &lt; =  100; i++)
    {
        Console.WriteLine(i);
        Thread.Sleep(500);
    }
});
 
task.Start();
while (!task.IsCompleted)
{
                 
}

دریافت خروجی از کلاس Task

می توان برای کلاس Task یک خروجی مشخص کرد، فرض کنید می خواهیم Task ای بنویسیم که میانگین حاصل جمع اعداد ۱ تا ۱۰۰ را حساب کرده و به عنوان خروجی بازگرداند. برای اینکار باید از کلاس Task که یک پارامتر جنریک دارد استفاده کنیم. ابتدا یک متد به صورت زیر تعریف می کنیم:

 
public static int CalcAverage()
{
    int sum = 0;
 
    for (int i = 1; i &lt; = 100; i++)
    {
        sum += i;
    }
 
    return sum/100;
}

در قدم بعدی می بایست یک Task جنریک از نوع int تعریف کنیم و به عنوان سازنده نام متد تعریف شده را به آن ارسال کنیم:

 
Task &lt; int &gt;  task = new Task &lt; int &gt; (CalcAverage);
task.Start();
 
Console.WriteLine("Average: {0}", task.Result);

در کلاس بالا بعد از Start کردن Task، بوسیله خصوصیت Result می توانیم نتیجه خروجی از Task را بگیریم، دقت کنید که زمانی که می خواهیم مقدار خروجی را از Task بگیریم، برنامه منتظر می شود تا عملیات Task به پایان برسد و سپس نتیجه در خروجی چاپ می شود.

به این موضوع توجه کنید که بوسیله متد StartNew نیز می توان Task هایی که پارامتر خروجی دارند تعریف کرد:

 
var task = Task.Factory.StartNew &lt; int &gt; (CalcAverage);

کد بالا دقیقاً کار نمونه قبلی را انجام می دهد، فقط به جای ایجاد شئ Task و فراخوانی آن، از متد StartNew استفاده کردیم.

ارسال پارامتر به Task ها

یکی از قابلیت های Task ها امکان ارسال State به متدی است که قرار است به عنوان Task اجرا شود. برای مثال، فرض کنید در مثال قبلی که Task ایجاد شده حاصل میانگین را حساب کرده و به عنوان خروجی بر میگرداند می خواهیم عدد شروع و پایان را مشخص کنیم، برای اینکار ابتدا یک کلاس به صورت زیر تعریف می کنیم:

 
public class TaskParameters
{
    public int Start { get; set; }
    public int Finish { get; set; }
}

در ادامه کد متد CalcAverage را به صورت زیر تغییر می دهیم:

 
public static int CalcAverage(object state)
{
    var parameters = (TaskParameters) state;
    int sum = 0;
 
    for (int i = parameters.Start; i &lt; = parameters.Finish; i++)
    {
        sum += i;
    }
 
    return sum/100;
}

در قدم بعدی باید روند ساخت شئ Task را به گونه ای تغییر دهیم که پارامترهای مورد نظر به عنوان state به متد CalcAverage ارسال شوند، برای اینکار به عنوان پارامتر دوم سازنده کلاس Task شئ ای از نوع TaskParameters به صورت زیر ارسال می کنیم:

 
Task &lt; int &gt; task = new Task &lt; int &gt; (CalcAverage, new TaskParameters()
{
    Start = 100,
    Finish = 1000
});

با انجام تغییرات بالا، توانستیم شئ ای را به عنوان State به Task ارسال کنیم، همچنین توجه کنید که امکان ارسال State بوسیله متد StartNew در خصوصیت Factory نیز وجود دارد. در این بخش با کلاس Task آشنا شدیم، در قسمت بعدی با نحوه متوقف کردن Task ها در زمان اجرا و کلاس CancellationToken آشنا می شویم.

منبع


قسمت اول آموزش-برنامه نویسی 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 به صورت همزمان در سی شارپ