زمانی که یک Thread جدید در برنامه های دات نت ایجاد می شوند، این Thread ها می توانند به دو صورت Foreground و Background اجرا شوند:
Thread های Foreground: زمانی که کی Thread در حالت Foreground اجرا می شود باعث می شود که Thread اصلی برنامه تا زمان کامل شدن اجرای Thread ایجاد شده در حالت اجرا بماند. یعنی از Shut-down شدن Primary Thread توسط CLR جلوگیری می شود.
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 می باشد و در مورد راهکار های حل این مشکل نیز صحبت خواهیم کرد.
https://behsanandish.com/wp-content/uploads/logo-farsi-englisi-300x195-1.png00محمد مهدی ابراهیمیhttps://behsanandish.com/wp-content/uploads/logo-farsi-englisi-300x195-1.pngمحمد مهدی ابراهیمی2020-03-31 10:00:192019-11-30 10:14:31اولین مطالب بزودی منتشر خواهد شد
https://behsanandish.com/wp-content/uploads/logo-farsi-englisi-300x195-1.png00محمد مهدی ابراهیمیhttps://behsanandish.com/wp-content/uploads/logo-farsi-englisi-300x195-1.pngمحمد مهدی ابراهیمی2020-03-30 10:00:492019-11-30 10:14:20اولین مطالب بزودی منتشر خواهد شد
حذف نویز تصاویر _ گروهی از محققان سیستمی را توسعه داده اند که با استفاده از هوش مصنوعی و بدون نیاز به عکس های واضح از منبع، نویز تصاویر را از بین می برد.
شرح خبر
این گروه متشکل از محققان انویدیا، MIT و دانشگاه آلتو در توسعه این سیستم از یادگیری عمیق بهره برده اند که بر خلاف روش های قبلی نیازی به مشاهده نمونه های کامل از تصویر مورد نظر داشته و تنها با استفاده از داده های ناقص یا دو تصویر غیر واضح به افزایش کیفیت تصاویر می پردازد. علاوه بر این نتیجه نهایی افزایش کیفیت، حذف متون یا اصلاح تصویر نسبت به روش های قبلی به مراتب بهتر است.
یادگیری عمیق گونه ای از یادگیری ماشینی است که در آن سیستم با کمک هوش مصنوعی نحوه بازیابی تصاویر دارای نویز از طریق کنار هم قرار دادن تصاویر، متون یا ویدیوها را فرا می گیرد. یکی دیگر از قابلیت های جالب توجه سیستم جدید افزایش کیفیت تصاویر در عرض چند میلی ثانیه است. مبنای کار هوش مصنوعی در این سیستم بر شبکه عصبی استوار است که با استفاده از تصاویر دارای نویز آموزش دیده است. در این روش هوش مصنوعی علی رغم عدم نیاز به تصاویر واضح از منبع باید دوبار تصویر را مشاهده کند.
آزمایشات این گروه نشان داده که از تصاویر تخریب شده از طریق نویزهایی نظیر «گاوسی افزایشی»، «پواسون» یا ترکیب آنها می توان برای تولید تصاویر بهینه ای استفاده کرد که کیفیت آنها با تصاویر بازیابی شده از عکس های بدون مشکل تقریبا برابر است. کاربردهای علمی این سیستم مبتنی بر یادگیری عمیق شامل زمینه های پزشکی است که در آن می توان کیفیت اسکن های MRI و تصاویر دیگر را به شکل چشمگیری افزایش داد.
چند ماه قبل نیز تیم تحقیقاتی انستیتوی «ماکس پلانک» به رهبری دکتر مهدی سجادی، الگوریتمی را توسعه داده بودند که با بهره گیری از هوش مصنوعی وضوح تصاویر بی کیفیت را تا حد زیادی بهبود می بخشید.
راه اندازی کانال شرکت بهسان اندیش در تلگرام و سایر پیام رسان ها
کانال شرکت بهسان اندیش در تلگرام و سایر پیام رسان ها– در دنیای مجازی که شبکه های اجتماعی همچون کشوری مستقل عمل می کنند دسته ای از کاربران متناسب با فعالیت خود نیاز به امکاناتی خاص تر دارند. سازمان های دولتی و خصوصی، برند ها، شخصیت های سیاسی، هنری، فرهنگی و… برای ایجاد ارتباط و پیشبرد فعالیت خود نیاز به اعتماد مخاطب دارند.
در شبکه های اجتماعی ، گاه هویت واقعی یک کاربر مشخص نیست و ممکن است دیگران به اسم سازمان ها و اشخاص صفحاتی را ایجاد کنند و نام آن برند یا شخص را خدشه دار نمایند و با توجه به آنکه ایجاد اعتماد در مخاطب، امری مشکل است ، تلگرام با رسمیت بخشیدن به صفحات و تایید آنها این رویکرد را متفاوت کرده است.
شرکت بهسان اندیش به منظور ارائه خدمات و فعالیت های خود در شبکه های اجتماعی اقدام به راه اندازی کانال شرکت بهسان اندیش در تلگرام و پیام رسان های داخلی نموده که علاقمندان می توانند از طریق لینک زیر در این پیام رسان ها ما را دنبال کنید:
گام بعدی استفاده از Mask های Sobel برای پیدا کردن قدرت و جهت گرادیان لبه برای هر پیکسل است. ابتدا ماسک های Sobel به محدوده پیکسل ۳×۳ پیکسل فعلی در هر دو جهت x و y اعمال می شود. سپس مجموع مقدار هر ماسک ضربدر پیکسل مربوطه به ترتیب به عنوان مقادیر Gx و Gy محاسبه می شود. ریشه دوم مربع Gx به اضافه Gy مربع برابر قدرت لبه است. Tangent معکوس Gx / Gy جهت لبه را تولید می کند. سپس جهت لبه تقریب شده است به یکی از چهار مقادیر ممکن که ایجاد می کند جهت های ممکن را که یک لبه می تواند در یک تصویر از یک شبکه پیکسل مربع باشد. این جهت لبه در edgeDir [row] [col] ذخیره می شود و قدرت گرادیان در array gradient[row] [col] ذخیره می شود.
هر زاویه لبه در ۱۱٫۲۵ درجه از یکی از زاویه های ممکن به آن مقدار تغییر می کند.
#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;
}
}
https://behsanandish.com/wp-content/uploads/logo-farsi-englisi-300x195-1.png00توسعه دهندهhttps://behsanandish.com/wp-content/uploads/logo-farsi-englisi-300x195-1.pngتوسعه دهنده2020-03-18 10:00:332019-11-30 10:07:11الگوریتم Canny در سی پلاس پلاس قسمت 2
قبلا عامل های خردمند (عقلایی) را به عنوان مرکز ثقل رهیافت هوش مصنوعی مشخص کردیم. در این بخش ، این فرضیه را دقیق تر بررسی میکنیم. خواهیم دید که مفهوم عقلانیت (خردگرایی) میتواند به دامنه وسیعی از عاملهایی که در هر محیطی عمل میکنند، اعمال شود. با توجه به اینکه بعضی از عامل ها بهتر از عامل های دیگر رفتار میکنند، به ایده ی “عامل خردمند” یا “عامل عقلایی” میرسیم. “عامل خردمند” عاملی است که حتی الامکان خوب عمل میکند. عملکرد عامل به ماهیت محیط بستگی دارد. بعضی از محیط ها دشوارتر از محیط های دیگر هستند.
عامل ها و محیط ها
عامل هر چیزی است که قادر است محیط خود را از طریق حسگرها (سنسور ها) درک کند و از طریق محرک ها عمل کند. عامل انسانی دارای چشم، گوش و اعضای دیگری برای حس کردن، و دست، پا و دهان و اعضای دیگر به عنوان محرک هایی برای عمل کردن است. عامل روباتیک ممکن است شامل دوربین ها و مادون قرمر و فاصله سنج برای سنسورها و موتورهای متعددی به عنوان محرک ها باشد. عامل نرم افزاری “بسته های شبکه” ، محتویات فایل و ورودی های صفحه کلید را به عنوان ورودی حسی دریافت میکند و از طریق نوشتن در صفحه نمایش، نوشتن در فایل ها و ارسال بسته های شبکه، عمل میکند. از واژه ادراک (percept) برای ورودی های ادراکی عامل در هر لحظه استفاده میکنیم. دنباله ی ادراک یا توالی ادراک عامل، سابقه ی کامل هر چیزی است که عامل تاکنون درک کرده است. به طورکلی، انتخاب فعالیتی (کنشی) توسط عامل در هر لحظه، میتواندبه کل دنباله ی ادراک تا آن زمان بستگی داشته باشد. اگر بتوانیم فعالیت انتخابی عامل را برای هر دنباله ی ادراکی مشخص کنیم، همه چیز را در مورد عامل مطرح کرده ایم. از نظر ریاضی، میگوییم رفتار عامل توسط تابع عامل توصیف میشود که هر دنباله ی ادراک را به یک فعالیت نقش میکند.
تابع عامل را میتوانیم به صورت جدول نشان دهیم تا هر عاملی را تشریح کند. برای اغلب عامل ها، این جدول بسیار بزرگ خواهد بود، ولی میتوانیم حدی را برای طول دنباله ی ادراک در نظر بگیریم. برای ساخت جدول عامل، تمام دنباله های ادراک را در نظر گرفته فعالیت هایی را که عامل در پاسخ به آنها انجام میدهد، ذخیره میکنیم، البته این جدول یک شی ء خارجی است که عامل را مشخص میکند. تابع عامل مربوط به عامل مصنوعی، به طور داخلی توسط برنامه ی عامل پیاده سازی می شود. این دو ایده را از هم تفکیک میکنیم. تابع عامل یک توصیف ریاضی انتزاعی است، و برنامه عامل، پیاده سازی دقیقی است که در معماری عامل اجرا می شود.
تأکید میکنیم که فرضیه ی عامل، ابزاری برای تحلیل سیسیتم هاست، نه یک ویژگی خاص برای تقسیم دنیا به عامل و غیر عامل ها. ماشین حساب را میتوان عاملی در نظر گرفت که با توجه به “دنباله ادراک” ، “=۲+۲” نماد “۴” را نمایش میدهد. اما این تحلیل منجر به درک ماشین حساب نمیشود. از جهاتی تمام حوزه های مهندسی را می توان طراحی کننده ی محصولات مصنوعی دانست که با دنیا تعامل دارند؛ AI (از نظر مؤلفین) در انتهای این طیف واقع است، که در آنجا، محصولات مصنوعی، منابع محاسباتی ارزشمندی دارند و محیط کار ، نیازمند تصمیم گیری مهم است.
رفتار خوب : مفهوم خردمندی یا عقلانیت
عامل خردمند، عاملی است که رفتار خوب انجام میدهد- از نظر ادراکی، به معنای این است که هر عنصر جدول مربوط به تابع عامل، به درستی پر میشود. روشن است که انجام “رفتار درست”، بهتر از انجام “رفتار نادرست” است، اما معنای “رفتار درست” چیست؟ پاسخ به این پرسش قدیمی را، به روش قدیمی ارائه می دهیم: به در نظر گرفتن نتایج رفتار عامل. وقتی یک عامل در محیطی قرار گرفت، بر اساس آنچه که از محیط دریافت می کند، دنباله ای از فعالیت ها را انجام میدهد. این دنباله از فعالیت ها، موجب میشود که محیط از حالتی به حالت دیگر برود، یعنی محیط از دنباله ای از حالت ها عبور میکند. اگر این دنباله از حالتها مطلوب باشد، آنگاه این عامل به درستی عمل کرده است. برای تعیین مطلوب بودن دنباله ای از فعالیت ها، از مفهوم معیار کارایی استفاده میکنیم، که هر دنباله از حالت های محیط را ارزیابی میکند.
توجه کنید که حرف از حالتهای محیط زدیم نه حالتهای عامل. اگر موفقیت را بر اساس نظر عامل در مورد رضایت خود از کارایی اش تعریف کنیم، عامل می تواند خودش را فریب دهد و کارایی خود را بالا بداند و از آنجا نتیجه بگیریم که با خردمندی کامل عمل کرده است. مخصوصا عامل های انسانی، در مواردی که قرار است راجع به موضوعی تصمیم گیری کنند که در آن تصمیم گیری ، سودی به آنها نمیرسد، سابقه ی بدی دارند (سعی می کنند همه چیز را به نفع خود تمام کنند).
روشن است برای تمام کارها و عامل ها، یک معیار کارایی ثابت وجود ندارد؛ معمولا طراح، یک معیار را برای شرایط خاصی ، مناسب می بیند. این کار، چندان آسان نیست.
• خردمندی یا عقلانیت (rationality)
خردمند بودن در هر زمان به چهار چیز بستگی دارد:
۱٫ معیار کارایی که ملاک های موفقیت را تعریف می کند.
۲٫ دانش قبلی عامل نسبت به محیط.
۳٫ فعالیتهایی که عامل میتواند انجام دهد.
۴٫ دنباله ی ادراک عامل در این زمان.
به این ترتیب میتوانیم عامل خردمند را بصورت زیر تعریف کنیم:
برای هر دنباله از ادراک ممکن، عامل خردمند (عقلایی) باید فعالیتی را انتخاب کند که انتظار میرود معیار کارایی اش را به حداکثر برساند. این کار با توجه به شواهدی که از طریق این دنباله ادراک به دست می آید و دانش درونی عامل، صورت می گیرد.
• همه چیزدانی، یادگیری و خود مختاری
باید بین “همه چیزدانی” و خردمندی تمایز قائل شویم. عامل “همه چیزدان”، خروجی واقعی فعالیت های خودش را می داند و می تواند بر اساس آن عمل کند. اما “همه چیزدان” بودن عامل، غیرممکن است. این مثال را در نظر بگیرید: من روزی در حال قدم زدن با احمد هستم و دوست قدیمی خود را در آن طرف خیابان می بینم. هیچ ترافیکی وجود ندارد و “خردمندی” حکم میکند که در عرض خیابان حرکت کنم. در همین حال، در فاصله ۳۳۰۰۰ فوتی، یک هواپیمای مسافری با هواپیمای باری برخورد میکند و قبل از اینکه به آن طرف خیابان بروم، نقش بر زمین میشوم. آیا عبور از عرض خیابان، عقلایی (خردمندی) نبود؟ بعید است کسی بگوید که عبور از عرض خیابان حماقت بوده است.
این مثال نشان میدهد که خردمندی با “کمال مطلوب” متفاوت است. خردمندی، کارایی مورد انتظار را به حداکثر میرساند، در حالیکه کمال مطلوب، کارایی واقعی را به حداکثر می رساند. دست برداشتن از خواسته ی “کمال مطلوب”، فقط به معنای منصف بودن نسبت به عامل ها نیست. موضوع این است که، اگر انتظار داشته باشیم عامل، بهترین فعالیت را انجام دهد، نمیتوان عاملی طراحی کرد که با این مشخصات جور درآید، مگر اینکه کارایی جام جهان نما یا ماشین های زمان را بهبود بخشیم.
تعریفی که ما از خردمندی ارائه دادیم، نیاز به “همه چیزدانی” ندارد. زیرا انتخاب عقلایی (در یک زمان) فقط به “دنباله ادراک” در آن زمان بستگی دارد. باید مطمئن باشیم که ندانسته اجازه ندهیم که عامل به فعالیت های غیرهوشمند بپردازد. انجام فعالیت به منظور اصلاح ادراک های آینده، که گاهی جمع آوری اطلاعات نام دارد، بخش مهمی از خردمندی است. مثال دیگری از جمع آوری اطلاعات، از طریق اکتشاف صورت میگیرد. این تعریف، مستلزم آن است که عامل نه تنها باید اطلاعات را جمع آوری کند، بلکه باید از آنچه که درک میکند، یاد بگیرد. پیکربندی اولیه ی عامل می تواند دانشی از محیط را ارائه کند، اما هر چه که عامل تجربه بیشتری کسب میکند، ممکن است پیکربندی آن تغییر کند. موارد زیادی وجود دارد که محیط از قبل شناخته شده است. در این موارد لازم نیست عامل درک کند یا بیاموزد، زیرا به درستی عمل خواهد کرد. البته چنین عاملهایی خیلی آسیب پذیر هستند.
اگر عامل به جای ادراکات خود، براساس دانش قبلی طراح خود رفتار کند، می گوییم عامل خودمختار نیست. عامل خردمند باید خودمختار باشد. باید یاد بگیرد که نقص دانش قبلی را چگونه جبران کند. به عنوان یک موضوع عملی، عامل در ابتدا به خودمختاری کامل نیاز ندارد: وقتی عامل تجربه ای ندارد یا تجربه کمی دارد، باید به طور تصادفی عمل کند، مگر اینکه طراح به آن کمک کند. لذا، همان طور که تکامل حیوانات ، واکنش های ذاتی را برای آنها ایجاد میکند تا بیشتر زنده بمانند و به یادگیری خود بیفزایند، منطقی است که بتوان یک عامل هوش مصنوعی را ایجاد کرد که علاوه بر دانش اولیه، قدرت یادگیری داشته باشد. پس از تجربیات کافی با محیط خود ، رفتار عامل خردمند میتواندمستقل از دانش اولیه اش باشد. لذا، توانایی یادگیری منجر به طراحی عاملی میشود که در محیط های مختلف، موفق است.
لبه یاب کنی توسط جان اف کنی در سال ۱۹۸۶ ایجاد شد و هنوز یک لبه یاب استاندارد و با دقت و کیفیت بالا میباشد.الگوریتم لبه یابی کنی یکی از بهترین لبه یابها تا به امروز است. در ادامه روش کار این الگوریتم و هم چنین کد الگوریتم Canny در #C را بررسی خواهیم کرد. این الگوریتم لبه یابی از سه بخش اصلی زیر تشکیل شده:
تضعیف نویز
پیدا کردن نقاطی که بتوان آنها را به عنوان لبه در نظر گرفت
حذب نقاطی که احتمال لبه بودن آنها کم است
معیارهایی که در لبه یاب کنی مطرح است: ۱ -پایین آوردن نرخ خطا- یعنی تا حد امکان هیچ لبه ای در تصویر نباید گم شود و هم چنین هیچ چیزی که لبه نیست نباید به جای لبه فرض شود. لبه هان پیدا شده تا حد ممکن به لبه ها اصلی نزدیک باشند.
۲ -لبه در مکان واقعی خود باشد- یعنی تا حد ممکن لبه ها کمترین فاصله را با مکان واقعی خود داشته باشند. ۳ -بران هر لبه فقط یک پاسخ داشته باشیم.
۴ -لبه ها کمترین ضخامت را داشته باشند- (در صورت امکان یک پیکسل). لبه یاب کنی بخاطر توانایی در تولید لبه های نازک تا حد یک ییکسل برای لبه های پیوسته معروف شده است. این لبه یاب شامل چهار مرحله و چهار ورودی زیر است: یک تصویر ورودی یک پارامتر به نام سیگما جهت مقدار نرم کنندگی تصویر یک حد آستانه بالا (Th) یک حد آستانه پایین (Tl)
مراحل الگوریتم Canny:
۱- در ابتدا باید تصویر رنگی را به جهت لبه یابی بهتر به یک تصویر سطح خاکسترن تبدیب کرد.
۲- نویز را از تصویر دریافتی حذف کرد. بدلیل اینکه فیلتر گاوسین از یک ماسک ساده برای حذف نویز استفاده می کند لبه یاب کنی در مرحله اول برای حذف نویز آن را بکار میگیرد.
۳- در یک تصویر سطح خاکستر جایی را که بیشترین تغییرات را داشته باشند به عنوان لبه در نظر گرفته می شوند و این مکانها با گرفتن گرادیان تصویر با استفاده عملگر سوبل بدست می آیند. سپس لبه های مات یافت شده به لبه های تیزتر تبدیل می شوند.
۴- برخی از لبه های کشف شده واقعا لبه نیستند و در واقع نویز هستند که باید آنها توسط حد آستانه هیسترزیس فیلتر شوند.هیسترزیس از دو حد آستانه بالاتر (Th) و حد آستانه پایین تر (Tl) استفاده کرده و کنی پیشنهاد می کند که نسبت استانه بالا به پایین سه به یک باشد.
این روش بیشتر به کشف لبه های ضعیف به درستی می پردازد و کمتر فریب نویز را می خورد و از بقیه روش ها بهتر است.
کد الگوریتم 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 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 و تجزیه و تحلیل ۸-اتصال انجام می دهد.
وزنی (Weighted): امتیازاتی که توسط اجزای توصیه گر متفاوت داده می شود، بصورت عددی با یکدیگر ترکیب می شوند.
راه گزینی (Switching): سیستم از بین اجزای توصیه گر انتخاب کرده، و جزء انتخابی را به کار می گیرد.
مخلوط (Mixed): پیشنهادات توصیه گر های متفاوت، با هم ارائه می گردند.
ترکیب خصوصیات (Feature Combination): ویژگی های بدست آمده از منابع شناختی متفاوت، با یکدیگر ترکیب شده و یک الگوریتم پیشنهادی مجرد را ارائه می دهد.
تقویت خصوصیات(Feature Augmentation): یک فن توصیه، برای محاسبه ی یک ویژگی یا مجموعه ای از خصوصیات به کار برده می شود، که بخشی از ورودی تکنیک بعدیست.
آبشار(Cascade): به توصیه گرها اولویت اکید داده می شود؛ انهایی که از اولویت پایین تر برخوردارند برای به ثمر رساندن آنهایی که اولویت بالایی دارند، نادیده گرفته شوند.
Meta-level: یک تکنیک توصیه بکار برده شده و تعدادی مدل ایجاد می کند که پس از آن بعنوان ورودی تکنیک بعدی مورد استفاده قرار می گیرد.
سیستم توصیه گر شخصیت محور:
رهیافتی جدید که از بوتنر (Buettner) نشأت گرفت. او “توصیه گر محصول شخصیت محور” (Personality-based product recommender(PBPR)) را مطرح کرد، چارچوبی که داده های شبکه اجتماعی را تحلیل می کند تا شخصیت کاربر را پیش بینی کرده و از شخصیت کاربر پی به تمایلات او برد.
فراتر از دقت
معمولاً پژوهش در باب سیستم های توصیه گر، از بابت یافتن دقیق ترین الگوریتم های پیشنهاد، نگران است.
تنوع (Diversity): زمانیکه لیستی بلند بالا و متنوع از پیشنهادات ارائه گردد، موجب رضایت بیشتر کاربر میشود، برای مثال آیتم هایی از هنرمندان مختلف.
اصرار توصیه گر (Recommender persistence): در برخی موارد، دوباره نشان دادن توصیه ها یا اینکه رتبه بندی دوباره ی آیتم ها توسط کاربر، بسیار مؤثر تر از نشان دادن آیتم های جدید است. مثلاً، ممکن است کاربران در دفعه اول وقت کافی برای بررسی دقیق پیشنهادات نداشته اند.
حریم خصوصی(Privacy): معمولاً سیستم های توصیه گیر در رابطه با مسئله حریم خصوصی نگران هستند، چرا که کاربران مجبور به فاش کردن اطلاعات حساس هستند. ایجاد پروفایل کاربران با استفاده از پالایش گروهی می تواند از نقطه نظر حریم خصوصی، مشکل آفرین باشد. بسیاری از کشورهای اروپایی رسومی بسیار غنی از حریم خصوصی دارند، و هر تلاشی که منجر به معرفی سطحی از مشخصات کاربر شود، با عکس العمل منفی مشتری روبرو می شود. با پیشنهاد نت فلیکس برای رقابت جایزه نت فلیکس (Netflix Prize) مسائلی در رابطه با حریم خصوصی در حیطه ی مجموعه اطلاعات بوجود آمد. گرچه برای حفظ حریم خصوصی مشتری، مجوعه داده ها ناشناس بودند، در سال ۲۰۰۷ دو محقق از دانشگاه تگزاس با انطباق مجموعه ی داده هایی که از امتیاز دهی فیلم ها و از Internet Movie Database بدست آمده بود، توانستند کاربران را شناسایی کنند. در سال ۲۰۰۹ کاربر ناشناس نت فلیکس، نت فلیکس را در Doe v. Netflix شکایت کرد و مدعی شد که نت فلیکس قوانین تجارت عادلانه ی ایالات متحده و قانون حفاظت از حریم خصوصی ویدئو (Video Privacy Protection Act) را با پخش مجموعه ی داده ها، نقض کرده است. این ادعا در بخشی منجر به حذف دومین رقابت جایزه نت فلیکس سال ۲۰۱۰ گردید. در این مدت تحقیقات زیادی در زمینه حریم خصوصی انجام گرفته است. راماکریشنان و همکاران، در مورد موازنه ی شخصی سازی و حریم خصوصی تحقیق گسترده ای را انجام داده و دریافتند که از ترکیب روابط ضعیف (یک ارتباط غیر منتظره که بصورت اتفاقی پیشنهادات خوب و جالبی را ارائه می دهد) و دیگر منابع اطلاعاتی می توان برای کشف هویت کاربران در یک مجموعه داده ی ناشناس استفاده کرد.
جمعیت شناختی کاربر (User demographics): بیل وهمکاران، دریافتند که اطلاعات جمعیت شناختی کاربران می تواند بر میزان رضایت مندی آنها از پیشنهادات ارائه شده، تأثیر گذار باشد. آنها در مقاله خود نشان دادند که کاربران مسن تر بیشتر از کاربران جوان، علاقه مند به توصیه های ارائه شده هستند.
نیرومندی (Robustness): زمانیکه کاربران بتوانند در سیستم توصیه گر مشارکت کنند، مسئله کلاهبرداری بایستی مورد توجه قرار گیرد.
سرندی پیتی (یافتن تصادفی)(Serendipity): سرندی پیتی مقیاسی است که نشان می دهد، پیشنهادات چقدر شگفت انگیز و تعجب آور هستند. برای مثال، سیستم توصیه گری که در یک بقالی خرید شیر را به مشتری پیشنهاد می دهد، گرچه ممکن است پیشنهاد دقیقی باشد ولی پیشنهاد خوبی نیست، چرا که “خرید شیر” برای مشتری امری واضح و روشن است و نیازی به پیشنهاد ندارد.
اعتماد (Trust): سیستم توصیه گری که کاربر به آن اعتماد نداشته باشد، از ارزش بسیار پایینی برخوردار است. اعتماد توسط سیستم توصیه گر و با توصیف چگونگی ایجاد پیشنهادات و علت پیشنهاد یک آیتم، ایجاد میگردد.
برچسب گذاری (Labelling): بر چسب گذاری پیشنهادات ممکن است رضایت مندی کاربر را تحت تأثیر قرار دهد. برای مثال در مطالعه ای نرخ کلیک(click-through rate(CTR)) برای پیشنهاداتی که برچسب ” ضمانت” داشتند (CTR=5.93%) کمتر از حالتی بود که همان پیشنهادات برچسب “ارگانیک” داشتند (CTR=8.86%). نکته قابل توجه اینجاست؛ پیشنهاداتی که هیچ برچسبی نداشتند از نرخ کلیک بالاتری برخوردار بودند (CTR=9.87%).
سیستم توصیه گر سیار
تحقیق در حوزه ی سیستم های توصیه گر سیار، یکی از حیطه های تحقیقاتی در حال رشد در زمینه ی سیستم های توصیه گر است. با افزایش دسترسی اسمارت فون ها به اینترنت و همه گیر شدن آن، ارائه پیشنهادات شخصی سازی شده و حساس به محیط ممکن شده است. از آنجاییکه اطلاعات سیار بسیار پیچیده تر از داده هایی است که سیستم های توصیه گر با آن درگیر بوده اند، تحقیقات در این حیطه به مراتب دشوارتر است (مسائلی که این حیطه با آن روبروست: ناهمسانی، پر سر و صدایی، نیاز به همبستگی خودکار مکانی و زمانی ، و نیز مشکلات تأیید و عمومیت دارد). علاوه بر این سیستم های توصیه گر سیار از مشکلات جابجایی نیز متضرر هستند، چرا که ممکن است پیشنهادات ارائه شده در تمامی مناطق بکار نیاید (برای مثال، پیشنهاد یک دستور غذایی که همه ی اجزایش را نمی توان در آن منطقه فراهم کرد، پیشنهادی نابخردانه است).
سیستمی که پیشنهاد کننده مسیرهای ایده آل برای رانندگان شهریست، یک نمونه از سیستم های توصیه گر سیار است. این سیستم داده های خود را از طریق ردیابی GPS راههایی که تاکسی پیموده است، بدست می آورد که این داده ها عبارتند از؛ مکان یابی (طول و عرض جغرافیایی)، نشان دادن زمان و وضعیت اجرایی (با مسافر یا بدون مسافر). سیستم از این داده ها برای بهینه سازی زمان صرف شده برای هر مسافر (یعنی با پیشنهاد ایده آل ترین راه، مدت زمانی که مسافر در تاکسی است به کمترین میزان خود برسد) و عاید کردن سود بیشتر برای راننده تاکسی، بهره می گیرد. این نوع سیستم، وابسته به مکان است، و از آنجاییکه در دستگاههای دستی یا جاساز شده استفاده می شود نیاز محاسباتی و انرژی آن بایستی در سطح پایینی نگه داشته شود.
نمونه ای دیگر از سیستم های توصیه گر سیار، سیستمی است که برای کاربران متخصص توسعه داده شده است (بونفوف و همکاران، ۲۰۱۲). این سیستم با ردیابی GPS کاربر و برنامه ی کاری او، بهترین اطلاعات و پیشنهادات را بسته به موقعیت و علایق وی، ارائه می دهد. این سیستم، از فنون یادگیری ماشینی و پردازش استدلالها برای ایجاد یک سازگاری پویا بین سیستم توصیه گر سیار با سیر تحولی علایق کاربر ، بهره می برد. بانی این الگوریتم نام آن را hybrid-ε-greedyگذاشته است.
سیستم های توصیه گر سیار همچنین”Web of Data” را به عنوان منبعی برای اطلاعات ساختاری، ایجاد کرده اند. یک مثال خوب از این سیستم ها ” “SMARTMUSEUM است. این سیستم حتی زمانیکه اطلاعات کمی از کاربر ارائه شده باشد با استفاده از مدل سازی معنایی، بازیابی اطلاعات و فنون یادگیری ماشینی اقدام به توصیه ی محتوای مطابق با علایق کاربر می کند.
سیستم توصیه گر آگاه از ریسک
تمرکز عمده رهیافت های موجود بکار گرفته شده در سیستم های توصیه گر ارائه ی مرتبط ترین محتوا برای کاربران است و ریسک برآشفتن کاربر در شرایط خاص را به حساب نمی آورند. با این وجود در بسیاری از اپلیکیشن ها (مانند پیشنهاد محتوای شخصی سازی شده) ریسک پریشان کردن کاربر نیز مهم تلقی شده و از تحمیل پیشنهادات در شرایط خاصی همچون یک جلسه ی تخصصی، صبح زود یا دیر وقت ممانعت می شود. از این رو عملکرد سیستم توصیه گر در بخشی به درجه ای از ریسکی که در فرایند پیشنهاد دهی لحاظ می کند، بستگی دارد.
تعریف ریسک
در سیستم های توصیه گر واژه ی “ریسک” به احتمال ایجاد مزاحمت یا پریشان کردن کاربر که منجر به عکس العمل نامناسب وی شود، اطلاق می گردد.
در پاسخ به این چالش ها، جمعی از محققان یک سیستم توصیه گر پویا و حساس به ریسک DRARS(Dyanamic Risk-Aware Recommender system) را ابداع کردند که توصیه ی زمینه ی آگاه (context – aware) را همچون یک “مسئله ی راهزن” (bandit problem) مدلسازی کرده است. این سیستم یک تکنیک محتوا محور را به یک الگوریتم “راهزن متنی” (Contextual bandit) ترکیب کرده است. این محققان نشان دادند که DRARS با محاسبه ی بهینه ترین ارزش اکتشافی برای حفظ توازن بین اکتشاف و بهره برداری مبتنی بر سطح ریسک موقعیت کاربر، موجب بهبود خط مشی “اعتماد به نفس حد بالا” (Upper Confidence Bound(UCB)) می شود. این محققان آزمایشات خود را در یک زمینه صنعتی و با داده ها و کاربران واقعی اجرا کرده و نشان دادند که اهمیت دادن به سطح ریسک موقعیت کاربران، قدرت اجرای سیستم های توصیه گر را افزایش می دهد.
جایزه نت فلیکس
یکی از وقایعی که به تحقیقات سیستم های توصیه گر انرژی مضاعفی بخشید، جایزه نت فلیکس بود. از سال ۲۰۰۶ تا سال ۲۰۰۹ میلادی نت فلیکس اسپانسر رقابتی بود که در آن جایزه ی ۱۰۰۰۰۰۰ دلاری به گروهی تعلق می گرفت که بتواند مجموعه ی داده ای با بیش از ۱۰۰ میلیون فیلم رتبه بندی شده ارائه دهد به نحوی که بازخورد پیشنهادات ۱۰ درصد، دقیق تر از نرم افزار موجود نت فلیکس باشد (در ارائه پیشنهادات، ۱۰ درصد دقیق تر از نت فلیکس عمل کند). این رقابت به امر تحقیق جهت یافتن الگوریتم های جدید و دقیق تر، انرژی مضاعفی بخشید. در ۲۱ سپتامبر ۲۰۰۹ جایزه یک میلیون دلاری با رعایت قانون”tiebreaking” (قانونی که در صورت مساوی شدن رقبا، طرف برنده را تعیین می کند) به تیم Bellkor’s pragmatic Chaos اهدا گردید.
در سال ۲۰۰۷ ترکیبی از ۱۰۷ رهیافت الگوریتمی متفاوت، منجر به ایجاد دقیق ترین الگوریتم پیش بینی گردید:
زمانیکه چندین سیستم پیشگویی با یکدیگر ترکیب شوند، دقت پیشگویی به طور قابل ملاحظه ای افزایش می یابد. تجربه ی ما اینست که بیشتر تلاشها بایستی بر حصول رهیافت های مختلف معتبر متمرکز می شد نه پالایش یک تکنیک مجرد. در نتیجه راه حل ما نیز حاصل جمع آثار مجموعه ای از روش هاست.
مزایای بسیاری بخاطر پروژه نت فلیکس عاید وب شد. تعدادی از تیم فن آوریشان را گرفته و در دیگر بازارها بکار گرفتند. اعضای تیمی که به مقام دوم رسیدند یک موتور توصیه گر به نام Gravity R&Dایجاد کردند که در جامعه RecSys فعال است. ۴-Tell، Inc راه حلی را که از پروژه نت فلیکس بدست آورده بودند در وبسایت های تجارت الکترونیک بکار بردند.
مسابقه ی دوم نیز طراحی شد اما نهایتاً در عکس العمل به طرح دعوایی در دادگاه و ابراز نگرانی از “کمیسیون تجارت فدرال” (Federal Trade Commission) لغو گردید.
سنجش عملکرد
در تشخیص تأثیر الگوریتم های توصیه، ارزیابی امری بسیار مهم است. متریک های معمول استفاده شده در ارزیابی الگوریتم ها، میانگین مربعات خطا و ریشه ی میانگین مربعات خطا می باشد که مورد آخر در جایزه ی نت فلیکس مورد استفاده قرار گرفت. متریک های بازیابی اطلاعات مثل معیار دقت و بازیابی یا DCG جهت ارزیابی کیفیت یک روش پیشنهادی، سودمند و کارا هستند. اخیراً تنوع، نوآوری و پوشش نیز به عنوان جنبه های مهم ارزیابی در نظر گرفته می شوند. هر چند که بسیاری از اندازه گیری های ارزیابی کلاسیک، شدیداً مورد انتقاد قرار گرفته اند. اغلب، نتایج ارزیابی های به اصطلاح آفلاین، با تشخیص واقعی رضایت کاربر هم خوانی ندارد. نویسندگان بیان کردند”ما بایستی در نتایج ارزیابی های افلاین (یا همان اندازه گیری های کلاسیک) تردید می کردیم”.
سیستم توصیه گر چند معیاره
سیستم های توصیه گر چند معیاره (MCRS) به عنوان سامانه هایی توصیف می شوند که سلایق را در چند معیار با هم متحد می سازند. به جای توسعه ی فنون پیشنهادی مبتنی بر ارزش های تک معیاره، تمامی سلایق کاربر در نظر گرفته می شود. این سیستم ها تلاش می کنند تا رتبه بندی آیتم های ناشناخته توسط کاربر را، پیش بینی کنند. این امر با بهره گیری از اطلاعات سلیقه ای و براساس معیارهای چند گانه که تمامی ارزش های سلیقه ای را تحت تأثیر قرار می دهد، ممکن شده است. چندین محقق MCRS را به عنوان یک مسئله ی تصمیم گیر چند معیاره (MCDM) در نظر گرفته و فنون و روش های MCDM را برای اجرای سیستم های MCRS بکار گرفته اند.
سیستم توصیه گر (به انگلیسی: 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}
فرض کنید C مجموعه تمامی کاربران و S مجموعه اقلام در دسترس باشند. تابعی را که میزان مفید و متناسب بودن کالای S برای کاربر C را محاسبه میکند با u نشان میدهیم، که در آن R مجموعهای است کاملاً مرتب (براساس میزان اهمیت). هرکدام از عناصر S را میتوان با مجموعهای از خصوصیات، مشخص کرد. برای مثال، محصولی مثل فیلم را میتوان با مشخصههایی چون عنوان فیلم، کارگردان، طول زمانی فیلم، تاریخ تولید و … ثبت کرد. همچنین عناصر مجموعه C را نیز میتوان بر اساس ویژگیهای مثل سن، جنسیت و … ثبت کرد. (باید توجه داشت که u روی تمام فضای مجموعه آغازین S×C تعریف شده نیست؛ از این رو باید برونیابی شود)