نوشته‌ها

کار با Thread ها در زبان سی شارپ – آشنایی با Thread های Foreground و Background در دات نت

زمانی که یک Thread جدید در برنامه های دات نت ایجاد می شوند، این Thread ها می توانند به دو صورت Foreground و Background اجرا شوند:

  1. Thread های Foreground: زمانی که کی Thread در حالت Foreground اجرا می شود باعث می شود که Thread اصلی برنامه تا زمان کامل شدن اجرای Thread ایجاد شده در حالت اجرا بماند. یعنی از Shut-down شدن Primary Thread توسط CLR جلوگیری می شود.
  2. Thread های Background: این Thread ها که با نام Daemon Thread شناخته می شوند به CLR می گوید که اجرای این Thread آنقدر اهمیت ندارد که Thread اصلی برنامه بخواهد منتظر بماند تا عملیات آن به اتمام برسد و می تواند در هر زمان که Thread اصلی برنامه به اتمام رسید، به صورت خودکار Thread های Background را نیز از بین ببرد.

توجه کنید که کلیه Thread هایی که در برنامه ها ایجاد می کنیم به صورت پیش فرض در حالت Foreground قرار دارند. برای آشنایی بیشتر با این موضوع نمونه کد زیر را در نظر بگیرید:

static void Main(string[] args)
{
    var thread = new Thread(PrintNumbers);
    thread.Start();
}

public static void PrintNumbers()
{
    for (int counter = 1; counter < 10; counter++)
    {
        Console.WriteLine(counter);
        Thread.Sleep(200);
    }
}

همانطور که گفتیم Thread ایجاد شده به صورت پیش فرض از نوع Foreground است و به همین دلیل تا زمانی که روند اجرای Thread ایجاد شده به اتمام نرسد از برنامه خارج نمی شویم و کلیه اعداد در خروجی چاپ می شوند. اما در کد زیر Thread ایجاد شده به صورت Background است و خواهیم دید که پس از اجرای برنامه به دلیل اینکه Thread اصلی زودتر از Thread ایجاد شده به اتمام می رسد، CLR به صورت خودکار Thread ایجاد شده را از بین می برد و اعداد به صورت کامل در خروجی نمایش داده نمی شوند:

static void Main(string[] args)
{
    var thread = new Thread(PrintNumbers);
    thread.IsBackground = true;
    thread.Start();
}

public static void PrintNumbers()
{
    for (int counter = 1; counter < 10; counter++)
    {
        Console.WriteLine(counter);
        Thread.Sleep(200);
    }
}

در برنامه های واقعی باید با دقت نوع Thread ها را انتخاب کرد، برای مثال فرض کنید که در برنامه شما در یک Thread جداگانه عملیاتی بر روی داده های بانک اطلاعاتی انجام می شود و نتیجه این عملیات در انتها باید در جایی ذخیره شود، می توانید برای اینکار یک Thread از نوع Foreground ایجاد کرده تا پس از خروج از برنامه، Thread اصلی منتظر اتمام انجام عملیات شده و سپس عملیات خروج کامل انجام شود. در مبحث بعدی در مورد موضوع همزمانی یا Concurrency صحبت می کنیم که از مشکلات اساسی در زمینه برنامه نویسی asynchronous می باشد و در مورد راهکار های حل این مشکل نیز صحبت خواهیم کرد.

منبع


قسمت اول آموزش-برنامه نویسی Asynchronous – آشنایی با Process ها، Thread ها و AppDomain ها

قسمت دوم آموزش- آشنایی با ماهیت Asynchronous در Delegate ها

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

قسمت چهارم آموزش- آشنایی با Thread های Foreground و Background در دات نت

قسمت پنجم آموزش- آشنایی با مشکل Concurrency در برنامه های Multi-Threaded و راهکار های رفع این مشکل

قسمت ششم آموزش- آشنایی با کلاس Timer در زبان سی شارپ

قسمت هفتم آموزش-آشنایی با CLR ThreadPool در دات نت

قسمت هشتم آموزش- مقدمه ای بر Task Parallel Library و کلاس Parallel در دات نت

قسمت نهم آموزش- برنامه نویسی Parallel:آشنایی با کلاس Task در سی شارپ

قسمت دهم آموزش-برنامه نویسی Parallel در سی شارپ :: متوقف کردن Task ها در سی شارپ – کلاس CancellationToken

قسمت یازدهم آموزش- برنامه نویسی Parallel در سی شارپ :: کوئری های Parallel در LINQ

قسمت دوازدهم آموزش- آشنایی با کلمات کلیدی async و await در زبان سی شارپ

قسمت سیزدهم آموزش- استفاده از متد WhenAll برای اجرای چندین Task به صورت همزمان در سی شارپ

مقدمه

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

شرح خبر

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

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

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

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

راه اندازی کانال شرکت بهسان اندیش در تلگرام و سایر پیام رسان ها

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

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

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

? پیام رسان تلگرام (مرکز اصلی اطلاع رسانی شرکت) :

https://t.me/Behsan_Transit

 

? در سروش (با اینترنت نیم بها) :
sapp.ir/behsanandish

 

? در ایتا (با محیطی کاملا مشابه تلگرام) :
eitaa.com/behsanandish

 

مرحله ۲: پیدا کردن قدرت و جهت گرادیان لبه.

گام بعدی استفاده از Mask های Sobel برای پیدا کردن قدرت و جهت گرادیان لبه برای هر پیکسل است. ابتدا ماسک های Sobel به محدوده پیکسل ۳×۳ پیکسل فعلی در هر دو جهت x و y اعمال می شود. سپس مجموع مقدار هر ماسک ضربدر پیکسل مربوطه به ترتیب به عنوان مقادیر Gx و Gy محاسبه می شود. ریشه دوم مربع Gx به اضافه Gy مربع برابر قدرت لبه است. Tangent معکوس Gx / Gy جهت لبه را تولید می کند. سپس جهت لبه تقریب شده است به یکی از چهار مقادیر ممکن که ایجاد می کند جهت های ممکن را که  یک لبه می تواند در یک تصویر از یک شبکه پیکسل مربع باشد. این جهت لبه در edgeDir [row] [col] ذخیره می شود و قدرت گرادیان در  array gradient[row] [col] ذخیره می شود.

 

CannyEdgeWeel

هر زاویه لبه در ۱۱٫۲۵ درجه از یکی از  زاویه های ممکن به آن مقدار تغییر می کند.

 

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

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


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

using namespace std;

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

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

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

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

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

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

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

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

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

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

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

}

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

	// Kill program
	DestroyWindow();	

	

}

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

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

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

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

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

			}
			break;

		default:
			break;
	}	
}

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

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

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

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

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

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

	ReleaseDC(pDC);

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

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

	// End of adds
}

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

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

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

	BYTE			redValue;
	BYTE			greenValue;
	BYTE			blueValue;

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

عامل های هوشمند در هوش مصنوعی

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

عامل ها و محیط ها

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

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

تأکید میکنیم که فرضیه ی عامل، ابزاری برای تحلیل سیسیتم هاست، نه یک ویژگی خاص برای تقسیم دنیا به عامل و غیر عامل ها. ماشین حساب را میتوان عاملی در نظر گرفت که با توجه به “دنباله ادراک” ، “=۲+۲” نماد “۴” را نمایش میدهد. اما این تحلیل منجر به درک ماشین حساب نمیشود. از جهاتی تمام حوزه های مهندسی را می توان طراحی کننده ی محصولات مصنوعی دانست که با دنیا تعامل دارند؛ AI (از نظر مؤلفین) در انتهای این طیف واقع است، که در آنجا، محصولات مصنوعی، منابع محاسباتی ارزشمندی دارند و محیط کار ، نیازمند تصمیم گیری مهم است.

رفتار خوب : مفهوم خردمندی یا عقلانیت

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

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

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

• خردمندی یا عقلانیت (rationality)

خردمند بودن در هر زمان به چهار چیز بستگی دارد:

۱٫ معیار کارایی که ملاک های موفقیت را تعریف می کند.

۲٫ دانش قبلی عامل نسبت به محیط.

۳٫ فعالیتهایی که عامل میتواند انجام دهد.

۴٫ دنباله ی ادراک عامل در این زمان.

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

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

• همه چیزدانی، یادگیری و خود مختاری

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

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

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

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

منبع


منابع

۱٫https://fa.wikipedia.org

۲٫http://retro-code.ir

۳٫http://www.sourcecodes.ir

 

عامل های هوشمند قسمت ۱
عامل های هوشمند قسمت ۲
عامل های هوشمند قسمت ۳
عامل های هوشمند قسمت ۴
عامل های هوشمند قسمت ۵

الگوریتم Canny

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

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

 

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

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

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

 

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

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

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

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

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

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

 

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

 

 

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

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

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

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

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

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

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

float min=1000;

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

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

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

}

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

}

}
//Normalizing kernel Weight
Weight= sum;

return;
}

 

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

 

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

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

            float Sum=0;

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

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

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

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

            }

            return Output;
        }

 

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

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

 

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

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

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


                       }
                   }
                    Output[i,j]=sum;

                }

            }
            return Output;

        }

 

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

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

 

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

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

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

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



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

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

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

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

                }

            }

 

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

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

 

private void HysterisisThresholding(int[,] Edges)
        {

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


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

                    }

                }

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




            return;
        }

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

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

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

           //۳

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

           //۴

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

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


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

        } 
          
        //Canny Class Ends

    }

 

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

 

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

Canny Edge Detection C#

رمز فایل : behsanandish.com

منبع


منابع:

fa.wikipedia.org

http://mediasoft.ir

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

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

سیستم توصیه گر (به انگلیسی: Recommender System) یا سامانه پیشنهادگر (واژه سیستم یا سامانه گاهی با پلتفرم یا موتور جایگزین می‌شود)، با تحلیل رفتار کاربر خود، اقدام به پیشنهاد مناسب‌ترین اقلام (داده، اطلاعات، کالا و…)می‌نماید. این سیستم رویکردی است که برای مواجهه با مشکلات ناشی از حجم فراوان و رو به رشد اطلاعات ارائه شده‌است و به کاربر خود کمک می‌کند تا در میان حجم عظیم اطلاعات سریع‌تر به هدف خود نزدیک شوند. برخی سامانه پیشنهادگر را معادل پالایش گروهی (به انگلیسی: Collaborative filtering) می‌دانند.

مقدمه

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

تعاریف متفاوتی برای سیستم‌های توصیه‌گر ارائه شده‌است. از آن جمله، تعریف کلی‌نگر و خلاصه آقای Ting-peng liang در سال ۲۰۰۷ است که RS را زیرمجموعه‌ای از DSSها می‌داند و آن‌ها راسیستم‌های اطلاعاتی تعریف می‌کند که، توانایی تحلیل رفتارهای گذشته و ارائه توصیه‌هایی برای مسائل جاری را دارا هستند. به زبان ساده‌تر در سیستم‌های توصیه‌گر تلاش بر این است تا با حدس زدن شیوه تفکر کاربر (به کمک اطلاعاتی که از نحوه رفتار وی یا کاربران مشابه وی و نظرات آن‌ها داریم) به وی مناسب‌ترین و نزدیک‌ترین کالا به سلیقه او را شناسایی و پیشنهاد کنیم. این سیستم‌ها در حقیقت همان فرایندی که ما در زندگی روزمره خود به کار می‌بریم و طی آن تلاش می‌کنیم تا افرادی با سلایق نزدیک به خود را پیدا کرده و از آنها در مورد انتخاب‌هایمان نظر بخواهیم. توصیه‌هایی که از سوی سیستم‌های توصیه‌گر ارائه می‌شوند به طور کلی می‌توانند دو نتیجه دربرداشته باشند:

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

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

مزایا و پیشرفت‌ها

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

تاریخچه

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

ظرفیت رایانه‌ها در فراهم آوردن توصیه‌ها تقریباً از همان اوایل تاریخ‌چه رایانه‌ها شناخته شد. گراندی، یک کتابدار کامپیوتری گامی اولیه به سمت سامانه‌های توصیه‌گر خودکار بود. این کتابدار یک توصیه‌گر نسبتاً ساده و اولیه بود که کاربران را به قالب‌هایی بر اساس مصاحبه کوتاه با استفاده از اطلاعات مستقیم‌کدشده(hard-coded) دربارهٔ سلایق کتاب قالب‌های مختلف گروه‌بندی می‌کرد تا توصیه‌ها را تولید کند، ولی این کار ورود اولیه مهم به فضای سامانه‌های توصیه‌گر قلمداد می‌شود.

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

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

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

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

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

جعبه ابزار تکنیک‌های توصیه گر به چیزی بیش از پالایش مشارکتی گسترش یافته‌اند و شامل رویکردهای محتوامحور(Content-Based) بر اساس متدهای بازیابی اطلاعات، استنتاج بیزی (Bayesian Inference) و استدلال مورد محور (Case-Based Reasonong) می‌باشد. این متدها بجای یا درعوض الگوهای نمره دهی کاربران، محتوا یا ویژگی‌های اصلی آیتم‌هایی که قرار است توصیه شود را درنظر می‌گیرند. با به بلوغ رسیدن استراتژی‌های توصیه مختلف، سامانه‌های توصیه‌گر ترکیبی (Hybrid Recommender Systems) نیز ظهور یافته‌اند و الگوریتم‌های مختلفی را در سیستم‌های مرکبی ترکیب کرده‌اند که بر اساس قدرت الگوریتم‌های تشکیل‌دهنده‌شان ایجاد شده‌اند. البته در کنار رویکردهای محتوا محور، پالایش مشارکتی، هم روش تکی و هم ترکیب‌شده‌اش به عنوان روشی مؤثر همچنان مطرح هستند.

زمانی که Netflix جایزه Netflix Prize را در سال ۲۰۰۶ به منظور بهبود بخشیدن وضعیت توصیه‌های فیلمش برقرار کرد، تحقیق بر روی الگوریتم‌های سامانه‌های توصیه‌گر توجه بسیاری را به خودش جلب کرد. هدف این رقابت ساختن یک الگوریتم توصیه‌گری بود که بتواند الگوریتم CineMatch که متعلق به خود Netflix بود را با ۱۰٪ بهبود در آزمایشات آفلاین شکست دهد. این امر موجب ایجاد خروشی از اقدامات شد، هم در بین محیط آکادمیک و هم در بین سایر علاقمندان. جایزه یک میلیون دلاری ارزشی را که فروشندگان برای دقت توصیه‌ها قائل هستند نشان می‌دهد[۱].

کاربردها

سیستم‌های توصیه‌گر کاربردهای فراوانی دارند که برخی از زمینه‌های کاربردی آن به شرح زیر است:

  • تجارت الکترونیک: برای توصیه محصولات و خدمات مختلف.
  • اینترانت‌های بنگاهی: برای پیدا کردن افراد خبره در یک زمینه خاص یا افرادی که در رویارویی با شرایط مشابه، تجاربی کسب کرده و راه حل‌هایی یافته‌اند (بیشتر داخل یک سازمان کاربرد دارد).
  • کتابخانه دیجیتال: پیدا کردن کتاب، مقاله و …
  • کاربردهای پزشکی: انتخاب پزشک متناسب با شرایط (مکان، نوع بیماری، زمان و …) بیمار، انتخاب دارو و …
  • مدیریت ارتباط با مشتری CRM: برای ارائه راهکارهایی برای حل مشکلات تولیدکننده و مصرف‌کننده در زنجیره تأمین.

مقایسه سامانه‌های توصیه گر و سامانه‌های تصمیم‌یار کلاسیک

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

تعاریف و اصطلاحات عمده

لازم است برای درک مفهوم سیستم توصیه‌گر، مفاهیم چهارگانه و ابتدایی زیر را بررسی کنیم.

  • در سیستم‌های توصیه گر به کاربری که توصیه جاری در سیستم، برای وی در حال پردازش و آماده شدن است، کاربر فعال یا کاربر هدف می‌گویند.
  • الگوریتم‌های به کار رفته در این سیستم‌ها، از ماتریسی به نام ماتریس رتبه‌ها استفاده می‌کنند؛ اصطلاحات رایج برای این ماتریس Rating Database و Preference Database نیز هستند.
  • از فعل مصرف کردن در سیستم‌های توصیه‌گر، زمانی استفاده می‌کنند که کاربر توصیه ارائه شده را می‌پذیرد. به عبارتی وقتی کاربری پیشنهادی را که توسط سیستم به وی شده می‌پذیرد، می‌گوییم کاربر آن پیشنهاد را مصرف کرده، این پذیرش می‌تواند به شکل‌های مختلفی باشد، مثلاً کاربر، کتاب پیشنهادی را می‌خرد، سایت پیشنهادی را مرور می‌کند یا به شرکت خدماتی ای که به او پیشنهاد شده مراجعه می‌کند. ساختار ماتریس رتبه‌ها بدین گونه‌است که در آن، هر سطر ماتریس نمایانگر یک کاربر و هر ستون آن معرف کالایی (شئای) خاص است.

حال با مفهوم تابع سودمندی آشنا خواهیم شد که قصد داریم به کمک آن یک مدل کلی ریاضی از سیستم‌های توصیه‌گر را نیز ارائه دهیم. در واقع یک سیستم توصیه‌گر را می‌توان با این نگاشت همسان دانست و مدل کرد: {\displaystyle u:C*S->R}{\displaystyle u:C*S->R}

فرض کنید C مجموعه تمامی کاربران و S مجموعه اقلام در دسترس باشند. تابعی را که میزان مفید و متناسب بودن کالای S برای کاربر C را محاسبه می‌کند با u نشان می‌دهیم، که در آن R مجموعه‌ای است کاملاً مرتب (براساس میزان اهمیت). هرکدام از عناصر S را می‌توان با مجموعه‌ای از خصوصیات، مشخص کرد. برای مثال، محصولی مثل فیلم را می‌توان با مشخصه‌هایی چون عنوان فیلم، کارگردان، طول زمانی فیلم، تاریخ تولید و … ثبت کرد. همچنین عناصر مجموعه C را نیز می‌توان بر اساس ویژگی‌های مثل سن، جنسیت و … ثبت کرد. (باید توجه داشت که u روی تمام فضای مجموعه آغازین S×C تعریف شده نیست؛ از این رو باید برون‌یابی شود)

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