راهنمای SEO در سال 2018
پیشنیازهای این سری
در این سری از بانک اطلاعاتی استاندارد مثال به همراه SQL Server 2016، به نام WideWorldImporters استفاده میکنیم. برای دریافت آن، به قسمت releases مثالهای مایکروسافت مراجعه کرده و فایل WideWorldImporters-Full.bak را دریافت کنید. پس از دریافت این فایل، برای restore سریع آن، میتوانید دستور زیر را اجرا کنید که در آن باید مسیر فایل bak دریافتی و همچنین مسیر ایجاد فایلهای mdf/ldf/ndf را مطابق مسیرهای سیستم خودتان اصلاح نمائید (فقط مسیر پوشهها را نیاز است تغییر دهید):
use master; RESTORE DATABASE WideWorldImporters FROM disk='D:\path\WideWorldImporters-Full.bak' WITH MOVE 'WWI_Primary' TO 'D:\SQL_Data\WideWorldImporters.mdf', MOVE 'WWI_Log' TO 'D:\SQL_Data\WideWorldImporters_log.ldf', MOVE 'WWI_UserData' TO 'D:\SQL_Data\WideWorldImporters_UserData.ndf', MOVE 'WWI_InMemory_Data_1' TO 'D:\SQL_Data\WideWorldImporters_InMemory_Data_1'
یافتن اطلاعاتی در مورد کوئریها
SQL Server زمانیکه یک کوئری را اجرا میکند، اطلاعاتی را نیز به همراه آن تولید خواهد کرد که سبب ایجاد یک Query Plan میشود و در آن، اطلاعاتی مانند جداول مورد استفاده، نوع جوینها، ایندکسهای استفاده شده و غیره وجود دارند. علاوه بر آن، Query Statistics نیز قابل دسترسی هستند که در آن مدت زمان اجرای یک کوئری، میزان I/O صورت گرفته و میزان مصرف CPU کوئری، ذکر میشوند. برای دسترسی یافتن به این اطلاعات، میتوان به اشیاء مختلف SQL Server مراجعه کرد؛ مانند dynamic management objects یا به اختصار DMO's، همچنین extended events، traces، query stores و یا حتی management studio. مهمترین تفاوت اینها نیز در نحوهی دسترسی به اطلاعات آنها است که میتواند زنده (live) و یا ذخیره شده در جائی باشند. در اینجا تنها منبعی که امکان مشاهدهی این اطلاعات را به صورت زنده میسر میکند، management studio است. البته live در اینجا به معنای امکان مشاهدهی تمام اطلاعات مرتبط با یک کوئری، مانند آمار و کوئری پلن آن در داخل محیط management studio، پس از اجرای یک کوئری است. در این قسمت بیشتر به روش استخراج اطلاعات آماری کوئریهای زنده میپردازیم و در قسمتهای بعدی، سایر گزینههای نامبرده شده را نیز بررسی خواهیم کرد.
مشاهدهی زندهی دادههای مرتبط با اجرای یک کوئری در management studio
پس از restore بانک اطلاعاتی مثال WideWorldImporters که عنوان شد، در برنامهی Microsoft SQL Server Management Studio، کوئری زیر را اجرا میکنیم:
USE [WideWorldImporters]; GO SELECT [s].[StateProvinceName], [s].[SalesTerritory], [s].[LatestRecordedPopulation], [s].[StateProvinceCode] FROM [Application].[Countries] [c] JOIN [Application].[StateProvinces] [s] ON [s].[CountryID] = [c].[CountryID] WHERE [c].[CountryName] = 'United States'; GO
اینجا است که نیاز به اطلاعات بیشتری در مورد نحوهی اجرای این کوئری داریم. برای استخراج این اطلاعات، اینبار گزینههای تولید و جمع آوری اطلاعات آماری IO و TIME را روشن میکنیم و سپس همان کوئری قبلی را اجرا خواهیم کرد:
USE [WideWorldImporters]; GO SET STATISTICS IO ON; GO SET STATISTICS TIME ON; GO SELECT [s].[StateProvinceName], [s].[SalesTerritory], [s].[LatestRecordedPopulation], [s].[StateProvinceCode] FROM [Application].[Countries] [c] JOIN [Application].[StateProvinces] [s] ON [s].[CountryID] = [c].[CountryID] WHERE [c].[CountryName] = 'United States'; GO
SQL Server parse and compile time: CPU time = 0 ms, elapsed time = 504 ms. (53 rows affected) Table 'Countries'. Scan count 0, logical reads 118, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0. Table 'StateProvinces'. Scan count 1, logical reads 43, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0. SQL Server Execution Times: CPU time = 0 ms, elapsed time = 10 ms.
استخراج اطلاعات Actual Execution Plan یک کوئری
کوئری را زیر با فرض IO ON و TIME ON حاصل از اجرای کوئری قبل، اجرا میکنیم:
USE [WideWorldImporters]; GO SET STATISTICS XML ON; GO SELECT [s].[StateProvinceName], [s].[SalesTerritory], [s].[LatestRecordedPopulation], [s].[StateProvinceCode] FROM [Application].[Countries] [c] JOIN [Application].[StateProvinces] [s] ON [s].[CountryID] = [c].[CountryID] WHERE [c].[CountryName] = 'United States'; GO SET STATISTICS XML OFF; GO
SQL Server parse and compile time: CPU time = 0 ms, elapsed time = 0 ms. SQL Server Execution Times: CPU time = 0 ms, elapsed time = 0 ms. SQL Server parse and compile time: CPU time = 0 ms, elapsed time = 0 ms. SQL Server Execution Times: CPU time = 0 ms, elapsed time = 0 ms. SQL Server parse and compile time: CPU time = 0 ms, elapsed time = 7 ms. (53 rows affected) Table 'Countries'. Scan count 0, logical reads 118, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0. Table 'StateProvinces'. Scan count 1, logical reads 43, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0. (1 row affected) SQL Server Execution Times: CPU time = 15 ms, elapsed time = 179 ms. SQL Server parse and compile time: CPU time = 0 ms, elapsed time = 0 ms. SQL Server Execution Times: CPU time = 0 ms, elapsed time = 0 ms.
اگر بر روی این XML کلیک کنیم، برگهی جدید نمایش گرافیکی این plan ظاهر میشود:
با کلیک راست بر روی این برگه، میتوان اطلاعات آنرا جهت بررسیهای بعدی و یا به اشتراک گذاری آن ذخیره کرد.
در این plan اگر اشارهگر ماوس را بر روی هر کدام از عناصر آن حرکت دهیم، اطلاعاتی مانند actual number of rows نیز مشاهده میشود، در کنار اطلاعات تخمینی؛ به همین جهت به آن Actual Execution Plan هم گفته میشود.
این یک روش دسترسی به Execution Plan است. روش دوم آن با استفاده از امکانات رابط کاربری خود Management Studio است؛ با فشردن دکمههای Ctrl+M و یا انتخاب گزینهی Include actual execution plan از منوی Query آن. پس از آن کوئری زیر را اجرا کنید:
SET STATISTICS IO ON; GO SET STATISTICS TIME ON; GO SELECT [s].[StateProvinceName], [s].[SalesTerritory], [s].[LatestRecordedPopulation], [s].[StateProvinceCode] FROM [Application].[Countries] [c] JOIN [Application].[StateProvinces] [s] ON [s].[CountryID] = [c].[CountryID] WHERE [c].[CountryName] = 'United States'; GO
استخراج اطلاعات Estimated Execution Plan یک کوئری
تا اینجا نحوهی استخراج اطلاعات Actual Execution Plan را بررسی کردیم که به همراه اطلاعات دقیق حاصل از اجرای کوئری نیز بود؛ مانند actual number of rows. نوع دیگری از Execution Planها را نیز میتوان از SQL Server درخواست کرد که به آنها Estimated Execution Plan گفته میشود و حاصل اجرای کوئری نیستند؛ بلکه تخمینی هستند از روش اجرای این کوئری توسط SQL Server. برای فعالسازی محاسبهی آن، ابتدا کوئری زیر را در management studio انتخاب کنید:
USE [WideWorldImporters]; GO SELECT [s].[StateProvinceName], [s].[SalesTerritory], [s].[LatestRecordedPopulation], [s].[StateProvinceCode] FROM [Application].[Countries] [c] JOIN [Application].[StateProvinces] [s] ON [s].[CountryID] = [c].[CountryID] WHERE [c].[CountryName] = 'United States'; GO
همانطور که مشاهده میکنید، اینبار نتیجهی حاصل، به همراه اطلاعاتی مانند actual number of rows نیست و صرفا تخمینی است از روش اجرای این کوئری، توسط SQL Server.
جمع آوری اطلاعات آماری کلاینتها
در منوی Query، گزینهای تحت عنوان Include client statistics نیز وجود دارد. با انتخاب آن، اگر کوئری زیر را اجرا کنیم:
USE [WideWorldImporters]; GO SELECT [s].[StateProvinceName], [s].[SalesTerritory], [s].[LatestRecordedPopulation], [s].[StateProvinceCode] FROM [Application].[Countries] [c] JOIN [Application].[StateProvinces] [s] ON [s].[CountryID] = [c].[CountryID] WHERE [c].[CountryName] = 'United States'; GO
در اینجا مشخص میشود که آیا عملیات insert/update/delete انجام شدهاست. چه تعداد ردیف تحت تاثیر اجرای این کوئری قرار گرفتهاند. چه تعداد تراکنش انجام شدهاست. همچنین اطلاعات آماری شبکه و زمان نیز در اینجا ارائه شدهاند.
در همین حالت، کوئری جدید زیر را با تغییر قسمت where کوئری قبلی، اجرا کنید:
SELECT [s].[StateProvinceName], [s].[SalesTerritory], [s].[LatestRecordedPopulation], [s].[StateProvinceCode] FROM [Application].[Countries] [c] JOIN [Application].[StateProvinces] [s] ON [s].[CountryID] = [c].[CountryID] WHERE [s].[StateProvinceName] LIKE 'O%'; GO
در اینجا حداکثر 10 کوئری را میتوان با هم مقایسه کرد و بیشتر از آن سبب حذف موارد قدیمی از لیست میشود.
عدم نمایش ردیفهای بازگشت داده شدهی توسط کوئری در حین جمع آوری اطلاعات آماری
هربار اجرای یک کوئری در management studio، به همراه بازگشت و نمایش ردیفهای مرتبط با آن کوئری نیز میباشد. اگر میخواهید در حین بررسی کارآیی کوئریها از نمایش این ردیفها صرف نظر کنید (تا بار این برنامه کاهش یابد)، میتوانید از منوی Query، گزینهی Query Options را انتخاب کرده و در قسمت Results، گزینهی Grid آن، گزینهی discard results after execution را انتخاب کنید تا دیگر برگهی results نمایش داده نشود و وقت و منابع را تلف نکند. بدیهی است پس از پایان کار بررسی آماری، نیاز به عدم انتخاب این گزینه خواهد بود.
SELECT T0.StockId ,T0.QuoteId ,T0.QuoteDay ,T0.QuoteClose , AVG (T0.QuoteClose) OVER (PARTITION BY T0.StockId ORDER BY T0.QuoteId ROWS 19 PRECEDING) AS MA20 FROM dbo.Quotes AS T0
- CUME_DIST:
CUME_DIST( ) OVER ( [ partition_by_clause ] order_by_clause )
Create Table TestCUME_DIST (SalesOrderID int not null, OrderQty smallint not null, ProductID int not null ); GO Insert Into TestCUME_DIST Values (43663,1,760),(43667,3,710),(43667,1,773), (43667,1,775),(43667,1,778),(43669,1,747), (43670,1,709),(43670,2,710),(43670,2,773),(43670,1,776)
SELECT SalesOrderID, OrderQty, CUME_DIST() OVER(ORDER BY SalesOrderID) AS [CUME_DIST] FROM TestCUME_DIST ORDER BY [CUME_DIST] DESC
*** برای بدست آوردن CUME_DIST سطر پنجم نیز خواهیم داشت:
Rows=(c1+c2)/c3 بنابراین خواهیم داشت: 0/6=10/(5+1)=Rows
مثال دوم : ابتدا Script زیر را اجرا نمایید:
SELECT SalesOrderID, OrderQty, ProductID, CUME_DIST() OVER(PARTITION BY SalesOrderID ORDER BY ProductID ) AS [CUME_DIST] FROM TestCUME_DIST WHERE SalesOrderID IN (43670, 43669, 43667, 43663) ORDER BY SalesOrderID DESC, [CUME_DIST] DESC
همانگونه که ملاحظه میکنید، در این مثال، خروجی، براساس SalesOrderID به چهار گروه تقسیم میشود و عملیات مرتب سازی روی فیلد ProductID انجام می گیرد، بنابراین CUME_DIST، روی هر گروه بر روی فیلد ProductID محاسبه میشود.
گروه اول : نحوه محاسبه Cume_DIST سطر اول:
سوال:چه تعداد از مقادیر ProductID آن برابر 776 میباشد؟
جواب: فقط مقدار سطر اول، بنابراین خواهیم داشت C1=1
سوال: چه تعداد از مقادیر کوچکتر از ProductID=776 میباشد؟
جواب: مقدار سه سطر، در واقع مقادیر سطر دوم،سوم و چهارم کوچکتر از مقدار سطر اول میباشند، c2=3
سوال: تعداد کل سطرهای گروه اول چه مقدار میباشد؟
جواب: 4سطر
بنابراین برای بدست آوردن CUME_DIST سطر اول خواهیم داشت:
1=4/(1+3)=Rows
محاسبه سطر دوم از گروه اول بدون شرح:
0/75=4/(1+2)=Rows
امیدوارم مفید واقع شده باشد.
پیشنیاز : «تکرار خودکار سرستونهای یک جدول در صفحات مختلف، توسط iTextSharp»
همانطور که در مطلب پیشنیاز عنوان شده ذکر گردید، iTextSharp امکان درج خودکار header و footer به علاوه محاسبه خودکار تعداد ردیفهای یک جدول در یک صفحه را بر اساس طول و اندازه محتوای هر ردیف، دارد. برای مثال یک صفحه ممکن است 2 ردیف شود و یک صفحه 20 ردیف. تمام اینها را به صورت خودکار محاسبه میکند و بسیار عالی است. (این امکان مهمی است که خیلی از ابزارهای گزارشگیری موجود هنوز با آن مشکل دارند)
اما اگر فرض را بر این بگذاریم که اندازه سلولها و در نتیجه طول هر ردیف ثابت است و مثلا تمام صفحات نهایتا از یک تعداد ردیف مشخص تشکیل خواهند شد، خاصیتی را به نام number of rows یا rows count و امثال آنرا ندارد که مثلا به آن گفت، من در هر صفحه فقط 5 ردیف را میخواهم نمایش دهم و نه 20 ردیف را.
روش حل این مساله را در ادامه ملاحظه خواهید کرد و یک نکتهی خیلی ساده و مستند نشده دارد!
using System.Diagnostics;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;
namespace RowsCountSample
{
class Program
{
static void Main(string[] args)
{
using (var pdfDoc = new Document(PageSize.A4))
{
var pdfWriter = PdfWriter.GetInstance(pdfDoc, new FileStream("Test.pdf", FileMode.Create));
pdfDoc.Open();
var table1 = new PdfPTable(3);
table1.HeaderRows = 2;
table1.FooterRows = 1;
//header row
var headerCell = new PdfPCell(new Phrase("header"));
headerCell.Colspan = 3;
headerCell.HorizontalAlignment = Element.ALIGN_CENTER;
table1.AddCell(headerCell);
//footer row
var footerCell = new PdfPCell(new Phrase("footer"));
footerCell.Colspan = 3;
footerCell.HorizontalAlignment = Element.ALIGN_CENTER;
table1.AddCell(footerCell);
//adding some rows
for (int i = 0; i < 70; i++)
{
//adds a new row
table1.AddCell(new Phrase("Cell[0], Row[" + i + "]"));
table1.AddCell(new Phrase("Cell[1], Row[" + i + "]"));
table1.AddCell(new Phrase("Cell[2], Row[" + i + "]"));
//sets the number of rows per page
if (i > 0 && table1.Rows.Count % 7 == 0)
{
pdfDoc.Add(table1);
table1.DeleteBodyRows();
pdfDoc.NewPage();
}
}
pdfDoc.Add(table1);
}
//open the final file with adobe reader for instance.
Process.Start("Test.pdf");
}
}
}
نکته جدید این مثال، قسمت زیر است:
if (i > 0 && table1.Rows.Count % 7 == 0)
{
pdfDoc.Add(table1);
table1.DeleteBodyRows();
pdfDoc.NewPage();
}
هر زمان که table1 به صفحه اضافه شود، header و footer هم اضافه خواهند شد، اما اگر BodyRows آن حذف نشود، دفعهی دومی که این table به صفحه اضافه میشود، شامل ردیفهای مثلا یک تا 10 خواهد بود بجای 6 تا 10 .
در این قسمت در خصوص توابع مرتبط با ساختار سلسله مراتبی
صحبت خواهد شد.
Select { [Date].[Calendar].[Calendar Quarter].[Q1 CY 2006], cousin( [Date].[Calendar].[Calendar Quarter].[Q1 CY 2006], [Date].[Calendar].[Calendar Year].[CY 2007] ) } on columns, [Measures].[Reseller Sales Amount] on rows From [Adventure Works]
تابع عمو زاده به این صورت کار می کند که دو پارامتر می گیرد . پارامتر اول سطح فعلی را مشخص می کند . پارامتر دوم سطح بالاتر از سطح اول را مشخص می کند در ساختار سلسله مراتبی و خروجی برابر است با سطحی برابر سطح پارامتر اول در زیر مجموعه ی پارامتر دوم و هم تراز پارامتر اول .
خروجی به صورت زیر میباشد:
خوب حالا به ساختار زیر دقت کنید (ساختار سلسله مراتبی Date )
همانطور که مشخص میباشد تاریخها از 2005 تا 2008 و سال 2010 میباشند و فصول عبارتند از دو فصل پایانی سال 2005 و تمامی فصول سال 2006 و 2007 و سه فصل اول سال 2008 و فصل چهارم سال 2010 . حال دوباره به کوئری نوشته شده دقت کنید. در کوئری بالا فصل همسطح فصل اول سال 2006 در سال 2007 مورد واکشی قرار گرفته است که همان فصل اول در سال 2007 میباشد.
حال به بررس کوئری زیر خواهیم پرداخت:
Select { [Date].[Calendar].[Calendar Quarter].[Q1 CY 2006], cousin( [Date].[Calendar].[Calendar Quarter].[Q1 CY 2006], [Date].[Calendar].[Calendar Semester].[H2 CY 2006] ) } on columns, [Measures].[Reseller Sales Amount] on rows From [Adventure Works]
در این کوئری ما ابتدا ستون فصل اول سال 2006 را بر می گردانیم . سپس در تابع پسر عمو در نیم فصل دوم سال 2006 به دنبال هم سطح فصل اول 2006 می گردیم .
نمودار درختی زیر توضیح کاملی به ما خواهد داد:
حال برای ادامهی مطلب کار بر روی ساختارهای سلسله مراتبی، ابتدا باید در خصوص نحوهی ایجاد Range توضیحاتی ارایه گردد. دو کوئری زیر را در نظر گرفته و خروجی آنها را با هم مقایسه نمایید
Select { [Date].[Calendar].[Calendar Quarter].[Q1 CY 2006], [Date].[Calendar].[Calendar Quarter].[Q2 CY 2006], [Date].[Calendar].[Calendar Quarter].[Q3 CY 2006] } on columns, [Measures].[Reseller Sales Amount] on rows From [Adventure Works]
و
Select [Date].[Calendar].[Calendar Quarter].[Q1 CY 2006]: [Date].[Calendar].[Calendar Quarter].[Q3 CY 2006] on columns, [Measures].[Reseller Sales Amount] on rows From [Adventure Works]
خروجیها به صورت زیر میباشد :
و
مشخص میباشد که از علامت <:> برای ایجاد یک محدوده و جلوگیری از تولید کدهای بلند و طولانی استفاده میشود.
حال کوئری زیر را اجرا کنید:
Select [Date].[Calendar].[Calendar Quarter].[Q1 CY 2006] : cousin( [Date].[Calendar].[Calendar Quarter].[Q1 CY 2006], [Date].[Calendar].[Calendar Semester].[H2 CY 2006] ) on columns, [Measures].[Reseller Sales Amount] on rows From [Adventure Works]
در این کوئری در ابتدا تابع پسر عمو اجرا می گردد، سپس تابع رنج اجرا می گردد و در نتیجه، فاصله ی بین Q1 CY 2006 تا Q3 CY 2006 را بدست میآورد.
نمودار درختی زیر توضیح کاملی به ما خواهد داد :
خروجی به صورت زیر میباشد
در قسمتهای بعدی دیگر توابع MDX Queryها را بررسی میکنیم.
کتابخانه Timesheet.js
OpenCVSharp #4
فرض کنید قصد داریم یک چنین مثال زبان C را که در مورد کار با فیلترها در OpenCV است، به نمونهی دات نتی آن تبدیل کنیم:
#include <cv.h> #include <highgui.h> #include <stdio.h> int main (int argc, char **argv) { IplImage *src_img = 0, *dst_img; float data[] = { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; CvMat kernel = cvMat (1, 21, CV_32F, data); if (argc >= 2) src_img = cvLoadImage (argv[1], CV_LOAD_IMAGE_ANYDEPTH | CV_LOAD_IMAGE_ANYCOLOR); if (src_img == 0) exit (-1); dst_img = cvCreateImage (cvGetSize (src_img), src_img->depth, src_img->nChannels); cvNormalize (&kernel, &kernel, 1.0, 0, CV_L1); cvFilter2D (src_img, dst_img, &kernel, cvPoint (0, 0)); cvNamedWindow ("Filter2D", CV_WINDOW_AUTOSIZE); cvShowImage ("Filter2D", dst_img); cvWaitKey (0); cvDestroyWindow ("Filter2D"); cvReleaseImage (&src_img); cvReleaseImage (&dst_img); return 0; }
using (var src = new IplImage(@"..\..\Images\Penguin.Png", LoadMode.AnyDepth | LoadMode.AnyColor)) using (var dst = new IplImage(src.Size, src.Depth, src.NChannels)) { float[] data = { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; var kernel = new CvMat(rows: 1, cols: 21, type: MatrixType.F32C1, elements: data); Cv.Normalize(src: kernel, dst: kernel, a: 1.0, b: 0, normType: NormType.L1); Cv.Filter2D(src, dst, kernel, anchor: new CvPoint(0, 0)); using (new CvWindow("src", image: src)) using (new CvWindow("dst", image: dst)) { Cv.WaitKey(0); } }
در قسمتهای قبلی در مورد بارگذاری تصاویر، تهیهی یک Clone از آن و همچنین ساخت یک پنجره به روشهای مختلف و رها سازی خودکار منابع مرتبط، بیشتر بحث شد. در اینجا تصویر اصلی با همان عمق و وضوح تغییر نیافتهی آن بارگذاری میشود.
کار cvMat، آغاز یک ماتریس OpenCV است. پارامترهای آن، تعداد ردیفها، ستونها، نوع دادهی المانها و دادههای مرتبط را مشخص میکنند.
در این مثال آرایهی data، یک فیلتر را تعریف میکند که در اینجا، حالت یک بردار را دارد تا یک ماتریس. برای تبدیل آن به ماتریس، از شیء CvMat استفاده خواهد شد که آنرا تبدیل به ماتریسی با یک ردیف و 21 ستون خواهد کرد.
در اینجا از نام کرنل استفاده شدهاست. کرنل در OpenCV به معنای ماتریسی از دادهها با یک نقطهی anchor (لنگر) است. این لنگر به صورت پیش فرض در میانهی ماتریس قرار دارد (نقطهی 1- , 1- ).
مرحلهی بعد، نرمال سازی این فیلتر است. تاثیر نرمال سازی اطلاعات را به این نحو میتوان نمایش داد:
double sum = 0; foreach (var item in data) { sum += Math.Abs(item); } Console.WriteLine(sum); // => .999999970197678
اگر مرحلهی نرمال سازی اطلاعات را حذف کنیم، تصویر نهایی حاصل، چنین شکلی را پیدا میکند:
زیرا عملیات تغییر اندازهی اطلاعات بردار صورت نگرفتهاست و دادههای آن مطلوب متد cvFilter2D نیست.
و مرحلهی آخر، اجرای این بردار نرمال شده خطی، بر روی تصویر اصلی به کمک متد cvFilter2D است. این متد، تصویر مبدا را پس از تبدیلات ماتریسی، به تصویر مقصد تبدیل میکند. فرمول ریاضی اعمال شدهی در اینجا برای محاسبهی نقاط تصویر خروجی به صورت زیر است:
فیلترهای توکار OpenCV
علاوه بر امکان طراحی فیلترهای سفارشی خطی مانند مثال فوق، کتابخانهی OpenCV دارای تعدادی فیلتر توکار نیز میباشد که نمونهای از آنرا در مثال ذیل میتوانید مشاهده کنید:
using (var src = new IplImage(@"..\..\Images\Car.jpg", LoadMode.AnyDepth | LoadMode.AnyColor)) { using (var dst = new IplImage(src.Size, src.Depth, src.NChannels)) { using (new CvWindow("src", image: src)) { Cv.Erode(src, dst); using (new CvWindow("Erode", image: dst)) { Cv.Dilate(src, dst); using (new CvWindow("Dilate", image: dst)) { Cv.Not(src, dst); using (new CvWindow("Invert", image: dst)) { Cv.WaitKey(0); } } } } } }
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید.