بایگانی برچسب برای: ;kjvg jvnn

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

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

 

CannyEdgeWeel

هر زاویه لبه در 11.25 درجه از یکی از  زاویه های ممکن به آن مقدار تغییر می کند.

 

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

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


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

using namespace std;

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

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

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

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

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

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

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

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

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

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

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

}

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

	// Kill program
	DestroyWindow();	

	

}

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

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

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

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

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

			}
			break;

		default:
			break;
	}	
}

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

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

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

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

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

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

	ReleaseDC(pDC);

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

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

	// End of adds
}

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

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

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

	BYTE			redValue;
	BYTE			greenValue;
	BYTE			blueValue;

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

الگوریتم Canny در سی پلاس پلاس قسمت 1
الگوریتم Canny در سی پلاس پلاس قسمت 2
الگوریتم Canny در سی پلاس پلاس قسمت 3
الگوریتم Canny در سی پلاس پلاس قسمت 4

عامل‌ های هوشمند

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

تعریف عامل:

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

نحوه کار عامل:

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

عامل خود مختار

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

انواع محیط ها

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

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

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

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

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

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

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

عامل های واکنشی ساده

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

منبع


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

ساختار یک عامل

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

عامل هوشمند (Rational Agent)

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

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

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

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

تفاوت ِعقلانیت و علم لایتناهی

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

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

سوالی که پیش می آید این است که تصمیم شما برای عبور از خیابان یک تصمیم اشتباه و غیر هوشمندانه بوده است؟ آیا از عمل شما به عنوان یک عمل غیر عقلانی یاد خواهد شد؟

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

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

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

عامل هوشمند ایده آل

مجموعه تعاریف و مطالب فوق، ما را به سمت تعرف مفوهم عامل هوشمند ایده آل هدایت مینماید. عامل هوشمند ایده آل، عاملی است که برای هر مجموعه از توالی مشاهدات، با توجه به شواهد موجود در محیط و دانش پیش ساخته خود، آن عملِ مورد انتظاری را انجام دهد که باعث افزایش اندازه عملکرد و یا همان performance measure بشود.

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

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

نگاشت ایده آل، از توالی مشاهدات به عمل

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

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

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

 

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

فرض کنید که داخل یک متد باید چندین متد را به صورت await فراخوانی کنید. به صورت عادی زمانی که متدها فراخوانی می شوند هر بخش await بعد از تکمیل await قبلی اجرا خواهد شد و مقادیر بازگشتی به صورت یکجا در اختیار شما قرار نمیگیرند. برای مثال، کد زیر را در نظر بگیرید:

private async void AsyncBtn_Click(object sender, EventArgs e)
{
    Result1TextBox.Text = (await Task1()).ToString();
    Result12extBox.Text = (await Task2()).ToString();
}
 
private Task < long > Task1()
{
    return Task.Run<long>(() = >
    {
        var num = Enumerable.Repeat(10, 1000);
        long sum = 0;
        foreach (var item in num)
        {
            System.Threading.Thread.Sleep(2);
            sum += item;
        }
        return sum;
    });
}
private Task < long > Task2()
{
    return Task.Run<long>(() = >
    {
        var num = Enumerable.Repeat(10, 1000);
        long sum = 0;
        foreach (var item in num)
        {
            System.Threading.Thread.Sleep(2);
            sum += item;
        }
        return sum;
    });
}

 

در کد بالا، ابتدا عملیات Task1 انجام شده و نتیجه نمایش داده می شود و پس از آن Task2 اجرا شده و نتیجه نمایش داده می شود. برای رفع وقفه بین اجرای دو Task از متد WhenAll استفاده می کنیم. برای استفاده از متد WhenAll کد BtnAsync_Click را به صورت زیر تغییر می دهیم:

 

private async void AsyncBtn_Click(object sender, EventArgs e)
{
    var results = await Task.WhenAll(Task1(), Task2());
    txtBox.Text = results[0].ToString();
    txtSecond.Text = results[1].ToString();
}

 

با ایجاد تغییر کد بالا، خروجی متد WhenAll یک آرایه از نوع long خواهد بود که هر یک از اندیس های آرایه به ترتیب خروجی متدهای اول و دوم می باشد و به صورت بالا می توان خروجی ها را در TextBox ها نمایش داد.

منبع


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

 

 

 

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

تا این لحظه از مجموعه مطالب مرتبط با مباحث Asynchronous Programming در سی شارپ با ماهیت Asynchronous در delegate ها، کار با Thread ها و کتابخانه TPL در دات نت آشنا شدیم. اما باز هم در برخی سناریو ها و انجام کارهای پیچیده در برنامه نویسی Asynchronous، نیاز به حجم زیادی از کدها دارد.

از نسخه 4.5 دات، در زبان سی شارپ (و همینطور زبان VB) دو کلمه کلیدی اضافه شد که اجازه نوشتن کدهای Asynchronous را به شکل دیگری به برنامه نویسان می داد. این دو کلمه کلیدی، کلمات async و await هستند و زمانی که شما در کدهای خود از این دو کلمه کلیدی استفاده می کنید، در زمان کامپایل کدها، کامپایلر کدهایی را برای شما تولید می کند که به صورت بهینه و البته مطمئن کارهای Asynchronous را برای شما انجام می دهند، کدهای تولید شده از کلاس هایی که در فضای نام System.Threading.Tasks قرار دارند استفاده می کنند.

نگاه اولیه با ساختار async و await

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

برای آشنایی بیشتر برنامه ای از نوع Windows Forms Application ایجاد کرده، یک Button بر روی فرم قرار می دهیم. زمانی که بر روی Button ایجاد شده کلیک می شود، یک متد دیگر فراخوانی شده و بعد از یک وقفه 10 ثانیه ای عبارتی را بر میگرداند و در نهایت این متن به عنوان Title برای فرم برنامه ست می شود:

public partial class MainForm : Form
{
    public MainForm()
    {
        InitializeComponent();
    }
 
    private void CallButton_Click(object sender, EventArgs e)
    {
        this.Text = DoWork();
    }
 
    private string DoWork()
    {
        Thread.Sleep(10000);
        return "Done.";
    }
}

مشکلی که وجود دارد این است که بعد از کلیک بر روی Button ایجاد شده، 10 ثانیه باید منتظر شده تا عنوان فرم تغییر کند. اما با انجام یکسری تغییرات در کد بالا، می توان بوسیله کلمات کلیدی async و await کاری کرد که عملیات اجرای متد به صورت Asynchronous انجام شود. برای اینکار کد بالا را به صورت زیر تغییر می دهیم:

public partial class MainForm : Form
{
    public MainForm()
    {
        InitializeComponent();
    }
 
    private async void CallButton_Click(object sender, EventArgs e)
    {
        this.Text = await DoWork();
    }
 
    private Task<string> DoWork()
    {
        return Task.Run(() = >
        {
            Thread.Sleep(10000);
            return "Done.";
        });
    }
}

بعد از اجرای برنامه، خواهیم دید که فرم ما به قول معروف block نمی شود، یعنی تا زمان اتمام فراخوانی DoWork می توانیم کارهای دیگری در فرم انجام دهیم. اگر در کد بالا دقت کنید، متدی که برای رویداد Click دکمه CallButton تعریف شده، با کلمه کلیدی async مشخص شده، یعنی اجرای این متد باید به صورت Aynchronous انجام شود.

علاوه بر این، داخل بدنه این متد، زمان فراخوانی DoWork از کلمه await استفاده کردیم، دقت کنید که نوشتن کلمه کلیدی await اینجا الزامی است، اگر این کلمه کلیدی نوشته نشود، زمان اجرای DoWork باز هم عملیات فراخوانی متد باعث block شدن فرم ما می شود. همچنین دقت کنید که متد DoWork به جای اینکه مقدار string برگرداند، مقداری از نوع <Task<string بر میگرداند. به طور خلاصه کاری که DoWork انجام می دهد به صورت زیر است:

زمانی که متد DoWork فراخوانی می شود، یک Task جدید اجرا می شود و داخل Task ابتدا عملیات اجرای Thread به مدت 10 ثانیه متوقف می شود و بعد از 10 ثانیه یک رشته به عنوان خروجی برگردانده می شود. البته این رشته تحت یک شئ از نوع Task به متدی که DoWork را فراخوانی کرده بازگردانده می شود.

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

قواعد نام گذاری برای متدهای Async

همانطور که گفتیم، داخل متدهایی که با async مشخص شده اند، حتماً می بایست کلمه کلیدی await نیز نوشته شود. اما از کجا بدانیم کدام متدها می توانند به صورت Async فراخوانی شوند؟ یعنی نوع خروجی آن ها یک Task است؟ اصطلاحاً به متدهایی که خروجی آن ها از نوع <Task<T است Awaitable گفته می شود. برای اینکار باید از قواعد نامگذاری متدهای Async پیروی کنیم. بر اساس مستندات مایکروسافت، می بایست کلیه متدهایی که مقدار خروجی آن ها از نوع Task است، به صورت async تعریف شوند و در انتهای نام متد کلمه Async نوشته شود، بر اساس مطالب گفته شده، متد DoWork را به صورت زیر تغییر می دهیم:

private async Task<string> DoWorkAsync()
{
    return await Task.Run(() = >
    {
        Thread.Sleep(10000);
        return "Done.";
    });
}

با انجام تغییرات بالا، کد رویداد Click را برای CallButton به صورت زیر تغییر می دهیم:

private async void CallButton_Click(object sender, EventArgs e)
{
    this.Text = await DoWorkAsync();
}

متدهای Async با مقدار خروجی void

در صورتی که متدی که قرار است به صورت async فراخوانی شود، مقدار خروجی ندارد می توان نوع خروجی متد را از نوع کلاس غیر جنریک Task انتخاب کرد و کلمه کلیدی return را ننوشت:

private async Task DoWorkAsync()
{
    await Task.Run(() = >
    {
        Thread.Sleep(10000);
    });
}

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

await DoWorkAsync();
MessageBox.Show("Done.");

متدهای async با چندین await

یکی از قابلیت های async و await، نوشتن چندین قسمت await در یک متد async است. نمونه کد زیر حالت گفته شده را نشان می دهد:

private async void CallButton_Click(object sender, EventArgs e)
{
    await Task.Run(() = > { Thread.Sleep(5000); });
    MessageBox.Show("First Task Done!");
 
    await Task.Run(() = > { Thread.Sleep(5000); });
    MessageBox.Show("Second Task Done!");
 
    await Task.Run(() = > { Thread.Sleep(5000); });
    MessageBox.Show("Third Task Done!");
}

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

منبع


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

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

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

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

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

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

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

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

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

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

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

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

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

 

کار با Thread ها در زبان سی شارپ :: آشنایی با CLR ThreadPool در دات نت

به عنوان آخرین مبحث از سری مباحث مرتبط کار با Thread ها به سراغ نقش CLR ThreadPool می رویم. در قسمت ماهیت Asynchronous در delegate ها گفتیم که بوسیله متد BeginInvoke و EndInvoke می توان یک متد را به صورت Asynchronous فراخوانی کرد، اما نکته ای که اینجا وجود دارد این است که CLR با این کار به صورت مستقیم Thread جدیدی ایجاد نمی کند!

برای کارآیی بیشتر متد BeginInvoke یک آیتم در ThreadPool ایجاد می کند که فراخوانی آن در زمان اجرا مدیریت می شود. برای اینکه بتوانیم به طور مستقیم با این قابلیت در ارتباط باشیم، کلاسی با نام ThreadPool در فضای نام System.Threading وجود دارد که قابلیت این کار را به ما می دهد.

برای مثال اگر بخواهیم فراخوانی یک متد را به Thread Pool بسپاریم، کافیست از متد استاتیکی که در کلاس ThreadPool و با نام QueueUserWorkItem وجود دارد استفاده کنیم. بوسیله این متد می توان Callback مرتبط با کار مد نظر و همچنین شئ ای که به عنوان state استفاده می شود را به این متد ارسال کنیم. در زیر ساختار این کلاس را مشاهده می کنید:

public static class ThreadPool
{
    public static bool QueueUserWorkItem(WaitCallback callback);
    public static bool QueueUserWorkItem(WaitCallback callback, object state);
} 

پارامتر WaitCallBack می تواند به هر متدی که نوع بازگشتی آن void و پارامتر ورودی آن از نوع System.Object است اشاره کند. دقت کنید که اگر مقداری برای state مشخص نکنید، به صورت خودکار مقدار null به آن پاس داده می شود. برای آشنایی بیشتر با این کلاس به مثال زیر که همان مثال نمایش اعداد در خروجی است دقت کنید، در ابتدا کلاس Printer:

public class Printer
{
    object threadLock = new object();
    public void PrintNumbers()
    {
        lock (threadLock)
        {
            Console.Write("{0} is printing numbers &gt; ", Thread.CurrentThread.Name);
            for (int counter = 0; counter &lt; 10; counter++)
            {
                Thread.Sleep(200 * new Random().Next(5));
                Console.Write("{0},", counter);
            }
            Console.WriteLine();
        }
    }
}

در ادامه کد متد Main که با استفاده از ThreadPool عملیات ایجاد Thread ها را انجام می دهد:

class Program
{
    static void Main(string[] args)
    {
        var printer = new Printer();
        for (int index = 0; index &lt; 10; index++)
            ThreadPool.QueueUserWorkItem(PrintNumbers, printer);
 
        Console.ReadLine();
    }
 
    static void PrintNumbers(object state)
    {
        var printer = (Printer) state;
        printer.PrintNumbers();
    }
}

شاید این سوال برای شما پیش بیاید که مزیت استفاده از ThreadPool نسبت به اینکه به صورت دستی عملیات ایجاد و فراخوانی thread ها را انجام دهیم چیست؟ در زیر به برخی از مزایای اینکار اشاره می کنیم:

  1. Thread Pool به صورت بهینه تعداد عملیات مدیریت thread هایی که می بایست ایجاد شوند، شروع بشوند یا متوقف شوند را برای ما انجام می دهد.
  2. با استفاده از Thread Pool شما می توانید تمرکز خود را به جای ایجاد و مدیریت Thread ها بر روی منطق و اصل برنامه بگذارید و سایر کارها را به عهده CLR بگذارید.

اما موارد زیر نیز را مد نظر داشته باشید که مزیت ایجاد و مدیریت thread ها به صورت دستی می باشند:

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

با به پایان رسیدن این مطلب، بحث ما بر روی Thread ها به پایان می رسد. به امید خدا در مطالب بعدی راجع به بحث Parallel Programming صحبت خواهیم خواهیم کرد.

 

منبع


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

 OSD (on screen Display) Menu Setup

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

Menu Setup

DWDR

تصاویر ویدئویی واضح تر و با جزئیات بیشتر. DWDR نقاط سایه ای سیاهی که در تصویر بوجود می آید را از بین می برد این قابلیت در محیط هایی که هم نقاط تیره و هم نقاط روشن دارد بسیار کاربردی است .

DWDR

SMART IR

این ویژگی از بین برنده ی نوردهی بالای چراغ های IR روی دوربین ها در شرایطی است که ممکن است فاصله ی دوربین تا جسم خیلی زیاد نباشد.

SMART IR

(DNR(Digital Noise Redctron

کاهش نویز تصویر در محیط های با نور پایین (تصویر تهیه شده در شب).
دوربین های با امکان DNR در مقایسه با دوربین هایی که امکان DNR ندارند نویز کمتری ایجاد می کنند.

(DNR(Digital Noise Redctron

HLC Highlight Compensation

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

HLC Highlight Compensation

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

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

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

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

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

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

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

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

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

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

Press any key to terminate application...
06:40:06
06:40:07
06:40:08
06:40:09
06:40:10
06:40:11

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

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

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

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

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

Press any key to terminate application...
06:42:10 State from Main Method!
06:42:11 State from Main Method!
06:42:12 State from Main Method!
06:42:13 State from Main Method!
06:42:14 State from Main Method!
06:42:15 State from Main Method!

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

منبع


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

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

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

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

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

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

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

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

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

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

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

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

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

مقدمه

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

مراحل اتصال به DVR

1. آدرس آی پی (IP)، زیر شبکه(subnet mask) و دروازه پیش فرض(default gateway) کامپیوتر خود را پیدا کنید و در جدول شماره 1 یادداشت نمایید

        الف ) صفحه command prompt را باز میکنید

command prompt

شکل شماره 1

        ب ) در صفحه cmd باز شده، دستور ipconfig را تایپ کرده و اطلاعات داده شده را در جدول 1 یادداشت نمایید.

صفحه cmd

شکل شماره 2

جدول شماره 1

شکل شماره 3-جدول شماره 1

توجه داشته باشید که اطلاعات آدرس شبکه ای کامپیوتر شما ممکن است با آنچه در تصویر آمده است متفاوت باشد. همچنین در نظر داشته باشید که آدرس IP در ویندوز های Vista و 7 با عنوان IPV4 نمایش داده می شوند.

2. آدرس آی پی دستگاه DVR خود را با استفاده از راهنمای موجود در جعبه تغییر دهید.
الف ) اتصالاتDVR را برقرار کنید. (بجز اتصال کابل شبکه)
ب ) DVR را روشن کرده و از طریق setup و گزینه Network وارد تنظیمات شبکه شوید.
ج ) امکان استفاده از DHCP را روی DVR غیر فعال کنید.
د ) یک آدرس آی پی برای دستگاه DVR خود در نظر بگیرید.
A ) دقیقا آدرس آی پی کامپیوتر خود را بجز قسمت آخر یادداشت کنید. مثلا 192.168.1.
B ) برای قسمت آخر یک عدد بین 1 تا 255 انتخاب کنید. توجه کنید که این عدد باید با عدد آخر آدرس آی پی کامپیوتر شما متفاوت باشد.
C ) در صفحه cmd با استفاده از دستور ping تست کنید که آیا این آی پی در شبکه موجود می باشد یا خیر
Ping 192.168.1.عدد انتخاب شده

دستور ping

شکل شماره 4

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

استفاده از ادرس برای DVR

شکل شماره 5

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

بررسی ارتباط با DVR

شکل شماره 6

ک ) درصورتیکه ارتباط برقرار است می توانید با وارد کردن آی پی در برنامه مورد نظر روی دستگاه کامپیوتر یا دستگاه موبایل خود و دادن پورت مشخص شده در قسمت “و” ارتباط تصویری با DVR را برقرار نمایید.
ل ) درصورتیکه می خواهید از مرورگر های اینترنتی جهت اتصال و مانیتورینگ DVR استفاده نمایید کافیست مرورگر مورد نظر (IE, firefox, chrome) را باز کرده و در قسمت address bar، آی پی   DVR را وارد کرده و در انتها با یک “:” پورت را به آن معرفی نمایید.
برای مثال اگر آی پی DVR شما 192.168.1.100 است و پورت تعریف شده 92 می باشد. آدرس 192.168.1.100:92 را در مرورگر وارد نمایید.

آدرس ای پی DVR به همراه پورت

شکل شماره 7

انتقال تصاویر روی اینترنت

بعضی از DVR ها امکان انتقال تصاویر بر روی اینترنت از طریق مرورگر یا دستگاه های موبایل را دارا می باشند.
برای این منظور نیاز به یک ارتباط پرسرعت اینترنت در محل قرارگیری دستگاه DVR و یک ارتباط پرسرعت اینترنت دیگر در محلی که قرار است مانیتورینگ انجام پذیرد، وجود دارد.
برای استفاده از ویژگی انتقال تصویر بایستی port forwarding روی روتر اینترنت محل قرارگیری دستگاه DVR انجام شود.
1. با کمک اطلاعات موجود در دفترچه راهنمای روتر (مودم ADSL) خود و یا استفاده از اطلاعات وب سایت، http://portforwarding.comروتر خود را برای انجام port forwarding تنظیم نمایید.

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

2. بعد از انجام پروسه port forwarding کافیست با مراجعه به سایت www.yougetsignal.com و استفاده از ابزار port forwarding tester بررسی نمایید که آیا پروسه با موفقیت انجام شده است یا خیر.
3. با استفاده از ابزار what is my IP در وب سایت www.yougetsignal.com یا با بازکردن سایت www.whatismyip.com روی یکی از کامپیوتر های موجود در شبکه ای که دستگاه DVR هم در آن قرار دارد، شبکه خود را بدست آورید.
4. روی کامپیوتر یا دستگاه موبایل محلی که می خواهید مانیتوریگ انجام شود کافیست همانند اینکه کامپیوتر یا موبایل در همان شبکه DVR قرار دارد، عمل نمایید تنها با این تفاوت که بجای آدرس آی پی محلی که قبلا وارد کرده اید (قسمت “ل”) آدرس آی پی ای را که از سایت www.whatismyip.com بدست آورده اید وارد می کنید و بجای پورت داخلی، پورت خارجی ای که از شرکت سرویس دهنده اینترنت دریافت کرده اید وارد می نمایید.
لازم به ذکر است مرورگرهای اینترنتی بطور پیش فرض از پورت 80 استفاده می کنند که گاهی این پورت در شبکه داخلی یا شبکه اینترنت قبلا توسط نرم افزار دیگری مورد استفاده قرار گرفته یا شرکت سرویس دهنده اینترنت جهت امنیت بیشتر این پورت را بسته است.

منبع

الگوریتم Canny

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

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

 

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

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

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

 

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

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

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

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

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

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

 

 

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

 

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

 

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

using namespace cv;

/// Global variables

Mat src, src_gray;
Mat dst, detected_edges;

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

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

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

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

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

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

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

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

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

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

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

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

  return 0;
  }

 

 

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

CannyInOpenCV

رمز فایل : behsanandish.com