بایگانی برچسب برای: نرم افزار باسکول

کار با 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 می توان مکانیزمی پیاده کرد که کدهای مورد نظر در بازه های زمانی خاص، مثلاً هر 5 ثانیه یکبار و در یک 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 های گفته شده در مرحله 2 ساختیم را به آن ارسال کنیم.
  4. اطلاعات و تنظیمات اولیه مورد نظر برای Thread مانند Name یا Priority را برای آن ست کنیم.
  5. متد Start را در کلاس Thread را برای شروع کار Thread فراخوانی کنیم. با این کار متدی که در مرحله 2 مشخص کردیم در یک Thread جداگانه اجرا می شود.

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

 

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

گام بعدی در واقع این است که در امتداد لبه ها بر اساس نقاط قوت و جهت های لبه که قبلا محاسبه شده است ردیابی شود. هر پیکسل از طریق استفاده از دو تودرتو برای حلقه ها چرخه می زند. اگر پیکسل فعلی دارای قدرت شیب بیشتر از مقدار 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	10

	// 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
		0,											// Row position to display bitmap in videoportal
		0,											// 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:
	// (1) grayScaleTheFrameData - which mallocs m_destinationBmp
	// (2) doMyImageProcesing
	// (3) 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 در سی پلاس پلاس قسمت 1
الگوریتم Canny در سی پلاس پلاس قسمت 2
الگوریتم Canny در سی پلاس پلاس قسمت 3
الگوریتم Canny در سی پلاس پلاس قسمت 4

عامل های هدف گرا

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

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

برای مثال فرض کنید که راننده هوشمند قصد دارد تا مسیری را از میان خیابان های تو در تو یک شهر بزرگ برای رسیدن به مقصد  انتخاب نماید. (Google Map یک مثال کاربردی آن میباشد) آیا این کار به سادگی انتخاب یک واکنش خواهد بود؟! نکته ی مهمی که باید به آن توجه داشت این است که در عامل های هدف گرا مکانیزم تصمیم گیری شباهتی به مکانیزم استراتژی واکنشی ساده ندارد. در این عامل ها باید همواره به این دو سوال جواب داد.

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

عامل های مبتنی بر سودمندی

گاهی اوقات تنها رسیدن به هدف نیست که در افزایش بازدهی عامل هوشمند موثر است. برای مثال فرض کنید میخواهیم از تهران به چالوس سفر کنیم. برای این سفر انتخاب های متعددی میتواند توسط راننده هوشمند انجام شود. اگر قرار باشد راننده هوشمند در محاسبات خود تنها به رسین به هدف توجه کند ممکن است در طول سفر خود از زاهدان عبور نماییم! دلیل این اتفاق کاملا واضح است. در این مسئله برای عامل هیچ اهمیتی ندارد که کدام مسیر را انتخاب نماید!

تنها موردی که برای عامل اهمپیت دارد رسیدن به هدف است! بدین ترتیب در این مثال سفر 5 ساعته ما به 50 ساعت افزایش پیدا خواهد کرد و عملا هوشمندی عامل مورد نظر شکست خورده و فاقد ارزش میباشد. (در مطالبی که مرتبط با الگورتیم های جستجوی هدف میباشند این مشکل را به وضوح مشاهده خواهید کرد.) پس میتوان نتیجه گیری نمود که  در مسئله فوق عواملی جز هدف وجود دارند که در استراتژی انتخاب واکنش ها تتاثیر چشم گیری میگذارند.

اگر بخواهیم خیلی ساده عامل های مبتنی بر سودمندی را تعریف نماییم میتوانیم بگوییم اگر دو عامل داشته باشیم که برای حل یک مسئله یکسان دو مجموعه واکنش را به عنوان خروجی بازگردانند (دو راه حل متفاوت) بطوری یکی از این دو مجموعه بر دیگری برتری داشته و ترجیح داده شود (هزینه ی کمتری داشته باشد) میگوییم سودمندی یک عامل از دیگری بیشتر است. در کتاب راسل مفهوم سودمندی را فانکشنی دانسته که میتواند وضعیت عامل را به یک عدد حقیقی نسبت داده که این عدد نشان دهنده میزان کسب موفقیت توسط عامل میباشد. استفاده از چنین عاملی در دوحالت باعث گرفتن تصمیم عاقلانه در زمان رخ دادن مشکل بین اهداف میشود.

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

منبع : http://retro-code.ir


ساختار عامل های هوشمند

کار AI طراحی برنامه ی عامل است که “تابع عامل” را پیاده سازی می کند. تابع عامل، ادراکات را به فعالیت ها نگاشت می کند. فرض میکنیم این برنامه بر روی یک دستگاه محاسباتی با حسگرها و محرک های فیزیکی، یعنی معماری اجرا می شود: برنامه + معماری = عامل

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

♦ برنامه های عامل  (agent programs)

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

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

function TABLE-DRIVEN-AGENT (percept) returns an action
  presistent: percepts, a sequence, initially empty table, a table of actions, indexed by percept sequences, initially fully specified
    
  append percept to end of percepts
  action  < - -  LOOKUP(percepts,table)
  return action
 

برای هر درک جدید فراخوانی می شود و هر بار فعالیتی را بر می گرداند. با استفاده از برنامه TABLE-DRIVEN-AGENT ساختمان داده های خود، دنباله ادراک را ردیابی می کند.
برنامه TABLE-DRIVEN-AGENT  تابع عامل مطلوب را پیاده سازی میکند. چالش مهم AI، چگونگی نوشتن برنامه ای است که با استفاده از یک کد کوچک (به جای جدول بزرگ)، رفتار عقلایی را انجام دهد. مثال های زیادی داریم که نشان می دهد، این کار امکان پذیر است. به عنوان مثال، جدول های بزرگ ریشه دوم که قبل از دهه 1970 توسط مهندسین و دانش آموزان مورد استفاده قرار گرفت، جای خود را به یک برنامه  5 خطی داده است که از روش نیوتن استفاده میکند و در ماشین حساب های الکترونیکی قابل استفاده است. AI همان کاری را انجام می دهد که نیوتن برای ریشه دوم انجام میدهد.

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

♦ عامل های واکنشی ساده (simple reflex agents)

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

انسان نیز  چنین اتصالاتی دارد، که بعضی از آنها پاسخ های آموخته شده هستند (مثل رانندگی) و بعضی دیگر غریزی هستند (مثل بستن چشم هنگام نزدیک شدن شی ء ای به آن). روش کلی و قابل انعطاف این است که یک مفسر همه منظوره برای قوانین شرط فعالیت ساخته شود و سپس مجموعه ای از قوانین برای محیط های کار خاص ایجاد گردد. در شکل زیر، برنامه ی عامل نشان داده شده است که خیلی ساده است. تابع INTERPRET-INPUT با استفاده از ادراک، یک توصیف انتزاعی از حالت فعلی ایجاد میکند، و تابع RULE-MATCH اولین قانون موجود در مجموعه ای از قوانین را بر می گرداند که با توصیف حالت خاص مطابقت دارد:

function  SIMPLE-REFLEX-AGENT (percept) returns an action
  presistent: ruless, a set of condition-action rules
    
  state  <  - - INTEERPRET-INPUT (percept)
  rule  < - -  RULE-MATCH (state,rules)
  action  <  - -  rule.ACTION
  return action
 

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

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

♦ عامل های واکنشی مبتنی بر مدل (model-based reflex  agents)

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

تغییر این اطلاعات داخلی با مرور زمان، مستلزم دو نوع دانش است که باید در برنامه عامل کدنویسی شود. اولا باید بدانیم که دنیا چگونه مستقل از عامل تکامل می یابد. ثانیا، باید بدانیم که فعالیت های عامل، چه تاثیری در دنیا دارد. این دانش درباره ی “چگونگی عملکرد جهان” چه به صورت مدارهای منطقی ساده پیاده سازی شود یا به صورت تئوری های علمی، مدلی از دنیا نام دارد. عاملی که از چنین مدلی استفاده میکند، عامل مبتنی بر مدل نام دارد. برنامه عامل در شکل زیر نشان داده شده است. بخش جالب، تابع UPDATE-STATE است که مسئول ایجاد توصیف جدیدی از حالت داخلی است. جزئیات چگونگی نمایش مدل ها و حالت ها، به نوع محیط و فناوری استفاده شده در طراحی عامل بستگی دارد.

function  MODEL-BASED-REFLEX-AGENT (percept) returns an action
  presistent: state, the agent's current conception of the world state
              model, a description of how the next state depends on current state and action
              rules, a set of condition-action rules 
              action, the most recent action, initially none
  
  state  < -- UPDATE-STATE (state, action, peercept, model)
  rule  < -- RULE-MATCH (state,rules)
  action  < -- rule.ACTION
  return action
 

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

عامل های هوشمند قسمت 1
عامل های هوشمند قسمت 2
عامل های هوشمند قسمت 3
عامل های هوشمند قسمت 4
عامل های هوشمند قسمت 5

الگوریتم Canny

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

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

 

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

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

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

 

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

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

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

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

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

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

 

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

 

 

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

برنامه زیر یک فایل BMP سیاه و سفید 8 بیت در هر پیکسل را می خواند و نتیجه را در ‘out.bmp’ ذخیره می کند.با `-lm ‘ کامپایل می شود.

 

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <float.h>
#include <math.h>
#include <string.h>
#include <stdbool.h>
#include <assert.h>
 
#define MAX_BRIGHTNESS 255
 
// C99 doesn't define M_PI (GNU-C99 does)
#define M_PI 3.14159265358979323846264338327
 
/*
 * Loading part taken from
 * http://www.vbforums.com/showthread.php?t=261522
 * BMP info:
 * http://en.wikipedia.org/wiki/BMP_file_format
 *
 * Note: the magic number has been removed from the bmpfile_header_t
 * structure since it causes alignment problems
 * bmpfile_magic_t should be written/read first
 * followed by the
 * bmpfile_header_t
 * [this avoids compiler-specific alignment pragmas etc.]
 */
 
typedef struct {
 uint8_t magic[2];
} bmpfile_magic_t;
 
typedef struct {
 uint32_t filesz;
 uint16_t creator1;
 uint16_t creator2;
 uint32_t bmp_offset;
} bmpfile_header_t;
 
typedef struct {
 uint32_t header_sz;
 int32_t width;
 int32_t height;
 uint16_t nplanes;
 uint16_t bitspp;
 uint32_t compress_type;
 uint32_t bmp_bytesz;
 int32_t hres;
 int32_t vres;
 uint32_t ncolors;
 uint32_t nimpcolors;
} bitmap_info_header_t;
 
typedef struct {
 uint8_t r;
 uint8_t g;
 uint8_t b;
 uint8_t nothing;
} rgb_t;
 
// Use short int instead `unsigned char' so that we can
// store negative values.
typedef short int pixel_t;
 
pixel_t *load_bmp(const char *filename,
 bitmap_info_header_t *bitmapInfoHeader)
{
 FILE *filePtr = fopen(filename, "rb");
 if (filePtr == NULL) {
 perror("fopen()");
 return NULL;
 }
 
 bmpfile_magic_t mag;
 if (fread(&mag, sizeof(bmpfile_magic_t), 1, filePtr) != 1) {
 fclose(filePtr);
 return NULL;
 }
 
 // verify that this is a bmp file by check bitmap id
 // warning: dereferencing type-punned pointer will break
 // strict-aliasing rules [-Wstrict-aliasing]
 if (*((uint16_t*)mag.magic) != 0x4D42) {
 fprintf(stderr, "Not a BMP file: magic=%c%c\n",
 mag.magic[0], mag.magic[1]);
 fclose(filePtr);
 return NULL;
 }
 
 bmpfile_header_t bitmapFileHeader; // our bitmap file header
 // read the bitmap file header
 if (fread(&bitmapFileHeader, sizeof(bmpfile_header_t),
 1, filePtr) != 1) {
 fclose(filePtr);
 return NULL;
 }
 
 // read the bitmap info header
 if (fread(bitmapInfoHeader, sizeof(bitmap_info_header_t),
 1, filePtr) != 1) {
 fclose(filePtr);
 return NULL;
 }
 
 if (bitmapInfoHeader->compress_type != 0)
 fprintf(stderr, "Warning, compression is not supported.\n");
 
 // move file point to the beginning of bitmap data
 if (fseek(filePtr, bitmapFileHeader.bmp_offset, SEEK_SET)) {
 fclose(filePtr);
 return NULL;
 }
 
 // allocate enough memory for the bitmap image data
 pixel_t *bitmapImage = malloc(bitmapInfoHeader->bmp_bytesz *
 sizeof(pixel_t));
 
 // verify memory allocation
 if (bitmapImage == NULL) {
 fclose(filePtr);
 return NULL;
 }
 
 // read in the bitmap image data
 size_t pad, count=0;
 unsigned char c;
 pad = 4*ceil(bitmapInfoHeader->bitspp*bitmapInfoHeader->width/32.) - bitmapInfoHeader->width;
 for(size_t i=0; i<bitmapInfoHeader->height; i++){
 for(size_t j=0; j<bitmapInfoHeader->width; j++){
 if (fread(&c, sizeof(unsigned char), 1, filePtr) != 1) {
 fclose(filePtr);
 return NULL;
 }
 bitmapImage[count++] = (pixel_t) c;
 }
 fseek(filePtr, pad, SEEK_CUR);
 }
 
 // If we were using unsigned char as pixel_t, then:
 // fread(bitmapImage, 1, bitmapInfoHeader->bmp_bytesz, filePtr);
 
 // close file and return bitmap image data
 fclose(filePtr);
 return bitmapImage;
}
 
// Return: true on error.
bool save_bmp(const char *filename, const bitmap_info_header_t *bmp_ih,
 const pixel_t *data)
{
 FILE* filePtr = fopen(filename, "wb");
 if (filePtr == NULL)
 return true;
 
 bmpfile_magic_t mag = {{0x42, 0x4d}};
 if (fwrite(&mag, sizeof(bmpfile_magic_t), 1, filePtr) != 1) {
 fclose(filePtr);
 return true;
 }
 
 const uint32_t offset = sizeof(bmpfile_magic_t) +
 sizeof(bmpfile_header_t) +
 sizeof(bitmap_info_header_t) +
 ((1U << bmp_ih->bitspp) * 4);
 
 const bmpfile_header_t bmp_fh = {
 .filesz = offset + bmp_ih->bmp_bytesz,
 .creator1 = 0,
 .creator2 = 0,
 .bmp_offset = offset
 };
 
 if (fwrite(&bmp_fh, sizeof(bmpfile_header_t), 1, filePtr) != 1) {
 fclose(filePtr);
 return true;
 }
 if (fwrite(bmp_ih, sizeof(bitmap_info_header_t), 1, filePtr) != 1) {
 fclose(filePtr);
 return true;
 }
 
 // Palette
 for (size_t i = 0; i < (1U << bmp_ih->bitspp); i++) {
 const rgb_t color = {(uint8_t)i, (uint8_t)i, (uint8_t)i};
 if (fwrite(&color, sizeof(rgb_t), 1, filePtr) != 1) {
 fclose(filePtr);
 return true;
 }
 }
 
 // We use int instead of uchar, so we can't write img
 // in 1 call any more.
 // fwrite(data, 1, bmp_ih->bmp_bytesz, filePtr);
 
 // Padding: http://en.wikipedia.org/wiki/BMP_file_format#Pixel_storage
 size_t pad = 4*ceil(bmp_ih->bitspp*bmp_ih->width/32.) - bmp_ih->width;
 unsigned char c;
 for(size_t i=0; i < bmp_ih->height; i++) {
 for(size_t j=0; j < bmp_ih->width; j++) {
 c = (unsigned char) data[j + bmp_ih->width*i];
 if (fwrite(&c, sizeof(char), 1, filePtr) != 1) {
 fclose(filePtr);
 return true;
 }
 }
 c = 0;
 for(size_t j=0; j<pad; j++)
 if (fwrite(&c, sizeof(char), 1, filePtr) != 1) {
 fclose(filePtr);
 return true;
 }
 }
 
 fclose(filePtr);
 return false;
}
 
// if normalize is true, map pixels to range 0..MAX_BRIGHTNESS
void convolution(const pixel_t *in, pixel_t *out, const float *kernel,
 const int nx, const int ny, const int kn,
 const bool normalize)
{
 assert(kn % 2 == 1);
 assert(nx > kn && ny > kn);
 const int khalf = kn / 2;
 float min = FLT_MAX, max = -FLT_MAX;
 
 if (normalize)
 for (int m = khalf; m < nx - khalf; m++)
 for (int n = khalf; n < ny - khalf; n++) {
 float pixel = 0.0;
 size_t c = 0;
 for (int j = -khalf; j <= khalf; j++)
 for (int i = -khalf; i <= khalf; i++) {
 pixel += in[(n - j) * nx + m - i] * kernel;
 c++;
 }
 if (pixel < min)
 min = pixel;
 if (pixel > max)
 max = pixel;
 }
 
 for (int m = khalf; m < nx - khalf; m++)
 for (int n = khalf; n < ny - khalf; n++) {
 float pixel = 0.0;
 size_t c = 0;
 for (int j = -khalf; j <= khalf; j++)
 for (int i = -khalf; i <= khalf; i++) {
 pixel += in[(n - j) * nx + m - i] * kernel;
 c++;
 }
 
 if (normalize)
 pixel = MAX_BRIGHTNESS * (pixel - min) / (max - min);
 out[n * nx + m] = (pixel_t)pixel;
 }
}
 
/*
 * gaussianFilter:
 * http://www.songho.ca/dsp/cannyedge/cannyedge.html
 * determine size of kernel (odd #)
 * 0.0 <= sigma < 0.5 : 3
 * 0.5 <= sigma < 1.0 : 5
 * 1.0 <= sigma < 1.5 : 7
 * 1.5 <= sigma < 2.0 : 9
 * 2.0 <= sigma < 2.5 : 11
 * 2.5 <= sigma < 3.0 : 13 ...
 * kernelSize = 2 * int(2*sigma) + 3;
 */
void gaussian_filter(const pixel_t *in, pixel_t *out,
 const int nx, const int ny, const float sigma)
{
 const int n = 2 * (int)(2 * sigma) + 3;
 const float mean = (float)floor(n / 2.0);
 float kernel[n * n]; // variable length array
 
 fprintf(stderr, "gaussian_filter: kernel size %d, sigma=%g\n",
 n, sigma);
 size_t c = 0;
 for (int i = 0; i < n; i++)
 for (int j = 0; j < n; j++) {
 kernel = exp(-0.5 * (pow((i - mean) / sigma, 2.0) +
 pow((j - mean) / sigma, 2.0)))
 / (2 * M_PI * sigma * sigma);
 c++;
 }
 
 convolution(in, out, kernel, nx, ny, n, true);
}
 
/*
 * Links:
 * http://en.wikipedia.org/wiki/Canny_edge_detector
 * http://www.tomgibara.com/computer-vision/CannyEdgeDetector.java
 * http://fourier.eng.hmc.edu/e161/lectures/canny/node1.html
 * http://www.songho.ca/dsp/cannyedge/cannyedge.html
 *
 * Note: T1 and T2 are lower and upper thresholds.
 */
pixel_t *canny_edge_detection(const pixel_t *in,
 const bitmap_info_header_t *bmp_ih,
 const int tmin, const int tmax,
 const float sigma)
{
 const int nx = bmp_ih->width;
 const int ny = bmp_ih->height;
 
 pixel_t *G = calloc(nx * ny * sizeof(pixel_t), 1);
 pixel_t *after_Gx = calloc(nx * ny * sizeof(pixel_t), 1);
 pixel_t *after_Gy = calloc(nx * ny * sizeof(pixel_t), 1);
 pixel_t *nms = calloc(nx * ny * sizeof(pixel_t), 1);
 pixel_t *out = malloc(bmp_ih->bmp_bytesz * sizeof(pixel_t));
 
 if (G == NULL || after_Gx == NULL || after_Gy == NULL ||
 nms == NULL || out == NULL) {
 fprintf(stderr, "canny_edge_detection:"
 " Failed memory allocation(s).\n");
 exit(1);
 }
 
 gaussian_filter(in, out, nx, ny, sigma);
 
 const float Gx[] = {-1, 0, 1,
 -2, 0, 2,
 -1, 0, 1};
 
 convolution(out, after_Gx, Gx, nx, ny, 3, false);
 
 const float Gy[] = { 1, 2, 1,
 0, 0, 0,
 -1,-2,-1};
 
 convolution(out, after_Gy, Gy, nx, ny, 3, false);
 
 for (int i = 1; i < nx - 1; i++)
 for (int j = 1; j < ny - 1; j++) {
 const int c = i + nx * j;
 // G = abs(after_Gx) + abs(after_Gy);
 G = (pixel_t)hypot(after_Gx, after_Gy);
 }
 
 // Non-maximum suppression, straightforward implementation.
 for (int i = 1; i < nx - 1; i++)
 for (int j = 1; j < ny - 1; j++) {
 const int c = i + nx * j;
 const int nn = c - nx;
 const int ss = c + nx;
 const int ww = c + 1;
 const int ee = c - 1;
 const int nw = nn + 1;
 const int ne = nn - 1;
 const int sw = ss + 1;
 const int se = ss - 1;
 
 const float dir = (float)(fmod(atan2(after_Gy,
 after_Gx) + M_PI,
 M_PI) / M_PI) * 8;
 
 if (((dir <= 1 || dir > 7) && G > G[ee] &&
 G > G[ww]) || // 0 deg
 ((dir > 1 && dir <= 3) && G > G[nw] &&
 G > G[se]) || // 45 deg
 ((dir > 3 && dir <= 5) && G > G[nn] &&
 G > G[ss]) || // 90 deg
 ((dir > 5 && dir <= 7) && G > G[ne] &&
 G > G[sw])) // 135 deg
 nms = G;
 else
 nms = 0;
 }
 
 // Reuse array
 // used as a stack. nx*ny/2 elements should be enough.
 int *edges = (int*) after_Gy;
 memset(out, 0, sizeof(pixel_t) * nx * ny);
 memset(edges, 0, sizeof(pixel_t) * nx * ny);
 
 // Tracing edges with hysteresis . Non-recursive implementation.
 size_t c = 1;
 for (int j = 1; j < ny - 1; j++)
 for (int i = 1; i < nx - 1; i++) {
 if (nms >= tmax && out == 0) { // trace edges
 out = MAX_BRIGHTNESS;
 int nedges = 1;
 edges[0] = c;
 
 do {
 nedges--;
 const int t = edges[nedges];
 
 int nbs[8]; // neighbours
 nbs[0] = t - nx; // nn
 nbs[1] = t + nx; // ss
 nbs[2] = t + 1; // ww
 nbs[3] = t - 1; // ee
 nbs[4] = nbs[0] + 1; // nw
 nbs[5] = nbs[0] - 1; // ne
 nbs[6] = nbs[1] + 1; // sw
 nbs[7] = nbs[1] - 1; // se
 
 for (int k = 0; k < 8; k++)
 if (nms[nbs[k]] >= tmin && out[nbs[k]] == 0) {
 out[nbs[k]] = MAX_BRIGHTNESS;
 edges[nedges] = nbs[k];
 nedges++;
 }
 } while (nedges > 0);
 }
 c++;
 }
 
 free(after_Gx);
 free(after_Gy);
 free(G);
 free(nms);
 
 return out;
}
 
int main(const int argc, const char ** const argv)
{
 if (argc < 2) {
 printf("Usage: %s image.bmp\n", argv[0]);
 return 1;
 }
 
 static bitmap_info_header_t ih;
 const pixel_t *in_bitmap_data = load_bmp(argv[1], &ih);
 if (in_bitmap_data == NULL) {
 fprintf(stderr, "main: BMP image not loaded.\n");
 return 1;
 }
 
 printf("Info: %d x %d x %d\n", ih.width, ih.height, ih.bitspp);
 
 const pixel_t *out_bitmap_data =
 canny_edge_detection(in_bitmap_data, &ih, 45, 50, 1.0f);
 if (out_bitmap_data == NULL) {
 fprintf(stderr, "main: failed canny_edge_detection.\n");
 return 1;
 }
 
 if (save_bmp("out.bmp", &ih, out_bitmap_data)) {
 fprintf(stderr, "main: BMP image not saved.\n");
 return 1;
 }
 
 free((pixel_t*)in_bitmap_data);
 free((pixel_t*)out_bitmap_data);
 return 0;
}

 

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

Canny in C

رمز فایل : behsanandish.com

الگوریتم Canny

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

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

 

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

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

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

 

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

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

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

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

4- برخی از لبه های کشف شده واقعا لبه نیستند و در واقع نویز هستند که باید آنها توسط حد آستانه هیسترزیس فیلتر شوند.هیسترزیس از دو حد آستانه بالاتر (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

 

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

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

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

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

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

 

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

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

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

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

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

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

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

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

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

الگوریتم‌ها

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

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

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

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

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

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

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

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

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

 

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

 

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

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

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

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

 

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

 

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

منبع


 

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

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

 

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

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

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

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

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

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

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

 

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

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

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

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

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

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

تاریخچه

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

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

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

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

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

نگارخانه

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

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

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

1) لنز دوربین
2)نگه دارنده ی لنز
3)دیافراگم
4)چرخاننده ی فیلم
5)فیلم عکاسی
6)محل اتصال بند دوربین
7)شاتر
8)دکمه ی کنترل سرعت عکسبرداری
9)صفحه ی مشخصات عکس و شارژ دوربین(در دوربین هایی که باتری دارند)
10)ویزور
11)محل اتصال فلش خارجی
12)حلقه ی فوکوس

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

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

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

یکی از مزیت های دوربین آنالوگ نشان دادن خیلی خوب و واقعی رنگ است. اما اگر به یک نقطه نور زیاد برسد (در واقع اور اکسپوز بشود) آن قسمت کاملا سفید خواهد شد.سپس فیلم ظاهر شده اسکن و اینورت می شود و عکس قابلیت چاپ پیدا می کند. حساسیت فیلم های مورد استفاده قرار گرفته به مواد شیمیایی موجود در اون وابسته است.
فیلم های سیاه سفید از حساسیت 100 تا 3200 یا حتی 6400 برخوردارند اما فیلم های رنگی معمولا دارای حساسیت های 100 و 200 و 400 هستند. هرچه حساسیت بیشتر باشد ذرات شیمیایی روی صفحه فیلم بزرگتر هستندکه این باعث پایین آمدن کیفیت عکس خواهد شد ولی فقط در چاپ به ابعاد خیلی بزرگ این افت کیفیت احساس میشود.

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

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


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

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

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

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

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

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

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

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

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

9- قابلیت گرفتن صدها عکس بدون این که نیازی به تغییر چیزی باشد. در حالی که در دوربین های فیلمی بعد از 24 یا 36 عکس باید فیلم را عوض کرد.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

 


منابع

1.http://fa.wikipedia.org

2. http://forum.avastarco.com

3. http://www.kingit.ir

4. http://farnet.ir

با سلام. قصد دارم در این پست تعدادی از منابع اصلی آموش پردازش تصویر و بینایی ماشین رو معرفی کنم.

امیدوارم که مفید باشد

 

لگچرهای کتاب آقای گنزالس

تعداد فایل : 17 عدد

فرمت: pdf

زبان : انگلیسی

نویسنده: گنزالس

پسورد فایل: behsanandish.com

دانلود

 

______________________________________

 

مفاهیم پایه پردازش تصویر دانشگاه شهید بهشتی

تعداد صفحه: 109 صفحه

فرمت: pdf

زبان : فارسی

نویسنده: ‫احمد محمودی ازناوه‬

پسورد فایل: behsanandish.com

دانلود

فهرست مطالب:
‫• مقدمه اي بر پردازش تصوير‬
‫– كاربردهاي پردازش تصوير‬
‫• ساختار تصوير ديجيتال‬
‫• تصاوير رنگي‬
‫• حساسيت چشم‬
‫– تباين‬
‫• حسگرهاي تصوير‬
‫• آشنايي با ‪Matlab‬‬
‫• آشنايي با فضارنگها‬

 

______________________________________

 

 

روش بهینه‌سازی ازدحام ذرات

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

انواع الگوریتم ازدحام ذرات

الگوریتم ازدحام ذرات پیوسته

مقدمه

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

در ادامه کمی به توضیح مفهوم هوش جمعی میپردازیم.

هوش جمعی

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

اولین الگوریتم ازدحام ذرات

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

به ا ین شکل است که گروهٔ از ذرات در آغاز کار به صورت تصادفی به وجود می‌آیند و با به روز کردن نسلها سعی در یافتن راه‌حل بهینه می‌نمایند. در هر گام، هر ذره با استفاده از دو بهترین مقدار به روز می‌شود. اولین مورد، بهترین موقعیتی است که تاکنون ذره موفق به رسیدن به آن شده‌است. موقعیت مذکور شناخته و نگهداری می‌شود که این بهترین مقدار نوستالژی آن ذره نیز گفته میشود که آن را با pbest نمایش میدهیم. بهترین مقدار دیگری که توسط الگوریتم مورد استفاده قرار میگیرد، بهترین موقعیتی است که تا کنون توسط جمعیت ذرات بدست آمده‌است که آن را gbest میگوییم (هوش جمعی).

پس از یافتن بهترین مقادیر، سرعت و مکان هر ذره با استفاده از رابطه ۶ و ۷به روز می‌شود.

رابطه 6{\displaystyle v(t+1)=v(t)+c1*rand(t)*(pbest(t)-position(t)+c2*rand(t)*(gbest(t)-position(t))}

رابطه 7 {\displaystyle position(t+1)=position(t)+v(t+1)}

سمت راست معادله ۶ از سه قسمت تشکیل شده‌است که قسمت اول، سرعت فعلی ذره است ({\displaystyle v(t)}) و قسمتهای دوم ({\displaystyle c1*rand(t)*(pbest(t)-position(t))}) و سوم ({\displaystyle c2*rand(t)*(gbest(t)-position(t))}) میزان تغییر سرعت ذره و جهت آن به سمت بهترین تجربه شخصی (نوستالژی) و بهترین تجربه گروه (هوش جمعی) را به عهده دارند. اگر قسمت اول را در این معادله درنظر نگیریم ({\displaystyle v(t)})، آنگاه سرعت ذرات تنها با توجه به موقعیت فعلی و بهترین تجربه ذره و بهترین تجربه جمع تعیین می‌شود و عملاً تأثیر سرعت کنونی و لختی آن حذف می‌شود. به این ترتیب، بهترین ذره گروه، در جای خود ثابت می‌ماند و سایرین به سمت آن ذره حرکت می‌کنند. در واقع حرکت دسته جمعی ذرات بدون قسمت اول معادله ۶، پروسه ای خواهد بود که طی آن فضای جستجو به تدریج کوچک می‌شود و جستجویی محلی حول بهترین ذره شکل می‌گیرد. در مقابل اگر فقط قسمت اول معادله ۶ را در نظر بگیریم، ذرات راه عادی خود را می‌روند تا به دیواره محدوده برسند و به نوعی جستجویی سراسری را انجام میدهند. پارامترهای c1 و c2 (مقدار آن حدود ۲ است) میزان اهمیت و وزن هوش جمعی و نوستالژی را مشخص می‌کنند. پارامتر {\displaystyle rand(t)}

با صفر قرار دادن یا ندادن پارامترهای c1 و c2 سه حالت مختلف زیر به وجود می‌آید:

  1. هیچ‌کدام از این دو پارامتر را صفر نگذاریم که میشود pso با لختی سرعت، هوش جمعی و نوستالژی.
  2. وزن نوستالژی را صفر کنیم و فقط براساس هوش جمعی و لختی عمل کنیم.
  3. وزن هوش جمعی را صفر کنیم و فقط براساس نوستالژی و لختی عمل کنیم.

برای مقداردهی اولیه توسط رابطه ۸ عمل میکنیم. سرعت اولیه نیز طبق ۹ صفر است.

رابطه 8 {\displaystyle x(0)=x_{min}+rand(x_{max}-x_{min})}

رابطه 9 {\displaystyle v(0)=0}

شبه کد الگوریتم PSO:

For each particle
    Initialize particle
End For
Do
For each particle
    Calculate fitness value of the particle fp
    /*updating particle’s best fitness value so far)*/
    If fp is better than pBest
    set current value as the new pBest
End For
/*updating population’s best fitness value so far)*/
Set gBest to the best fitness value of all particles
For each particle
    Calculate particle velocity according equation
    Update particle position according equation
End For While maximum iterations OR minimum error criteria is not attained

اما در مورد شرط توقف باید گفت که راههای زیر موجود است:

  • ‌ تعداد تکرار معین
  • رسیدن به یک شایستگی آستانه
  • یک تعداد تکرار که شایستگی تغییر نکند (مثلاً اگر بعد از ۱۰ تکرار شایستگی ثابت بود و بهتر نشد).
  • راه آخر بر اساس چگالی تجمیع اطراف نقطه بهینه است. به این صورت که میگوییم اگر ۸۰ درصد ذرات در فاصلهای کمتر از ۲۰ درصد بیشترین فاصله نسبت به بهترین جواب قرار داشتند، الگوریتم باید متوقف شود.

روش آخر به این صورت است که طبق رابطه ۱۰ می‌توان را بدست آورد. همان‌طور که گفته شد{\displaystyle R_{norm}} یک مقدار بین ۰ و ۱ است و همین‌طور F بیشترین فاصله بین دو ذره در حالت کنونی است.

رابطه 10 {\displaystyle R_{norm}=\left({\frac {R_{max}}{F}}\right)}

مشکل اساسی اولین الگوریتم ازدحام ذرات

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

برای این کار چند راه حل ارائه شده‌است.

استفاده از سرعت بیشینه برشی

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

{\displaystyle v\prime (t+1)={\begin{cases}v(t+1)&v(t+1)<v_{max}\\v_{max}&v(t+1)>=v_{max}\end{cases}}}

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

در این روش از یک تابع تانژانت هیپربولیک برای محدود کردن سرعت به یک مقدار خاص استفاده می‌شود. تفاوت این حالت با حالت برشی اینست که در این حالت تابع ما مشتق پذیر در همهٔ نقاط است درحالیکه در حالت برشی مشتق پذیری از بین می رود.

{\displaystyle v\prime _{ij}(t+1)=tanh({\frac {v_{ij}(t+1)}{v_{maxj}}})v_{maxj}(t)}

در حالت برشی در نقطه برش مشتق پذیری از بین میرود؛ ولی در تانژانت هیپربولیک این مشکل وجود ندارد.

استفاده از ضریب کاهنده سرعت

در این روش از یک که باید بین ۰ و ۱ باشد، استفاده می‌شود. در غیر این صورت اگر بیش از ۱ باشد، سرعت افزاینده خواهد بود.

۱۴

حال تعیین کردن خود این یک مسئله است که روش‌های متعددی برای آن وجود دارد.

روش اول:

۱۵

روش دوم:

۱۶

در این روش باید را طوری تعیین کنیم که کمتر از ۱ شود.

روش سوم:

۱۷

برای می‌توان ۰٫۹ را انتخاب کرد و برای مقدار ۰٫۲ مناسب است.

روش چهارم:

در این روش براساس مقدار شایستگی تغییر می‌کند که این روش مناسب تری از روش‌های بالا است.

۱۸
۱۹

که شایستگی راه‌حل بهینه سراسری است.

روش پنجم:

در این روش که جدیدترین روش است یک پارامتر X خی که در کل سرعت ضرب می‌شود.

۲۰
۲۱ + ,
۲۲ ; k ∈[۰٬۱]
استفاده از تابعی برحسب تکرار برای سرعت بیشینه

در این روش سعی می‌شود تا از یک سرعت بیشینه به صورت تابعی از تکرار استفاده شود. در این روش سرعت بیشینه طبق رابطه ۱۳ برای هر بعد جداگانه حساب می‌شود.

[null 13]

این قابلیت باعث میشود که سرعت بیشینه با توجه به نیاز ما برای تنظیم وزن کاوش و انتفاع در هر تکرار مربوطه تعیین شود.

الگوریتم ازدحام ذرات آسنکرون

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

الگوریتم ازدحام ذرات تمام آگاه

ذرات با اطلاع هم و با هماهنگی حرکت می‌کنند و یک ذره حرکت بقیه را نیز در حرکت خودش دخالت می‌دهد.

۲۳

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

الگوریتم ازدحام ذرات استخوان خالی

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

نسخه اول:

۲۴
۲۵
۲۶

نسخه دوم:

در این حالت در نیمی از حالات از روش استخوان خالی و در نیمی از الگوریتم ازدحام ذرات معمول استفاده میشود.

۲۷
الگوریتم ازدحام ذرات پیش کشیدن و پس زدن

در این الگوریتم ذره به سمت بهینه سراسری جذب می‌شود وقتی که به مقدار آستانه اول برسد، دفع می‌شود تا به جستجوی بیشتری بپردازد و این دفع شدن تا جایی ادامه پیدا میکند که به آستانه دوم برسد. عملیات جذب شدن با فرمول ۳۲ ولی دفع شدن با فرمول ۳۳ صورت می‌گیرد.

[null 32]
[null 33]
الگوریتم ازدحام ذرات شکار و شکارچی

هنگامی که ذرات همگرا شوند یک پارامتر ترس اعمال می‌کنیم با احتمال که باعث دور شدن ذرات از بهینه سراسری می‌شود و درنتیجه جستجو بیشتر می‌شود.

۳۴
۳۵
الگوریتم ازدحام ذرات چندشروعه

در این روش عملاً یک ذره را جهش می‌دهیم. برای وقتی است که مقدار شایستگی در چند تکرار بهبودی ندارد.

جمع‌بندی

الگوریتم ازدحام ذرات دودویی

این یکی از الگوریتم‌هایی است که برای مسائل جایگذاری یا عدم جایگذاری استفاده می‌شود. برای این کار باید از یک نگاشت برای تبدیل از پیوسته به دودویی استفاده شود.

منبع

بهینه‌سازی ازدحام ذرات (PSO) قسمت 1
بهینه‌سازی ازدحام ذرات (PSO) قسمت 2

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

سامانه‌های توصیه‌گر به طور کلی به سه دسته تقسیم می‌شوند؛ در رایج‌ترین تقسیم‌بندی، آنها را به سه گروه ۱. محتوا محور ۲. دانش محور و ۳. صافی سازی تجمعی، تقسیم می‌کنند، که البته گونه چهارمی تحت عنوان 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) قسمت 1
سیستم توصیه گر (Recommender System) قسمت 2
سیستم توصیه گر (Recommender System) قسمت 3