Quantcast
Channel: ‫فید مطالب وحید نصیری .NET Tips
Viewing all 271 articles
Browse latest View live

‫عدم نمایش آیکن‌های نگارش جدید TortoiseSVN

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


تطابق نگارش ویندوز و نگارش TortoiseSVN

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


برنامه‌های دیگری که از Icon overlays استفاده می‌کنند

در سیستم‌های 64 بیتی، سقف تعداد مدیریت کننده‌های overlay icons (همین آیکن‌های کوچک سبز و قرمز رنگ برنامه TortoiseSVN برای مثال)، فقط 15 عدد است. برای یافتن آن‌ها برنامه‌ی معروف AutoRunsرا نصب کنید:


در اینجا Shell overlay icon identifiers را یافته و موارد اضافی و غیر مهم را به حالت انتخاب نشده در آورید.


مشکل SVN_ASP_DOT_NET_HACK

نگارش‌های اولیه VS.NET، با پوشه‌هایی که ابتدای نام آن‌ها با دات شروع می‌شدند، مشکل داشتند. به همین جهت برنامه‌ی TortoiseSVN در تنظیمات خود، امکان تعریف این پوشه‌ها را به نام SVN_ بجای SVN. میسر کرده بود.
این تنظیم در نگارش‌های جدید TortoiseSVN حذف شده‌است. بنابراین اگر TortoiseSVN جدیدی را بر روی سیستمی که با روش قدیمی پوشه‌های تغییر نام یافته SVN_ کار می‌کرده‌است نصب کنید، به نظر خواهد رسید که همه چیز از کار افتاده است. تنها کاری که باید انجام شود، حذف _ و تبدیل آن به دات است.
اگر پوشه‌های زیادی را دارید که باید به این نحو تغییر کنند، یک bat فایل درست کرده و از دستور زیر استفاده کنید:
 FOR /R %%f IN (_svn) DO IF EXIST "%%f" (
ATTRIB -h "%%f"
RENAME "%%f" .svn
ATTRIB +h "%%f"
)
این دستورات بازگشتی بوده و تمام پوشه‌ها و زیر پوشه‌ها را برای یافتن پوشه SVN_ گشته و آن‌ها را تبدیل به SVN. می‌کنند. برای مثال این فایل bat را در ریشه drive کپی کرده و اجرا کنید.


نیاز به به‌روز رسانی ساختار SVN

در نگارش‌های اولیه SVN، پوشه‌های مخفی آن در هر زیر پوشه‌ای حضور داشتند. در نگارش‌های جدیداین برنامه، تمام این زیر پوشه‌های مخفی تبدیل به یک پوشه‌، در ریشه‌ی پروژه شده‌اند. در این حالت اگر در ریشه‌ی پروژه کلیک راست کنید، TortoiseSVN گزینه‌ی upgrade را نمایش خواهد داد. پس از آن همه چیز به حالت معمول بر می‌گردد.

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

$
0
0
این مطلب دنباله‌ی «تغییر عملکرد و یا ردیابی توابع ویندوز با استفاده از Hookهای دات نتی» است.
روش ارائه شده در آن با ویندوز‌های XP تا 7 نگارش‌های 32 بیتی و 64 بیتی، بدون مشکل کار می‌کند. اما تاثیری بر روی ویندوز 8 و نگار‌ش‌های پس از آن نداشت.

تغییرات توابع GetDateFormatW و GetTimeFormatW در ویندوز اکسپلورر ویندوز 8

چه برنامه‌ی ExplorerPCal و چه API Monitor را اگر با فعال سازی توابع GetDateFormatW و GetTimeFormatW اجرا کنید، هیچ خروجی خاصی را مشاهده نخواهید کرد. در ابتدا به نظر می‌رسد که ساختار ویندوز شاید تغییر کرده‌است ... ولی اینطور نیست. فقط اینبار بجای فراخوانی این توابع از kernel32.dll، از یک dll مخفی در پوشه‌ی System32 استفاده می‌شود. روش پیدا کردن آن نیز به صورت زیر است:
 "C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\bin\dumpbin.exe" /imports c:\windows\explorer.exe > explorer.imports.txt
کار dumpbin.exe موجود در پوشه‌ی VC\bin ویژوال استودیو، استخراج import table و export table یک فایل اجرایی و یا یک dll بومی ویندوز است. به این ترتیب می‌توان دریافت یک فایل exe، از چه dll هایی استفاده می‌کند و همچنین از این dllها، کدامیک از توابع آن‌ها را مورد استفاده قرار داده است.
اگر خروجی این برنامه را که اکنون در فایل explorer.imports.txt ذخیره شده‌است، بررسی کنیم، به نتیجه‌ی زیر خواهیم رسید:
    api-ms-win-core-datetime-l1-1-1.dll
             14015E848 Import Address Table
             1401613E0 Import Name Table
                     0 time date stamp
                     0 Index of first forwarder reference

                           2 GetDateFormatW
                           1 GetDateFormatEx
                           4 GetTimeFormatEx
بله. در ویندوزهای سری 8، دیگر از کرنل32 برای دریافت GetDateFormatW استفاده نمی‌شود. اینبار از dll ایی به نام api-ms-win-core-datetime-l1-1-1.dll کمک گرفته شده‌است. این dll در پوشه‌ی System32 با خاصیت مخفی قرار دارد.
بنابراین تنها تغییری که باید در برنامه‌ی ExplorerPCal داده شود، اضافه کردن مداخل جدید فوق است. در ویندوزهای قبل از 8، از نگارش‌های Ex استفاده نمی‌شد. در اینجا هم از نگارش‌های W و هم Ex دار استفاده شده‌است.

اگر خواستید این تغییرات را با برنامه‌ی API Monitor بررسی کنید، فایل جدید api-ms-win-core-datetime-l1-1-1.xml ذیل را در پوشه‌ی API\Windows آن کپی نمائید تا مداخل api-ms-win-core-datetime-l1-1-1.dll نیز به مجموعه‌ی تعاریف آن اضافه شوند.
api-ms-win-core-datetime-l1-1-1.xml

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

تاثیر آن‌را نیز بر روی Explorer ویندوز 8، در تصاویر ذیل می‌توانید ملاحظه نمائید:

ساعت و تقویم نوار وظیفه‌ی ویندوز

تاریخ تغییرات فایل‌ها، در نمایش لیستی ویندوز اکسپلورر

تاریخ ایجاد و تغییرات یک فایل در خواص آن

تاریخ نمایش داده شده به همراه charm bar ویندوز 8

‫انجام کارهای زمانبندی شده در برنامه‌های ASP.NET توسط DNT Scheduler

$
0
0
اگر به دو مطلب استفاده از Quartz.Net (^و ^) و خصوصا نظرات آن دقت کرده باشید به این نتیجه خواهید رسید که ... این کتابخانه‌ی در اصل جاوایی گنگ طراحی شده‌است. در سایت جاری برای انجام کارهای زمانبندی شده (مانند ارسال ایمیل‌های روزانه خلاصه مطالب، تهیه خروجی PDF و XML سایت، تبدیل پیش نویس‌ها به مطالب، بازسازی ایندکس‌های جستجو و امثال آن) از یک Thread timer استفاده می‌شود که حجم نهایی کتابخانه‌ی محصور کننده و مدیریت کننده‌ی وظایف آن جمعا 8 کیلوبایت است؛ متشکل از ... سه کلاس. در ادامه کدهای کامل و نحوه‌ی استفاده از آن را بررسی خواهیم کرد.


دریافت کتابخانه DNT Scheduler و مثال آن

DNTScheduler.7z
در این بسته، کدهای کتابخانه‌ی DNT Scheduler و یک مثال وب فرم را، ملاحظه خواهید کرد. از این جهت که برای ثبت وظایف این کتابخانه، از فایل global.asax.cs استفاده می‌شود، اهمیتی ندارد که پروژه‌ی شما وب فرم است یا MVC. با هر دو حالت کار می‌کند.



نحوه‌ی تعریف یک وظیفه‌ی جدید

کار با تعریف یک کلاس و پیاده سازی ScheduledTaskTemplate شروع می‌شود:
 public class SendEmailsTask : ScheduledTaskTemplate
برای نمونه :
using System;

namespace DNTScheduler.TestWebApplication.WebTasks
{
    public class SendEmailsTask : ScheduledTaskTemplate
    {
        /// <summary>
        /// اگر چند جاب در یک زمان مشخص داشتید، این خاصیت ترتیب اجرای آن‌ها را مشخص خواهد کرد
        /// </summary>
        public override int Order
        {
            get { return 1; }
        }

        public override bool RunAt(DateTime utcNow)
        {
            if (this.IsShuttingDown || this.Pause)
                return false;

            var now = utcNow.AddHours(3.5);
            return now.Minute % 2 == 0 && now.Second == 1;
        }

        public override void Run()
        {
            if (this.IsShuttingDown || this.Pause)
                return;

            System.Diagnostics.Trace.WriteLine("Running Send Emails");
        }

        public override string Name
        {
            get { return "ارسال ایمیل"; }
        }
    }
}
- در اینجا Order، ترتیب اجرای وظیفه‌ی جاری را در مقایسه با سایر وظیفه‌هایی که قرار است در یک زمان مشخص اجرا شوند، مشخص می‌کند.
- متد RunAt ثانیه‌ای یکبار فراخوانی می‌شود (بنابراین بررسی now.Second را فراموش نکنید). زمان ارسالی به آن UTC است و اگر برای نمونه می‌خواهید بر اساس ساعت ایران کار کنید باید 3.5 ساعت به آن اضافه نمائید. این مساله برای سرورهایی که خارج از ایران قرار دارند مهم است. چون زمان محلی آن‌ها برای تصمیم گیری در مورد زمان اجرای کارها مفید نیست.
در متد RunAt فرصت خواهید داشت تا منطق زمان اجرای وظیفه‌ی جاری را مشخص کنید. برای نمونه در مثال فوق، این وظیفه هر دو دقیقه یکبار اجرا می‌شود. یا اگر خواستید اجرای آن فقط در سال 23 و 33 دقیقه هر روز باشد، تعریف آن به نحو ذیل خواهد بود:
        public override bool RunAt(DateTime utcNow)
        {
            if (this.IsShuttingDown || this.Pause)
                return false;

            var now = utcNow.AddHours(3.5);
            return now.Hour == 23 && now.Minute == 33 && now.Second == 1;
        }
- خاصیت IsShuttingDown موجود در کلاس پایه ScheduledTaskTemplate، توسط کتابخانه‌ی DNT Scheduler مقدار دهی می‌شود. این کتابخانه قادر است زمان خاموش شدن پروسه‌ی فعلی IIS را تشخیص داده و خاصیت IsShuttingDown را true کند. بنابراین در حین اجرای وظیفه‌ای مشخص، به مقدار IsShuttingDown دقت داشته باشید. اگر true شد، یعنی فقط 30 ثانیه وقت دارید تا کار را تمام کنید.
خاصیت Pause هر وظیفه را برنامه می‌تواند تغییر دهد. به این ترتیب در مورد توقف یا ادامه‌ی یک وظیفه می‌توان تصمیم گیری کرد. خاصیت ScheduledTasksCoordinator.Current.ScheduledTasks، لیست وظایف تعریف شده را در اختیار شما قرار می‌دهد.
- در متد Run، منطق وظیفه‌ی تعریف شده را باید مشخص کرد. برای مثال ارسال ایمیل یا تهیه‌ی بک آپ.
- Name نیز نام وظیفه‌ی جاری است که می‌تواند در گزارشات مفید باشد.

همین مقدار برای تعریف یک وظیفه کافی است.


نحوه‌ی ثبت و راه اندازی وظایف تعریف شده

پس از اینکه چند وظیفه را تعریف کردیم، برای مدیریت بهتر آن‌ها می‌توان یک کلاس ثبت و معرفی کلی را مثلا به نام ScheduledTasksRegistry ایجاد کرد:
using System;
using System.Net;

namespace DNTScheduler.TestWebApplication.WebTasks
{
    public static class ScheduledTasksRegistry
    {
        public static void Init()
        {
            ScheduledTasksCoordinator.Current.AddScheduledTasks(
                new SendEmailsTask(),
                new DoBackupTask());

            ScheduledTasksCoordinator.Current.OnUnexpectedException = (exception, scheduledTask) =>
            {
                //todo: log the exception.
                System.Diagnostics.Trace.WriteLine(scheduledTask.Name + ":" + exception.Message);
            };

            ScheduledTasksCoordinator.Current.Start();
        }

        public static void End()
        {
            ScheduledTasksCoordinator.Current.Dispose();
        }

        public static void WakeUp(string pageUrl)
        {
            try
            {
                using (var client = new WebClient())
                {
                    client.Credentials = CredentialCache.DefaultNetworkCredentials;
                    client.Headers.Add("User-Agent", "ScheduledTasks 1.0");
                    client.DownloadData(pageUrl);
                }
            }
            catch (Exception ex)
            {
                //todo: log ex
                System.Diagnostics.Trace.WriteLine(ex.Message);
            }
        }
    }
}
- شیء ScheduledTasksCoordinator.Current، نمایانگر تنها وهله‌ی مدیریت وظایف برنامه است.
- توسط متد ScheduledTasksCoordinator.Current.AddScheduledTasks، تنها کافی است کلاس‌های وظایف مشتق شده از ScheduledTaskTemplate، معرفی شوند.
- به کمک متد ScheduledTasksCoordinator.Current.Start، کار Thread timer برنامه شروع می‌شود.
- اگر در حین اجرای متد Run، استثنایی رخ دهد، آن‌را توسط یک Action delegate به نام ScheduledTasksCoordinator.Current.OnUnexpectedException می‌توانید دریافت کنید. کتابخانه‌ی DNT Scheduler برای اجرای وظایف، از یک ترد با سطح تقدم Below normal استفاده می‌کند تا در حین اجرای وظایف، برنامه‌ی جاری با اخلال و کندی مواجه نشده و بتواند به درخواست‌های رسیده پاسخ دهد. در این بین اگر استثنایی رخ دهد، می‌تواند کل پروسه‌ی IIS را خاموش کند. به همین جهت این کتابخانه کار try/catch استثناهای متد Run را نیز انجام می‌دهد تا از این لحاظ مشکلی نباشد.
- متد ScheduledTasksCoordinator.Current.Dispose کار مدیر وظایف برنامه را خاتمه می‌دهد.
- از متد WakeUp تعریف شده می‌توان برای بیدار کردن مجدد برنامه استفاده کرد.


استفاده از کلاس ScheduledTasksRegistry تعریف شده

پس از اینکه کلاس ScheduledTasksRegistry را تعریف کردیم، نیاز است آن‌را به فایل استاندارد global.asax.cs برنامه به نحو ذیل معرفی کنیم:
using System;
using System.Configuration;
using DNTScheduler.TestWebApplication.WebTasks;

namespace DNTScheduler.TestWebApplication
{
    public class Global : System.Web.HttpApplication
    {
        protected void Application_Start(object sender, EventArgs e)
        {
            ScheduledTasksRegistry.Init();
        }

        protected void Application_End()
        {
            ScheduledTasksRegistry.End();
            //نکته مهم این روش نیاز به سرویس پینگ سایت برای زنده نگه داشتن آن است
            ScheduledTasksRegistry.WakeUp(ConfigurationManager.AppSettings["SiteRootUrl"]);
        }
    }
}
- متد ScheduledTasksRegistry.Init در حین آغاز برنامه فراخوانی می‌شود.
- متد ScheduledTasksRegistry.End در پایان کار برنامه جهت پاکسازی منابع باید فراخوانی گردد.
همچنین در اینجا با فراخوانی ScheduledTasksRegistry.WakeUp، می‌توانید برنامه را مجددا زنده کنید! IIS مجاز است یک سایت ASP.NET را پس از مثلا 20 دقیقه عدم فعالیت (فعالیت به معنای درخواست‌های رسیده به سایت است و نه کارهای پس زمینه)، از حافظه خارج کند (این عدد در application pool برنامه قابل تنظیم است). در اینجا در فایل web.config برنامه می‌توانید آدرس یکی از صفحات سایت را برای فراخوانی مجدد تعریف کنید:
<?xml version="1.0"?><configuration><appSettings><add key="SiteRootUrl" value="http://localhost:10189/Default.aspx" /></appSettings></configuration>
همینکه درخواست مجددی به این صفحه برسد، مجددا برنامه توسط IIS بارگذاری شده و اجرا می‌گردد. به این ترتیب وظایف تعریف شده، در طول یک روز بدون مشکل کار خواهند کرد.


گزارشگیری از وظایف تعریف شده

برای دسترسی به کلیه وظایف تعریف شده، از خاصیت ScheduledTasksCoordinator.Current.ScheduledTasks استفاده نمائید:
var jobsList = ScheduledTasksCoordinator.Current.ScheduledTasks.Select(x => new
{
   TaskName = x.Name,
   LastRunTime = x.LastRun,
   LastRunWasSuccessful = x.IsLastRunSuccessful,
   IsPaused = x.Pause,
}).ToList();
لیست حاصل را به سادگی می‌توان در یک Grid نمایش داد.

OpenCVSharp #8

$
0
0
بررسی morphology (ریخت شناسی) تصاویر

به تصویر زیر دقت کنید:




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


متدهایی که مورفولوژی تصاویر را تغییر می‌دهند

در OpenCV سه متد یا فیلترمهم، کار تغییر مورفولوژی تصاویر را انجام می‌دهند:
1) Cv2.Erode
تحلیل/فرسایش یا erosion سبب می‌شود تا نواحی تیره‌ی تصویر «رشد» کنند.


در اینجا فیلتر Erode کار یکی کردن اجزای جدای توپ‌های رنگی را انجام داده‌است.


2) Cv2.Dilate
اتساع یا dilation سبب خواهد شد تا نواحی روشن تصویر «رشد» کنند.


بکارگیری فیلتر Dilate سبب شده‌‌است تا نویزهای تصویر محو شوند و اشیاء به هم پیوسته از هم جدا گردند.


3) Cv2.MorphologyEx
کار این متد انجام اعمال پیشرفته‌ی مورفولوژی بر روی تصاویر است و در اینجا ترکیبی از erosion و dilation، با هم انجام می‌شوند.
اگر پارامتر سوم آن به MorphologyOperation.Open تنظیم شود، ابتدا erosion و سپس dilation انجام خواهد شد:


و اگر این پارامتر به MorphologyOperation.Close مقدار دهی شود، ابتدا dilation و سپس erosion انجام می‌شود:


در تمام این حالات، پارامتر آخر که Structuring Element نام دارد، یکی از مقادیر اشیاء مستطیل، به علاوه و بیضی را می‌تواند داشته باشد. این اشیاء و اندازه‌ی آن‌ها، مشخص کننده‌ی میزان تحلیل و یا اتساع نهایی هستند.



استفاده از متدهای مورفولوژی در عمل
 
در اینجا مثالی را از نحوه‌ی بکارگیری متدهای اتساع و فرسایش، ملاحظه می‌کنید:
using (var src = new Mat(@"..\..\Images\cvmorph.Png", LoadMode.AnyDepth | LoadMode.AnyColor))
using (var dst = new Mat())
{    src.CopyTo(dst);    var elementShape = StructuringElementShape.Rect;    var maxIterations = 10;     var erodeDilateWindow = new Window("Erode/Dilate", image: dst);    var erodeDilateTrackbar = erodeDilateWindow.CreateTrackbar(        name: "Iterations", value: 0, max: maxIterations * 2 + 1,        callback: pos =>        {            var n = pos - maxIterations;            var an = n > 0 ? n : -n;            var element = Cv2.GetStructuringElement(                    elementShape,                    new Size(an * 2 + 1, an * 2 + 1),                    new Point(an, an));            if (n < 0)            {                Cv2.Erode(src, dst, element);            }            else            {                Cv2.Dilate(src, dst, element);            }            Cv2.PutText(dst, (n < 0) ?                string.Format("Erode[{0}]", elementShape) :                string.Format("Dilate[{0}]", elementShape),                new Point(10, 15), FontFace.HersheyPlain, 1, Scalar.Black);            erodeDilateWindow.Image = dst;        });      Cv2.WaitKey();     erodeDilateWindow.Dispose();
}
اینترفیس به کار گرفته شده، همان C++ APIاست و در اینجا ابتدا یک تصویر و کپی آن تهیه می‌شوند. سپس پنجره‌ی سازگار با C++ API ایجاد شده و به این پنجره یک شیء tracker اضافه می‌شود. این tracker یا slider، چندسکویی است. بدیهی است اگر قرار بود چنین کاری را صرفا با یک برنامه‌ی دات نتی انجام داد، می‌شد قسمت ایجاد پنجره و tracker آن‌را حذف کرد و بجای آن‌ها از یک picture boxو یک slider به همراه مدیریت روال رخدادگردان تغییر مقادیر slider کمک گرفت. اما در اینجا تنها جهت آشنایی با این امکانات توکار OpenCV، از همان متدهای native آن استفاده شده‌است. در این مثال، روال رخدادگردان تغییر مقادیر tracker یا slider، یک function pointer است که معادل آن در دات نت یک delegate می‌باشد که در پارامتر callback متد ایجاد tracker قابل مشاهده است. هر بار که مقدار tracker تغییر می‌کند، مقدار pos را به callback خود ارسال خواهد کرد. از این مقدار جهت ایجاد شیء ساختاری و همچنین انتخاب بین حالات اتساع و فرسایش، کمک گرفته شده‌است.
کار متد PutText، نوشتن یک متن ساده بر روی پنجره‌ی native مربوط به OpenCV است.

همچنین در ادامه کدهای بکارگیری متد MorphologyEx را که کار ترکیب اتساع و فرسایش را با هم انجام می‌دهد، ذکر شده‌است و نکات بکارگیری آن همانند مثال اول بحث است:
  using (var src = new Mat(@"..\..\Images\cvmorph.Png", LoadMode.AnyDepth | LoadMode.AnyColor))    using (var dst = new Mat())    {        src.CopyTo(dst);        var elementShape = StructuringElementShape.Rect;        var maxIterations = 10;        var openCloseWindow = new Window("Open/Close", image: dst);        var openCloseTrackbar = openCloseWindow.CreateTrackbar(            name: "Iterations", value: 0, max: maxIterations * 2 + 1,            callback: pos =>            {                var n = pos - maxIterations;                var an = n > 0 ? n : -n;                var element = Cv2.GetStructuringElement(                        elementShape,                        new Size(an * 2 + 1, an * 2 + 1),                        new Point(an, an));                if (n < 0)                {                    Cv2.MorphologyEx(src, dst, MorphologyOperation.Open, element);                }                else                {                    Cv2.MorphologyEx(src, dst, MorphologyOperation.Close, element);                }                Cv2.PutText(dst, (n < 0) ?                    string.Format("Open/Erosion followed by Dilation[{0}]", elementShape)                    : string.Format("Close/Dilation followed by Erosion[{0}]", elementShape),                    new Point(10, 15), FontFace.HersheyPlain, 1, Scalar.Black);                openCloseWindow.Image = dst;            });         Cv2.WaitKey();          openCloseWindow.Dispose();    }

کدهای کامل این مثال را از اینجامی‌توانید دریافت کنید.

OpenCVSharp #9

$
0
0
تغییر اندازه، و چرخش تصاویر

در OpenCV با استفاده از مفهومی به نام affine transform، امکان تغییر اندازه و همچنین چرخش تصاویر میسر می‌شود. در اینجا، تصویر در یک ماتریس دو در سه ضرب می‌شود تا انتقالات یاد شده، انجام شوند.
private static void rotateImage(double angle, double scale, Mat src, Mat dst)
{    var imageCenter = new Point2f(src.Cols / 2f, src.Rows / 2f);    var rotationMat = Cv2.GetRotationMatrix2D(imageCenter, angle, scale);    Cv2.WarpAffine(src, dst, rotationMat, src.Size());
}
متد فوق کار چرخش تصویر مبدا (src) را به تصویر مقصد (dst) انجام می‌دهد. این عملیات توسط متد WarpAffine مدیریت شده و مهم‌ترین پارامتر آن، پارامتر سوم آن است که ماتریس تعریف کننده‌ی انتقالات تعریف شده توسط متد GetRotationMatrix2Dاست. در اینجا مرکز مشخص شده، زاویه و مقیاس، نحوه‌ی چرخش را تعریف می‌کنند.
برای مشاهده‌ی بهتر تاثیر پارامترهای مختلف در اینجا، به مثال ذیل دقت کنید:
using OpenCvSharp;
using OpenCvSharp.CPlusPlus;
namespace OpenCVSharpSample09
{    class Program    {        static void Main(string[] args)        {            using (var src = new Mat(@"..\..\Images\Penguin.Png", LoadMode.AnyDepth | LoadMode.AnyColor))            using (var dst = new Mat())            {                src.CopyTo(dst);                using (var window = new Window("Resize/Rotate/Blur",                                                image: dst, flags: WindowMode.AutoSize))                {                    var angle = 0.0;                    var scale = 0.7;                    var angleTrackbar = window.CreateTrackbar(                        name: "Angle", value: 0, max: 180,                        callback: pos =>                        {                            angle = pos;                            rotateImage(angle, scale, src, dst);                            window.Image = dst;                        });                    var scaleTrackbar = window.CreateTrackbar(                        name: "Scale", value: 1, max: 10,                        callback: pos =>                        {                            scale = pos / 10f;                            rotateImage(angle, scale, src, dst);                            window.Image = dst;                        });                     angleTrackbar.Callback.DynamicInvoke(0);                    scaleTrackbar.Callback.DynamicInvoke(1);                    Cv2.WaitKey();                }            }        }        private static void rotateImage(double angle, double scale, Mat src, Mat dst)        {            var imageCenter = new Point2f(src.Cols / 2f, src.Rows / 2f);            var rotationMat = Cv2.GetRotationMatrix2D(imageCenter, angle, scale);            Cv2.WarpAffine(src, dst, rotationMat, src.Size());        }    }
}
با این خروجی:


در این مثال، مانند مطلب قسمت قبل، ابتدا یک پنجره‌ی سازگار با C++ API ایجاد شده و سپس دو tracker به آن اضافه شده‌اند. این trackers کار دریافت ورودی اطلاعات را از کاربر به عهده دارند (دریافت مقادیر زاویه‌ی چرخش و مقیاس) و مقادیر دریافتی از آن‌ها، در نهایت به متد rotateImage ارسال می‌شوند. این متد کار چرخش و تغییر مقیاس تصویر اصلی را انجام داده و نتیجه را به تصویر dst کپی می‌کند. در آخر تصویر dst در پنجره به روز شده و نمایش داده می‌شود.


تغییر اندازه‌ی تصاویر

اگر صرفا قصد تغییر اندازه‌ی تصاویر را دارید (بدون چرخش آن‌ها)، متد ویژه‌ای به نام Resizeبرای این منظور تدارک دیده شده‌است:
var resizeTrackbar = window.CreateTrackbar(    name: "Resize", value: 1, max: 100,    callback: pos =>    {        Cv2.Resize(src, dst,            new Size(src.Width + pos, src.Height + pos),            interpolation: Interpolation.Cubic);        window.Image = dst;    });
در اینجا یک tracker دیگر به پنجره‌ی اصلی اضافه شده و توسط آن کار تعیین تغییر اندازه‌ی تصویر انجام می‌شود. نکته‌ی مهم این متد، امکان تعیین الگوریتم تغییر اندازه است که برای مثال در اینجا از Interpolation.Cubic استفاده شده‌است (احتمالا با این نام‌ها در برنامه‌های معروف کار با تصاویر، مانند فتوشاپ آشنایی دارید).

اگر می‌خواهید مقادیر پارامترهای چرخشی تصویر نیز در اینجا اعمال شوند، می‌توان به نحو ذیل عمل کرد:
var resizeTrackbar = window.CreateTrackbar(    name: "Resize", value: 1, max: 100,    callback: pos =>    {        rotateImage(angle, scale, src, dst);        Cv2.Resize(dst, dst,            new Size(src.Width + pos, src.Height + pos),            interpolation: Interpolation.Cubic);        window.Image = dst;    });
در این کد ابتدا تصویر اصلی چرخش یافته و سپس در متد Resize از این تصویر چرخش یافته، به عنوان src استفاده می‌شود (هر دو پارامتر متد Resize به dst تنظیم شده‌اند).



مات کردن تصاویر

در OpenCV با استفاده از متدهای GaussianBlurو یا medianBlur، می‌توان تصاویر را  مات کرد که نمونه‌ای از آن‌را در ادامه ملاحظه می‌کنید:
var blurTrackbar = window.CreateTrackbar(   name: "Blur", value: 1, max: 100,   callback: pos =>   {       if (pos % 2 == 0) pos++;       rotateImage(angle, scale, src, dst);       Cv2.GaussianBlur(dst, dst, new Size(pos, pos), sigmaX: 0);       window.Image = dst;   });
در اینجا ابتدا تصویر اصلی به متد چرخش تصویر ارسال شده و نتیجه‌ی آن در متد GaussianBlur استفاده خواهد شد. اندازه‌ی مشخص شده‌ی در این متد باید توسط اعداد فرد تعیین گردد. پارامتر sigmaX به معنای standard deviation در جهت x است و اگر صفر تعیین شود، برای محاسبه‌ی آن از پارامتر اندازه‌ی تعیین شده کمک گرفته خواهد شد.



کدهای کامل این مثال را از اینجامی‌توانید دریافت کنید.

OpenCVSharp #10

$
0
0
محاسبه و ترسیم Histogram تصاویر

هیستوگرام یک تصویر، توزیع میزان روشنایی آن تصویر را نمایش می‌دهد و در آن تعداد نقاط قسمت‌های روشن تصویر، ترسیم می‌شوند. محاسبه‌ی هیستوگرام تصاویر در حین دیباگ الگوریتم‌های پردازش تصویر، کاربرد زیادی دارند.
OpenCV به همراه متد توکاری است به نام cv::calcHistکه قادر است هیستوگرام تعدادی آرایه را محاسبه کند و در C++ API آن قرار دارد. البته هدف اصلی این متد، انجام محاسبات مرتبط است و در اینجا قصد داریم این محاسبات را نمایش دهیم.


تغییر میزان روشنایی و وضوح تصاویر در OpenCV

همانطور که عنوان شد، کار هیستوگرام تصاویر، نمایش توزیع میزان روشنایی نقاط و اجزای آن‌ها است. بنابراین می‌توان جهت مشاهده‌ی تغییر هیستوگرام محاسبه شده با تغییر میزان روشنایی و وضوح تصویر، از متد ذیل کمک گرفت:
private static void updateBrightnessContrast(Mat src, Mat modifiedSrc, int brightness, int contrast)
{    brightness = brightness - 100;    contrast = contrast - 100;    double alpha, beta;    if (contrast > 0)    {        double delta = 127f * contrast / 100f;        alpha = 255f / (255f - delta * 2);        beta = alpha * (brightness - delta);    }    else    {        double delta = -128f * contrast / 100;        alpha = (256f - delta * 2) / 255f;        beta = alpha * brightness + delta;    }    src.ConvertTo(modifiedSrc, MatType.CV_8UC3, alpha, beta);
}
در اینجا src تصویر اصلی است. brightness و contrast، مقادیر میزان روشنایی و وضوح دریافتی از کاربر هستند. این مقادیر را می‌توان به متد ConvertTo ارسال کرد تا src را تبدیل به modifiedSrc نماید و وضوح و روشنایی آن‌را تغییر دهد.

پس از اینکه متد تغییر وضوح تصویر اصلی را تهیه کردیم، می‌توان به پنجره‌ی نمایش تصویر اصلی، دو tracker جهت دریافت brightness و contrast اضافه کرد و به این ترتیب امکان نمایش پویای تغییرات را مهیا نمود:
using (var src = new Mat(@"..\..\Images\Penguin.Png", LoadMode.AnyDepth | LoadMode.AnyColor))
{    using (var sourceWindow = new Window("Source", image: src,           flags: WindowMode.AutoSize | WindowMode.FreeRatio))    {        using (var histogramWindow = new Window("Histogram",               flags: WindowMode.AutoSize | WindowMode.FreeRatio))        {            var brightness = 100;            var contrast = 100;            var brightnessTrackbar = sourceWindow.CreateTrackbar(                    name: "Brightness", value: brightness, max: 200,                    callback: pos =>                    {                        brightness = pos;                        updateImageCalculateHistogram(sourceWindow, histogramWindow, src, brightness, contrast);                    });            var contrastTrackbar = sourceWindow.CreateTrackbar(                name: "Contrast", value: contrast, max: 200,                callback: pos =>                {                    contrast = pos;                    updateImageCalculateHistogram(sourceWindow, histogramWindow, src, brightness, contrast);                });            brightnessTrackbar.Callback.DynamicInvoke(brightness);            contrastTrackbar.Callback.DynamicInvoke(contrast);            Cv2.WaitKey();        }    }
}
در اینجا src تصویر اصلی است. پنجره‌ی Source کار نمایش تصویر اصلی را به عهده دارد. همچنین به این پنجره، دو tracker اضافه شده‌اند تا کار دریافت مقادیر روشنایی و وضوح را از کاربر، مدیریت کنند.
پنجره‌ی دومی نیز به نام هیستوگرام در اینجا تعریف شده‌است. در این پنجره قصد داریم هیستوگرام تغییرات پویای تصویر اصلی را نمایش دهیم.



روش محاسبه‌ی هیستوگرام تصاویر و نمایش آن‌ها در OpenCVSharp

کدهای کامل محاسبه‌ی هیستوگرام تصویر اصلی تغییر یافته (modifiedSrc) و سپس نمایش آن‌را در پنجره‌ی histogramWindow، در ادامه ملاحظه می‌کنید:
private static void calculateHistogram1(Window histogramWindow, Mat src, Mat modifiedSrc)
{    const int histogramSize = 64;    int[] dimensions = { histogramSize }; // Histogram size for each dimension    Rangef[] ranges = { new Rangef(0, histogramSize) }; // min/max    using (var histogram = new Mat())    {        Cv2.CalcHist(            images: new[] { modifiedSrc },            channels: new[] { 0 },            mask: null,            hist: histogram,            dims: 1,            histSize: dimensions,            ranges: ranges);        using (var histogramImage = (Mat)(Mat.Ones(rows: src.Rows, cols: src.Cols, type: MatType.CV_8U) * 255))        {            // Scales and draws histogram            Cv2.Normalize(histogram, histogram, 0, histogramImage.Rows, NormType.MinMax);            var binW = Cv.Round((double)histogramImage.Cols / histogramSize);            var color = Scalar.All(100);            for (var i = 0; i < histogramSize; i++)            {                Cv2.Rectangle(histogramImage,                    new Point(i * binW, histogramImage.Rows),                    new Point((i + 1) * binW, histogramImage.Rows - Cv.Round(histogram.Get<float>(i))),                    color,                    -1);            }            histogramWindow.Image = histogramImage;        }    }
}
معادل متد cv::calcHist، متد Cv2.CalcHist در OpenCVSharp است. این متد آرایه‌ای از تصاویر را قبول می‌کند که در اینجا تنها قصد داریم با یک تصویر کار کنیم. به همین جهت آرایه‌های images، اندازه‌های آن‌ها و بازه‌های min/max این تصاویر تنها یک عضو دارند. خروجی این متد پارامتر hist آن است که توسط یک new Mat تامین شده‌است. مقدار dims به یک تنظیم شده‌است؛ زیرا در اینجا تنها قصد داریم شدت نقاط را اندازه گیری کنیم. پارامتر ranges مشخص می‌کند که مقادیر اندازه گیری شده باید در چه بازه‌ایی جمع آوری شوند.
پس از محاسبه‌ی هیستوگرام، یک تصویر خالی پر شده‌ی با عدد یک را توسط متد Mat.Ones ایجاد می‌کنیم. این تصویر به عنوان منبع تصویر هیستوگرام نمایش داده شده، مورد استفاده قرار می‌گیرد. سپس نیاز است اطلاعات محاسبه شده، در مقیاسی قرار گیرند که قابل نمایش باشد. به همین جهت با استفاده از متد Normalize، آن‌ها را در مقیاس و بازه‌ی ارتفاع تصویر، تغییر اندازه خواهیم داد. سپس به کمک متد مستطیل، خروجی آرایه هیستوگرام را در صفحه، با رنگ خاکستری مشخص شده توسط متد Scalar.All ترسیم خواهیم کرد.


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


کدهای کامل این مثال را از اینجامی‌توانید دریافت کنید.

OpenCVSharp #11

$
0
0
خوشه بندی تصویر به کمک الگوریتم K-Means توسط OpenCV

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

ارتباط الگوریتم k-means clustering با مباحث پردازش تصویر، در پیش پردازش‌های لازمی است که جهت سرفصل‌هایی مانند تشخیص اشیاء، آنالیز صحنه، ردیابی و امثال آن ضروری هستند. از الگوریتم خوشه بندی k-means عموما جهت مفهومی به نام Color Quantization یا کاهش تعداد رنگ‌های تصویر استفاده می‌شود. یکی از مهم‌ترین مزایای این کار، کاهش فشار حافظه و همچنین بالا رفتن سرعت پردازش‌های بعدی بر روی تصویر است. همچنین گاهی از اوقات برای چاپ پوسترها نیاز است تعداد رنگ‌های تصویر را کاهش داد که در اینجا نیز می‌توان از این الگوریتم استفاده کرد.


پیاده سازی الگوریتم خوشه بندی K-means

در ادامه کدهای بکارگیری متد kmeans کتابخانه‌ی OpenCV را به کمک OpenCVSharp مشاهده می‌کنید:
var src = new Mat(@"..\..\Images\fruits.jpg", LoadMode.AnyDepth | LoadMode.AnyColor);
Cv2.ImShow("Source", src);
Cv2.WaitKey(1); // do events
Cv2.Blur(src, src, new Size(15, 15));
Cv2.ImShow("Blurred Image", src);
Cv2.WaitKey(1); // do events
// Converts the MxNx3 image into a Kx3 matrix where K=MxN and
// each row is now a vector in the 3-D space of RGB.
// change to a Mx3 column vector (M is number of pixels in image)
var columnVector = src.Reshape(cn: 3, rows: src.Rows * src.Cols);
// convert to floating point, it is a requirement of the k-means method of OpenCV.
var samples = new Mat();
columnVector.ConvertTo(samples, MatType.CV_32FC3);
for (var clustersCount = 2; clustersCount <= 8; clustersCount += 2)
{    var bestLabels = new Mat();    var centers = new Mat();    Cv2.Kmeans(        data: samples,        k: clustersCount,        bestLabels: bestLabels,        criteria:            new TermCriteria(type: CriteriaType.Epsilon | CriteriaType.Iteration, maxCount: 10, epsilon: 1.0),        attempts: 3,        flags: KMeansFlag.PpCenters,        centers: centers);    var clusteredImage = new Mat(src.Rows, src.Cols, src.Type());    for (var size = 0; size < src.Cols * src.Rows; size++)    {        var clusterIndex = bestLabels.At<int>(0, size);        var newPixel = new Vec3b        {            Item0 = (byte)(centers.At<float>(clusterIndex, 0)), // B            Item1 = (byte)(centers.At<float>(clusterIndex, 1)), // G            Item2 = (byte)(centers.At<float>(clusterIndex, 2)) // R        };        clusteredImage.Set(size / src.Cols, size % src.Cols, newPixel);    }    Cv2.ImShow(string.Format("Clustered Image [k:{0}]", clustersCount), clusteredImage);    Cv2.WaitKey(1); // do events
}
Cv2.WaitKey();
Cv2.DestroyAllWindows();
با این خروجی


توضیحات

- ابتدا تصویر اصلی برنامه بارگذاری می‌شود و در یک پنجره نمایش داده خواهد شد. در اینجا متد Cv2.WaitKey را با پارامتر یک، مشاهده می‌کنید. این فراخوانی ویژه‌، شبیه به متد do events در برنامه‌های WinForms است. اگر فراخوانی نشود، تمام تصاویر پنجره‌های مختلف برنامه تا زمان پایان پردازش‌های مختلف برنامه، نمایش داده نخواهند شد و تا آن زمان صرفا یک یا چند پنجره‌ی خاکستری رنگ را مشاهده خواهید کرد.
- در ادامه متد Blur بر روی این تصویر فراخوانی شده‌است تا مقداری تصویر را مات کند. هدف از بکارگیری این متد در این مثال، برجسته کردن خوشه بندی گروه‌های رنگی مختلف در تصویر اصلی است.
- سپس متد Reshape بر روی ماتریس تصویر اصلی بارگذاری شده فراخوانی می‌شود.
هدف از بکارگیری الگوریتم k-means، انتساب برچسب‌هایی به هر نقطه‌ی RGB تصویر است. در اینجا هر نقطه به شکل یک بردار در فضای سه بعدی مشاهده می‌شود. سپس سعی خواهد شد تا این MxN بردار، به k قسمت تقسیم شوند.
متد Reshape تصویر اصلی MxNx3 را به یک ماتریس Kx3 تبدیل می‌کند که در آن K=MxN است و اکنون هر ردیف آن برداری است در فضای سه بعدی RGB.
- پس از آن توسط متد ConvertTo، نوع داده‌های این ماتریس جدید به float تبدیل می‌شوند تا در متد kmeans قابل استفاده شوند.
- در ادامه یک حلقه را مشاهده می‌کنید که عملیات کاهش رنگ‌های تصویر و خوشه بندی آن‌ها را 4 بار با مقادیر مختلف clustersCount انجام می‌دهد.
- در متد kmeans، پارامتر data یک ماتریس float است که هر نمونه‌ی آن در یک ردیف قرار گرفته‌است. K بیانگر تعداد خوشه‌ها، جهت تقسیم داده‌ها است.
در اینجا پارامترهای labels و centers خروجی‌های متد هستند. برچسب‌ها بیانگر اندیس‌های هر خوشه به ازای هر نمونه هستند. Centers ماتریس مراکز هر خوشه است و دارای یک ردیف به ازای هر خوشه است.
پارامتر criteria آن مشخص می‌کند که الگوریتم چگونه باید خاتمه یابد که در آن حداکثر تعداد بررسی‌ها و یا دقت مورد نظر مشخص می‌شوند.
پارامتر attempts مشخص می‌کند که این الگوریتم چندبار باید اجرا شود تا بهترین میزان فشردگی و کاهش رنگ حاصل شود.
- پس از پایان عملیات k-means نیاز است تا اطلاعات آن مجددا به شکل ماتریسی هم اندازه‌ی تصویر اصلی برگردانده شود تا بتوان آن‌را نمایش داد. در اینجا بهتر می‌توان نحوه‌ی عملکرد متد k-means را درک کرد. حلقه‌ی تشکیل شده به اندازه‌ی تمام نقاط طول و عرض تصویر اصلی است. به ازای هر نقطه، توسط الگوریتم k-means یک برچسب تشکیل شده (bestLabels) که مشخص می‌کند این نقطه متعلق به کدام خوشه و cluster رنگ‌های کاهش یافته است. سپس بر اساس این اندیس می‌توان رنگ این نقطه را از خروجی centers یافته و در یک تصویر جدید نمایش داد.



کدهای کامل این مثال را از اینجامی‌توانید دریافت کنید.

OpenCVSharp #12

$
0
0
قطعه بندی (segmentation) تصویر با استفاده از الگوریتم watershed

در تصویر ذیل، تصویر یک راه‌رو را مشاهده می‌کنید که توسط ماوس قطعه بندی شده‌است (تصویر اصلی یا سمت چپ). تصویر سمت راست، نسخه‌ی قطعه بندی شده‌ی این تصویر به کمک الگوریتم watershed است.

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


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

در اینجا کدهای آغازین مثال بحث جاری را ملاحظه می‌کنید:
var src = new Mat(@"..\..\Images\corridor.jpg", LoadMode.AnyDepth | LoadMode.AnyColor);
var srcCopy = new Mat();
src.CopyTo(srcCopy);
var markerMask = new Mat();
Cv2.CvtColor(srcCopy, markerMask, ColorConversion.BgrToGray);
var imgGray = new Mat();
Cv2.CvtColor(markerMask, imgGray, ColorConversion.GrayToBgr);
markerMask = new Mat(markerMask.Size(), markerMask.Type(), s: Scalar.All(0));
var sourceWindow = new Window("Source (Select areas by mouse and then press space)")
{    Image = srcCopy
};
var previousPoint = new Point(-1, -1);
sourceWindow.OnMouseCallback += (@event, x, y, flags) =>
{    if (x < 0 || x >= srcCopy.Cols || y < 0 || y >= srcCopy.Rows)    {        return;    }    if (@event == MouseEvent.LButtonUp || !flags.HasFlag(MouseEvent.FlagLButton))    {        previousPoint = new Point(-1, -1);    }    else if (@event == MouseEvent.LButtonDown)    {        previousPoint = new Point(x, y);    }    else if (@event == MouseEvent.MouseMove && flags.HasFlag(MouseEvent.FlagLButton))    {        var pt = new Point(x, y);        if (previousPoint.X < 0)        {            previousPoint = pt;        }        Cv2.Line(img: markerMask, pt1: previousPoint, pt2: pt, color: Scalar.All(255), thickness: 5);        Cv2.Line(img: srcCopy, pt1: previousPoint, pt2: pt, color: Scalar.All(255), thickness: 5);        previousPoint = pt;        sourceWindow.Image = srcCopy;    }
};
ابتدا تصویر راه‌رو بارگذاری شده‌است. سپس یک نسخه‌ی سیاه و سفید تک کاناله به نام markerMask از آن استخراج می‌شود. از آن برای ترسیم خطوط انتخاب نواحی مختلف تصویر به کمک ماوس استفاده می‌شود. به علاوه متد FindContours که در ادامه معرفی خواهد شد، نیاز به یک تصویر 8 بیتی تک کاناله دارد (به هر یک از اجزای RGB یک کانال گفته می‌شود).
همچنین این نسخه‌ی سیاه و سفید تک کاناله به یک تصویر سه کاناله برای نمایش رنگ‌های قسمت‌های مختلف قطعه بندی شده، تبدیل می‌شود.
سپس پنجره‌ی نمایش تصویر اصلی برنامه ایجاد شده و در اینجا روال رخدادگردان OnMouseCallback آن به صورت inline مقدار دهی شده‌است. در این روال می‌توان مدیریت ماوس را به عهده گرفت و کار نمایش خطوط مختلف را با فشرده شدن و سپس رها شدن کلیک سمت چپ ماوس انجام داد.
خط ترسیم شده بر روی دو تصویر از نوع Mat نمایش داده می‌شود. تصویر srcCopy، همان تصویر نمایش داده شده‌ی در پنجره‌ی اصلی است و تصویر markerMask، بیشتر جنبه‌ی محاسباتی دارد و در متدهای بعدی OpenCV استفاده خواهد شد.


تشخیص کانتورها (Contours) در تصویر

پس از ترسیم نواحی مورد نظر توسط ماوس، یک سری خطوط به هم پیوسته در شکل قابل مشاهده هستند. می‌خواهیم این خطوط را تشخیص داده و سپس از آن‌ها جهت محاسبات قطعه بندی تصویر استفاده کنیم. تشخیص این خطوط متصل، توسط متدی به نام FindContoursانجام می‌شود. کانتورها، قسمت‌های خارجی اجزای متصل به هم هستند.
Point[][] contours; //vector<vector<Point>> contours;
HiearchyIndex[] hierarchyIndexes; //vector<Vec4i> hierarchy;
Cv2.FindContours(    markerMask,    out contours,    out hierarchyIndexes,    mode: ContourRetrieval.CComp,    method: ContourChain.ApproxSimple);
متد FindContours همان تصویر markerMask را که توسط ماوس، قسمت‌های مختلف تصویر را علامتگذاری کرده‌است، دریافت می‌کند. سپس کانتورهای آن را استخراج خواهد کرد. کانتورها در مثال‌های اصلی OpenCVبا verctor مشخص شده‌اند. در اینجا (در کتابخانه‌ی OpenCVSharp) آن‌ها را توسط یک آرایه‌ی دو بعدی از نوع Point مشاهده می‌کنید یا شبیه به لیستی از آرایه‌ی نقاط کانتورهای مختلف تشخیص داده شده (هر کانتور، آرایه‌ی از نقاط است). از hierarchyIndexes جهت یافتن و ترسیم این کانتورها در متد DrawContours استفاده می‌شود.
متد FindContours یک تصویر 8 بیتی تک کاناله را دریافت می‌کند. اگر mode آن CCOMP یا FLOODFILL تعریف شود، امکان دریافت یک تصویر 32 بیتی را نیز خواهد داشت.
پارامتر hierarchy آن یک پارامتر اختیاری است که بیانگر اطلاعات topology تصویر است.
توسط پارامتر Mode، نحوه‌ی استخراج کانتور مشخص می‌شود. اگر به external تنظیم شود، تنها کانتورهای خارجی‌ترین قسمت‌ها را تشخیص می‌دهد. اگر مساوی list قرار گیرد، تمام کانتورها را بدون ارتباطی با یکدیگر و بدون تشکیل hierarchy استخراج می‌کند. حالت ccomp تمام کانتورها را استخراج کرده و یک درخت دو سطحی از آن‌ها را تشکیل می‌دهد. در سطح بالایی مرزهای خارجی اجزاء وجود دارند و در سطح دوم مرزهای حفره‌ها مشخص شده‌اند. حالت و مقدار tree به معنای تشکیل یک درخت کامل از کانتورهای یافت شده‌است.
پارامتر method اگر به none تنظیم شود، تمام نقاط کانتور ذخیره خواهند شد و اگر به simple تنظیم شود، قطعه‌های افقی، عمودی و قطری، فشرده شده و تنها نقاط نهایی آن‌ها ذخیره می‌شوند. برای مثال در این حالت یک کانتور مستطیلی، تنها با 4 نقطه ذخیره می‌شود.


ترسیم کانتورهای تشخیص داده شده بر روی تصویر


می‌توان به کمک متد DrawContours، مرزهای کانتورهای یافت شده را ترسیم کرد:
var markers = new Mat(markerMask.Size(), MatType.CV_32S, s: Scalar.All(0));
var componentCount = 0;
var contourIndex = 0;
while ((contourIndex >= 0))
{    Cv2.DrawContours(        markers,        contours,        contourIndex,        color: Scalar.All(componentCount + 1),        thickness: -1,        lineType: LineType.Link8,        hierarchy: hierarchyIndexes,        maxLevel: int.MaxValue);    componentCount++;    contourIndex = hierarchyIndexes[contourIndex].Next;
}
پارامتر اول آن تصویری است که قرار است ترسیمات بر روی آن انجام شوند. پارامتر کانتور، آرایه‌ای است از کانتورهای یافت شده‌ی در قسمت قبل. پارامتر ایندکس مشخص می‌کند که اکنون کدام کانتور باید رسم شود. برای یافتن کانتور بعدی باید از hierarchyIndexes یافت شده‌ی توسط متد FindContours استفاده کرد. خاصیت Next آن، بیانگر ایندکس کانتور بعدی است و اگر مساوی منهای یک شد، کار متوقف می‌شود. مقدار maxLevel مشخص می‌کند که بر اساس پارامتر hierarchyIndexes، چند سطح از کانتورهای به هم مرتبط باید ترسیم شوند. در اینجا چون به حداکثر مقدار Int32 تنظیم شده‌است، تمام این سطوح ترسیم خواهند شد. اگر پارامتر ضخامت به یک عدد منفی تنظیم شود، سطوح داخلی کانتور ترسیم و پر می‌شوند.



اعمال الگوریتم watershed

در مرحله‌ی آخر، تصویر کانتورهای ترسیم شده را به متد Watershed ارسال می‌کنیم. پارامتر اول آن تصویر اصلی است و پارامتر دوم، یک پارامتر ورودی و خروجی محسوب می‌شود و کار قطعه بندی تصویر بر روی آن انجام خواهد شد.
کار الگوریتم watershed، ایزوله سازی اشیاء موجود در تصویر از پس زمینه‌ی آن‌ها است. این الگوریتم، یک تصویر سیاه و سفید را دریافت می‌کند؛ به همراه یک تصویر ویژه به نام marker. تصویر marker کارش مشخص سازی اشیاء، از پس زمینه‌ی آن‌ها است که در اینجا توسط ماوس ترسیم و سپس به کمک یافتن کانتورها و ترسیم آ‌ن‌ها بهینه سازی شده‌است.
var rnd = new Random();
var colorTable = new List<Vec3b>();
for (var i = 0; i < componentCount; i++)
{    var b = rnd.Next(0, 255); //Cv2.TheRNG().Uniform(0, 255);    var g = rnd.Next(0, 255); //Cv2.TheRNG().Uniform(0, 255);    var r = rnd.Next(0, 255); //Cv2.TheRNG().Uniform(0, 255);    colorTable.Add(new Vec3b((byte)b, (byte)g, (byte)r));
}
Cv2.Watershed(src, markers);
var watershedImage = new Mat(markers.Size(), MatType.CV_8UC3);
// paint the watershed image
for (var i = 0; i < markers.Rows; i++)
{    for (var j = 0; j < markers.Cols; j++)    {        var idx = markers.At<int>(i, j);        if (idx == -1)        {            watershedImage.Set(i, j, new Vec3b(255, 255, 255));        }        else if (idx <= 0 || idx > componentCount)        {            watershedImage.Set(i, j, new Vec3b(0, 0, 0));        }        else        {            watershedImage.Set(i, j, colorTable[idx - 1]);        }    }
}
watershedImage = watershedImage * 0.5 + imgGray * 0.5;
Cv2.ImShow("Watershed Transform", watershedImage);
Cv2.WaitKey(1); //do events
متد Cv2.TheRNG یک تولید کننده‌ی اعداد تصادفی توسط OpenCV است و متد Uniform آن شبیه به متد Next کلاس Random دات نت عمل می‌کند. به نظر این کلاس تولید اعداد تصادفی، آنچنان هم تصادفی عمل نمی‌کند. به همین جهت از کلاس Random دات نت استفاده شد. در اینجا به ازای تعداد کانتورهای ترسیم شده، یک رنگ تصادفی تولید شده‌است.
پس از اعمال متد Watershed، هر نقطه‌ی تصویر marker مشخص می‌کند که متعلق به کدام قطعه‌ی تشخیص داده شده‌است. سپس به این نقطه، رنگ آن قطعه را نسبت داده و آن‌را در تصویر جدیدی ترسیم می‌کنیم.
در آخر، پس زمینه، با نواحی تشخیص داده ترکیب شده‌اند (watershedImage * 0.5 + imgGray * 0.5) تا تصویر ابتدای بحث حاصل شود. اگر این ترکیب صورت نگیرد، چنین تصویری حاصل خواهد شد:




کدهای کامل این مثال را از اینجامی‌توانید دریافت کنید.

OpenCVSharp #13

$
0
0
تشخیص قسمت‌های مشابه تصاویر در OpenCV

در شکل زیر، دو تصویر سمت چپ و راست، اندکی با هم تفاوت دارند و در تصویر سوم، نقاط مشابه یافت شده‌ی توسط OpenCV ترسیم شده‌اند:


کدهای مثال فوق را در ذیل مشاهده می‌کنید:
var img1 = new Mat(@"..\..\Images\left.png", LoadMode.GrayScale);
Cv2.ImShow("Left", img1);
Cv2.WaitKey(1); // do events
var img2 = new Mat(@"..\..\Images\right.png", LoadMode.GrayScale);
Cv2.ImShow("Right", img2);
Cv2.WaitKey(1); // do events
// detecting keypoints
// FastFeatureDetector, StarDetector, SIFT, SURF, ORB, BRISK, MSER, GFTTDetector, DenseFeatureDetector, SimpleBlobDetector
// SURF = Speeded Up Robust Features
var detector = new SURF(hessianThreshold: 400); //SurfFeatureDetector
var keypoints1 = detector.Detect(img1);
var keypoints2 = detector.Detect(img2);
// computing descriptors, BRIEF, FREAK
// BRIEF = Binary Robust Independent Elementary Features
var extractor = new BriefDescriptorExtractor();
var descriptors1 = new Mat();
var descriptors2 = new Mat();
extractor.Compute(img1, ref keypoints1, descriptors1);
extractor.Compute(img2, ref keypoints2, descriptors2);
// matching descriptors
var matcher = new BFMatcher();
var matches = matcher.Match(descriptors1, descriptors2);
// drawing the results
var imgMatches = new Mat();
Cv2.DrawMatches(img1, keypoints1, img2, keypoints2, matches, imgMatches);
Cv2.ImShow("Matches", imgMatches);
Cv2.WaitKey(1); // do events
Cv2.WaitKey(0);
Cv2.DestroyAllWindows();
img1.Dispose();
img2.Dispose();
در ابتدا نیاز به یک تشخیص دهنده یا Detector داریم. در OpenCVSharp، الگوریتم‌ها و کلاس‌های FastFeatureDetector, StarDetector, SIFT, SURF, ORB, BRISK, MSER, GFTTDetector, DenseFeatureDetector, SimpleBlobDetector برای اینکار قابل استفاده هستند. برای مثال در اینجا از الگوریتم SURF آن استفاده شده‌است.
کار این تشخیص دهنده، تشخیص نقاط کلیدی تصاویر است. برای مثال تشخیص گوشه‌ها، لبه‌ها و غیره.
سپس اطلاعات نواحی اطراف هر نقطه‌ی کلیدی را تحت عنوان descriptors استخراج می‌کنیم.
بعد از محاسبه‌ی نقاط کلیدی هر تصویر، اینبار نیاز است این نقاط را بین دو تصویر با هم مقایسه کرد و مشابه‌ها را یافت. برای مثال الگوریتم BFMatcher یک Brute force matcher است که بر اساس اطلاعات نواحی اطراف هر نقطه‌ی کلیدی، سعی در یافتن نقاط مشابه می‌کند.
پس از یافتن نقاط مشابه، نیاز است بر اساس آن‌ها نگاشتی بین دو تصویر صورت گیرد و مشابه‌ها ترسیم شوند. متد DrawMatches این‌کار را انجام می‌شود.

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


کدهای کامل این مثال را از اینجامی‌توانید دریافت کنید.

OpenCVSharp #14

$
0
0
تشخیص BLOBs در تصویر به کمک OpenCV

  BLOB یا Binary Large OBject به معنای گروهی از نقاط به هم پیوسته‌ی در یک تصویر باینری هستند. Large در اینجا به این معنا است که اشیایی با اندازه‌هایی مشخص، مدنظر هستند و اشیاء کوچک، به عنوان نویز درنظر گرفته خواهند شد و پردازش نمی‌شوند.
برای نمونه در تصویر ذیل، تصویر سمت چپ، تصویر اصلی است و تصویر سمت راست، نمونه‌ی باینری سیاه و سفید آن. سپس عملیات تشخیص Blobs بر روی تصویر اصلی انجام شده و نتیجه‌ی نهایی که مشخص کننده‌ی اشیاء تشخیص داده شده‌است، با دوایری قرمز در تصویر سمت راست، مشخص هستند.



معرفی کلاس SimpleBlobDetector

در کدهای ذیل، نحوه‌ی کار با کلاس SimpleBlobDetectorرا مشاهده می‌کنید. این کلاس کار تشخیص Blobs را در تصویر اصلی انجام می‌دهد:
var srcImage = new Mat(@"..\..\Images\cvlbl.png");
Cv2.ImShow("Source", srcImage);
Cv2.WaitKey(1); // do events
var binaryImage = new Mat(srcImage.Size(), MatType.CV_8UC1);
Cv2.CvtColor(srcImage, binaryImage, ColorConversion.BgrToGray);

Cv2.Threshold(binaryImage, binaryImage, thresh: 100, maxval: 255, type: ThresholdType.Binary);

 
var detectorParams = new SimpleBlobDetector.Params
{    //MinDistBetweenBlobs = 10, // 10 pixels between blobs    //MinRepeatability = 1,    //MinThreshold = 100,    //MaxThreshold = 255,    //ThresholdStep = 5,    FilterByArea = false,    //FilterByArea = true,    //MinArea = 0.001f, // 10 pixels squared    //MaxArea = 500,    FilterByCircularity = false,    //FilterByCircularity = true,    //MinCircularity = 0.001f,    FilterByConvexity = false,    //FilterByConvexity = true,    //MinConvexity = 0.001f,    //MaxConvexity = 10,    FilterByInertia = false,    //FilterByInertia = true,    //MinInertiaRatio = 0.001f,    FilterByColor = false    //FilterByColor = true,    //BlobColor = 255 // to extract light blobs
};
var simpleBlobDetector = new SimpleBlobDetector(detectorParams);
var keyPoints = simpleBlobDetector.Detect(binaryImage);
Console.WriteLine("keyPoints: {0}", keyPoints.Length);
foreach (var keyPoint in keyPoints)
{    Console.WriteLine("X: {0}, Y: {1}", keyPoint.Pt.X, keyPoint.Pt.Y);
}
var imageWithKeyPoints = new Mat();
Cv2.DrawKeypoints(        image: binaryImage,        keypoints: keyPoints,        outImage: imageWithKeyPoints,        color: Scalar.FromRgb(255, 0, 0),        flags: DrawMatchesFlags.DrawRichKeypoints);
Cv2.ImShow("Key Points", imageWithKeyPoints);
Cv2.WaitKey(1); // do events
Cv2.WaitKey(0);
Cv2.DestroyAllWindows();
srcImage.Dispose();
imageWithKeyPoints.Dispose();
توضیحات

در اینجا ابتدا تصویر منبع به صورتی متداول باگذاری شده‌است. سپس توسط متد CvtColor به نمونه‌ای سیاه و سفید و به کمک متد Threshold به یک تصویر باینری تبدیل شده‌است.
اگر به تصویر ابتدای بحث دقت کنید، اشیاء موجود در منبع مفروض، رنگ‌های متفاوتی دارند؛ اما در تصویر نهایی تولید شده، تمام آن‌ها تبدیل به اشیایی سفید رنگ شده‌اند. این کار را متد Cv2.Thresholdانجام داده‌است، تا کلاس SimpleBlobDetector قابلیت تشخیص بهتری را پیدا کند. تشخیص اشیایی یک دست، بسیار ساده‌تر هستند از تشخیص اشیایی با رنگ‌ها و شدت نور متفاوت.
اگر بخواهیم الگوریتم Threshold را بیان کنیم به pseudo code زیر خواهیم رسید:
 if src(x,y) > thresh
    dst(x,y) = maxValue
else
    dst(x,y) = 0
در اینجا رنگ نقطه‌ی x,y با مقدار thresh (پارامتر سوم متد Cv2.Threshold) مقایسه می‌شود. اگر مقدار آن بیشتر بود، با پارامتر چهارم جایگزین خواهد شد. مقدار 255 در اینجا روشن‌ترین حالت ممکن است؛ اگر خیر با صفر یا تیره‌ترین رنگ، جایگزین می‌شود. به همین دلیل است که در تصویر سمت راست، تمام اشیاء را با روشن‌ترین رنگ ممکن مشاهده می‌کنید.

سپس پارامتر اصلی سازنده‌ی کلاس SimpleBlobDetector مقدار دهی شده‌است. این تنظیمات در تشخیص اشیاء موجود در تصویر بسیار مهم هستند. برای درک بهتر واژه‌هایی مانند Circularity، Convexity، Inertia و امثال آن، به تصویر ذیل دقت کنید:



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

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



کدهای کامل این مثال را از اینجامی‌توانید دریافت کنید.

OpenCVSharp #15

$
0
0
تشخیص چهره به کمک OpenCV

OpenCV به کمک الگوریتم‌های machine learning (در اینجا Haar feature-based cascade classifiers) و داد‌ه‌های مرتبط با آن‌ها، قادر است اشیاء پیچیده‌ای را در تصاویر پیدا کند. برای پیگیری مثال بحث جاری نیاز است کتابخانه‌ی اصلی OpenCV را دریافت کنید؛ از این جهت که به فایل‌های XML موجود در پوشه‌ی opencv\sources\data\haarcascades آن نیاز داریم. در اینجا از دو فایل haarcascade_eye_tree_eyeglasses.xml و haarcascade_frontalface_alt.xml آن استفاده خواهیم کرد (این دوفایل جهت سهولت کار، به همراه مثال این بحث نیز ارائه شده‌اند).
فایل haarcascade_frontalface_alt.xml اصطلاحا trained data مخصوص یافتن چهره‌ی انسان در تصاویر است و فایل haarcascade_eye_tree_eyeglasses.xml کمک می‌کند تا بتوان در چهره‌ی یافت شده، چشمان شخص را نیز با دقت بالایی تشخیص داد؛ چیزی همانند تصویر ذیل:



مراحل تشخیص چهره توسط OpenCVSharp

ابتدا همانند سایر مثال‌های OpenCV، تصویر مدنظر را به کمک کلاس Mat بارگذاری می‌کنیم:
var srcImage = new Mat(@"..\..\Images\Test.jpg");
Cv2.ImShow("Source", srcImage);
Cv2.WaitKey(1); // do events
var grayImage = new Mat();
Cv2.CvtColor(srcImage, grayImage, ColorConversion.BgrToGray);
Cv2.EqualizeHist(grayImage, grayImage);
همچنین در اینجا جهت بالا رفتن سرعت کار و بهبود دقت تشخیص چهره، این تصویر اصلی به یک تصویر سیاه و سفید تبدیل شده‌است و سپس متد Cv2.EqualizeHist بر روی آن فراخوانی گشته‌است. این متد وضوح تصویر را جهت یافتن الگوها بهبود می‌بخشد.
سپس فایل xml یاد شده‌ی در ابتدای بحث را توسط کلاس CascadeClassifier بارگذاری می‌کنیم:
var cascade = new CascadeClassifier(@"..\..\Data\haarcascade_frontalface_alt.xml");
var nestedCascade = new CascadeClassifier(@"..\..\Data\haarcascade_eye_tree_eyeglasses.xml");
var faces = cascade.DetectMultiScale(    image: grayImage,    scaleFactor: 1.1,    minNeighbors: 2,    flags: HaarDetectionType.Zero | HaarDetectionType.ScaleImage,    minSize: new Size(30, 30)    );
Console.WriteLine("Detected faces: {0}", faces.Length);
پس از بارگذاری فایل‌های XML اطلاعات نحوه‌ی تشخیص چهره و اعضای آن، با فراخوانی متد DetectMultiScale، کار تشخیص چهره و استخراج آن از grayImage انجام خواهد شد. در اینجا minSize، اندازه‌ی حداقل چهره‌ی مدنظر است که قرار هست تشخیص داده شود. نواحی کوچکتر از این اندازه، به عنوان نویز در نظر گرفته خواهند شد و پردازش نمی‌شوند.
خروجی این متد، مستطیل‌ها و نواحی یافت شده‌ی مرتبط با چهره‌های موجود در تصویر هستند. اکنون می‌توان حلقه‌ای را تشکیل داد و این نواحی را برای مثال با مستطیل‌های رنگی، متمایز کرد:
var rnd = new Random();
var count = 1;
foreach (var faceRect in faces)
{    var detectedFaceImage = new Mat(srcImage, faceRect);    Cv2.ImShow(string.Format("Face {0}", count), detectedFaceImage);    Cv2.WaitKey(1); // do events    var color = Scalar.FromRgb(rnd.Next(0, 255), rnd.Next(0, 255), rnd.Next(0, 255));    Cv2.Rectangle(srcImage, faceRect, color, 3);    count++;
}
Cv2.ImShow("Haar Detection", srcImage);
Cv2.WaitKey(1); // do events
در اینجا علاوه بر رسم یک مستطیل، به دور ناحیه‌ی تشخیص داده شده، نحوه‌ی استخراج تصویر آن ناحیه را هم در سطر var detectedFaceImage مشاهده می‌کنید.

همچنین اگر علاقمند باشیم تا در این ناحیه‌ی یافت شده، چشمان شخص را نیز استخراج کنیم، می‌توان به نحو ذیل عمل کرد:
var rnd = new Random();
var count = 1;
foreach (var faceRect in faces)
{    var detectedFaceImage = new Mat(srcImage, faceRect);    Cv2.ImShow(string.Format("Face {0}", count), detectedFaceImage);    Cv2.WaitKey(1); // do events    var color = Scalar.FromRgb(rnd.Next(0, 255), rnd.Next(0, 255), rnd.Next(0, 255));    Cv2.Rectangle(srcImage, faceRect, color, 3);    var detectedFaceGrayImage = new Mat();    Cv2.CvtColor(detectedFaceImage, detectedFaceGrayImage, ColorConversion.BgrToGray);    var nestedObjects = nestedCascade.DetectMultiScale(        image: detectedFaceGrayImage,        scaleFactor: 1.1,        minNeighbors: 2,        flags: HaarDetectionType.Zero | HaarDetectionType.ScaleImage,        minSize: new Size(30, 30)        );    Console.WriteLine("Nested Objects[{0}]: {1}", count, nestedObjects.Length);    foreach (var nestedObject in nestedObjects)    {        var center = new Point        {            X = Cv.Round(nestedObject.X + nestedObject.Width * 0.5) + faceRect.Left,            Y = Cv.Round(nestedObject.Y + nestedObject.Height * 0.5) + faceRect.Top        };        var radius = Cv.Round((nestedObject.Width + nestedObject.Height) * 0.25);        Cv2.Circle(srcImage, center, radius, color, thickness: 3);    }    count++;
}
Cv2.ImShow("Haar Detection", srcImage);
Cv2.WaitKey(1); // do events
کدهای ابتدایی آن همانند توضیحات قبل است. تنها تفاوت آن، استفاده از nestedCascade بارگذاری شده‌ی در ابتدای بحث می‌باشد. این nestedCascade حاوی trained data استخراج چشمان اشخاص، از تصاویر است. پارامتر ورودی آن‌را نیز تصویر سیاه و سفید ناحیه‌ی چهره‌ی یافت شده‌، قرار داده‌ایم تا سرعت تشخیص چشمان شخص، افزایش یابد.


کدهای کامل این مثال را از اینجامی‌توانید دریافت کنید.

OpenCVSharp #16

$
0
0
در قسمت قبلبا نحوه‌ی استفاده از یک trained data از پیش آماده شده‌ی تشخیص چهره‌، آشنا شدیم. در این قسمت قصد داریم با نحوه‌ی تولید این فایل‌های XML آشنا شویم و یک تشخیص دهنده‌ی سفارشی را طراحی کنیم.


طراحی classifier سفارشی تشخیص خودروها

برای طراحی یک تشخیص دهنده‌ی سفارشی مبتنی بر الگوریتم‌های Machine learning، نیاز به تعداد زیادی تصویر داریم. در اینجا از بانک تصاویر خودروهای «UIUC Image Database for Car Detection» استفاده خواهیم کرد. در  این بسته، یک سری تصویر positive و negative را می‌توان ملاحظه کرد. تصاویر مثبت، تصاویر انواع و اقسام خودروها هستند (550 عدد) و تصاویر منفی، تصاویر غیر خودرویی (500 عدد)؛ یا به عبارتی، هر تصویری، منهای تصاویر خودرو می‌تواند تصویر منفی باشد.


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

در ادامه یک فایل متنی را به نام carImages.txt ایجاد می‌کنیم. هر سطر این فایل چنین فرمتی را خواهد داشت:
 pos/pos-177.pgm 1 0 0 100 40
ابتدا مسیر تصویر مشخص می‌شود. سپس عدد 1 به این معنا است که در این تصویر فقط یک عدد خودرو وجود دارد. 4 عدد بعدی، ابعاد مستطیلی تصویر هستند.
در ادامه فایل متنی دیگری را به نام negativeImages.txt جهت درج اطلاعات تصاویر منفی، ایجاد می‌کنیم. اینبار هر سطر این فایل تنها حاوی مسیر تصویر مدنظر است:
 neg/neg-274.pgm
این دو فایل را می‌توان با استفاده از دو متد ذیل، به سرعت تولید کرد:
private static void createCarImagesFile()
{    var sb = new StringBuilder();    foreach (var file in new DirectoryInfo(@"..\..\CarData\CarData\TrainImages").GetFiles("*pos-*.pgm"))    {        sb.AppendFormat("{0} {1} {2} {3} {4} {5}{6}", file.FullName, 1, 0, 0, 100, 40, Environment.NewLine);    }    File.WriteAllText(@"..\..\CarsInfo\carImages.txt", sb.ToString());
}
private static void createNegativeImagesFile()
{    var sb = new StringBuilder();    foreach (var file in new DirectoryInfo(@"..\..\CarData\CarData\TrainImages").GetFiles("*neg-*.pgm"))    {        sb.AppendFormat("{0}{1}", file.FullName,Environment.NewLine);    }    File.WriteAllText(@"..\..\CarsInfo\negativeImages.txt", sb.ToString());
}
برای کامپایل اطلاعات فایل‌های تولید شده، نیاز به فایل opencv_createsamples.exe است. این فایل را در پوشه‌ی opencv\build\x86\vc12\bin بسته‌ی اصلی OpenCV می‌توانید پیدا کنید.
 opencv_createsamples.exe -info carImages.txt -num 550 -w 48 -h 24 -vec cars.vec
پارامترهای این دستور شامل سوئیچ info است؛ به معنای مشخص سازی فایل اطلاعات تصاویر مثبت. سوئیچ num تعداد تصاویر آن‌را تعیین می‌کند و سوئیچ‌های w و h، طول و عرض تصاویر هستند. سوئیچ vec نیز جهت تولید یک فایل vector از این اطلاعات بکار می‌رود.
پس از اجرای این دستور، فایل cars.vec تولید خواهد شد؛ با این خروجی:
 Info file name: carImages.txt
Img file name: (NULL)
Vec file name: cars.vec
BG  file name: (NULL)
Num: 550
BG color: 0
BG threshold: 80
Invert: FALSE
Max intensity deviation: 40
Max x angle: 1.1
Max y angle: 1.1
Max z angle: 0.5
Show samples: FALSE
Original image will be scaled to:
  Width: $backgroundWidth / 48
  Height: $backgroundHeight / 24
Create training samples from images collection...
Done. Created 550 samples
اگر علاقمند هستید که محتویات فایل باینری cars.vec را مشاهده کنید، دستور ذیل را صادر نمائید:
 "c:\opencv\build\x86\vc12\bin\opencv_createsamples.exe" -vec cars.vec -w 48 -h 24


در این پنجره‌ی باز شده، تصاویر بعدی و قبلی را می‌توان با دکمه‌های arrow صفحه کلید، مشاهده کرد.


تبدیل فایل برداری تصاویر خودروها به trained data

تا اینجا موفق شدیم بیش از 500 تصویر خودرو را تبدیل به یک فایل برداری سازگار با OpenCV کنیم. اکنون نیاز است، این اطلاعات پردازش شده و trained data مخصوص الگوریتم‌های machine learning تولید شود. این‌کار را توسط برنامه‌ی opencv_traincascade.exe انجام خواهیم داد. این فایل نیز در پوشه‌ی opencv\build\x86\vc12\bin بسته‌ی اصلی OpenCV موجود است.
دستور ذیل در پوشه‌ی data، بر اساس اطلاعات برداری cars.vec و همچنین تصاویر منفی مشخص شده‌ی در فایل negativeImages.txt، با تعداد هر کدام 500 عدد (این عدد را توصیه شده‌است که اندکی کمتر از تعداد max موجود مشخص کنیم) و تعداد مراحل 2  (هر چقدر این تعداد مراحل بیشتر باشد، فایل نهایی تولید شده دقت بالاتری خواهد داشت؛ اما تولید آن به زمان بیشتری نیاز دارد) اجرا می‌شود. در اینجا featureType به LBP یا Local binary Pattern، تنظیم شده‌است. این الگوریتم از Haar cascade سریعتر است.
 "E:\opencv\bin\opencv_traincascade.exe" -data data -vec cars.vec -bg negativeImages.txt -numPos 500 -numNeg 500 -numStages 2 -w 48 -h 24 -featureType LBP
خروجی اجرای این دستور را می‌توانید در پوشه‌ی data با نام cascade.xml، پیدا کنید. پس از آن، روش استفاده‌ی از این فایل، با مطلب تشخیص چهرهتفاوتی ندارد.



کدهای کامل این مثال را از اینجامی‌توانید دریافت کنید.

OpenCVSharp #17

$
0
0
تشخیص اشخاص به کمک OpenCV

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


بانک اطلاعاتی تصاویر چهره‌های اشخاص

در اینجا از تصاویر «The Database of Faces» استفاده خواهیم کرد. این مجموعه شامل تصاویر 40 شخص، در 10 حالت مختلف است.


برای بارگذاری این تصاویر و استفاده‌ی از آن‌ها در الگوریتم FisherFaceRecognizer نیاز به ساختار ذیل است:
public class ImageInfo
{    public Mat Image { set; get; }    public int ImageGroupId { set; get; }    public int ImageId { set; get; }
}
در اینجا Image، محتوای تصویر انتخابی است. مقدار ImageGroupId مساوی مقدار عددی نام پوشه‌ی تصاویر منهای یک، تنظیم می‌شود. برای مثال پوشه‌ی s1 به گروه صفر تنظیم می‌شود. ImageId نیز به یک مقدار خود افزایش یابنده معادل شماره‌ی جاری تصویر، تنظیم می‌گردد؛ به این صورت:
var images = new List<ImageInfo>();
var imageId = 0;
foreach (var dir in new DirectoryInfo(@"..\..\Images").GetDirectories())
{    var groupId = int.Parse(dir.Name.Replace("s", string.Empty)) - 1;    foreach (var imageFile in dir.GetFiles("*.pgm"))    {        images.Add(new ImageInfo        {            Image = new Mat(imageFile.FullName, LoadMode.GrayScale),            ImageId = imageId++,            ImageGroupId = groupId        });    }
}
ابتدا پوشه‌های دیتابیس تصاویر یافت شده و سپس از نام هر پوشه یک شما‌‌ره‌ی گروه (یا شماره‌ی شخص) استخراج می‌شود. سپس تصاویر این پوشه به لیست تصاویر اصلی اضافه خواهند شد.


تشخیص یک چهره‌ی اتفاقی

پس از تشکیل لیست تصاویر، اکنون کار با الگوریتم FisherFaceRecognizer به نحو ذیل خواهد بود:
var model = FaceRecognizer.CreateFisherFaceRecognizer();
model.Train(images.Select(x => x.Image), images.Select(x => x.ImageGroupId));
var rnd = new Random();
var randomImageId = rnd.Next(0, images.Count - 1);
var testSample = images[randomImageId];
Console.WriteLine("Actual group: {0}", testSample.ImageGroupId);
Cv2.ImShow("actual", testSample.Image);
var predictedGroupId = model.Predict(testSample.Image);
Console.WriteLine("Predicted group: {0}", predictedGroupId);
پارامتر اول متد Train، لیست تصاویر است و پارامتر دوم، لیست شماره گروه‌های متناظر با هر تصویر است که در اینجا به عنوان برچسب نیز نامگذاری شده‌است.
سپس با استفاده از کلاس Random، یک تصویر اتفاقی انتخاب می‌شود.
اکنون این تصویر اتفاقی به متد Predict ارسال شده و نتیجه‌ی آن، شماره گروه چهره‌ی تشخیص داده شده‌است. به این ترتیب می‌توان تشخیص داد که یک تصویر مفروض ورودی، متعلق به چه شخصی (یا در اینجا گروه یا برچسب) است.



کدهای کامل این مثال را از اینجامی‌توانید دریافت کنید.

OpenCVSharp #18

$
0
0
ساخت یک OCR ساده تشخیص اعداد انگلیسی به کمک OpenCV

این مطلب را می‌توان به عنوان جمع بندی مطالبی که تاکنون بررسی شدند درنظر گرفت و در اساس مطلب جدیدی ندارد و صرفا ترکیب یک سری تکنیک است؛ برای مثال:
چطور یک تصویر را به نمونه‌ی سیاه و سفید آن تبدیل کنیم؟
کار با متد Threshold جهت بهبود کیفیت یک تصویر جهت تشخیص اشیاء
تشخیص کانتورها (Contours) و اشیاء موجود در یک تصویر
آشنایی با نحوه‌ی گروه بندی تصاویر مشابه و مفاهیمی مانند برچسب‌های تصاویر که بیانگر یک گروه از تصاویر هستند.


تهیه تصاویر اعداد انگلیسی جهت آموزش دادن به الگوریتم CvKNearest

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


مجموعه‌ای از تصاویر 0 تا 9 را جمع آوری کرده‌ایم. هر کدام از پوشه‌ها، بیانگر اعدادی از یک خانواده هستند. این تصویر را با فرمت ذیل جمع آوری می‌کنیم:
public class ImageInfo
{    public Mat Image { set; get; }    public int ImageGroupId { set; get; }    public int ImageId { set; get; }
}
به این ترتیب
public IList<ImageInfo> ReadTrainingImages(string path, string ext)
{    var images = new List<ImageInfo>();    var imageId = 1;    foreach (var dir in new DirectoryInfo(path).GetDirectories())    {        var groupId = int.Parse(dir.Name);        foreach (var imageFile in dir.GetFiles(ext))        {            var image = processTrainingImage(new Mat(imageFile.FullName, LoadMode.GrayScale));            if (image == null)            {                continue;            }            images.Add(new ImageInfo            {                Image = image,                ImageId = imageId++,                ImageGroupId = groupId            });        }    }    return images;
}
در متد خواندن تصاویر آموزشی، ابتدا پوشه‌های اصلی مسیر Numbers تصویر ابتدای بحث دریافت می‌شوند. سپس نام هر پوشه، شماره‌ی گروه تصاویر موجود در آن پوشه را تشکیل خواهد داد. به این نام در الگوریتم‌های machine leaning، کلاس هم گفته می‌شود. سپس هر تصویر را با فرمت سیاه و سفید بارگذاری کرده و به لیست تصاویر موجود اضافه می‌کنیم. در اینجا از متد processTrainingImage نیز استفاده شده‌است. هدف از آن بهبود کیفیت تصویر دریافتی جهت کار تشخیص اشیاء است:
private static Mat processTrainingImage(Mat gray)
{    var threshImage = new Mat();    Cv2.Threshold(gray, threshImage, Thresh, ThresholdMaxVal, ThresholdType.BinaryInv); // Threshold to find contour    Point[][] contours;    HiearchyIndex[] hierarchyIndexes;    Cv2.FindContours(        threshImage,        out contours,        out hierarchyIndexes,        mode: ContourRetrieval.CComp,        method: ContourChain.ApproxSimple);    if (contours.Length == 0)    {        return null;    }    Mat result = null;    var contourIndex = 0;    while ((contourIndex >= 0))    {        var contour = contours[contourIndex];        var boundingRect = Cv2.BoundingRect(contour); //Find bounding rect for each contour        var roi = new Mat(threshImage, boundingRect); //Crop the image        //Cv2.ImShow("src", gray);        //Cv2.ImShow("roi", roi);        //Cv.WaitKey(0);        var resizedImage = new Mat();        var resizedImageFloat = new Mat();        Cv2.Resize(roi, resizedImage, new Size(10, 10)); //resize to 10X10        resizedImage.ConvertTo(resizedImageFloat, MatType.CV_32FC1); //convert to float        result = resizedImageFloat.Reshape(1, 1);        contourIndex = hierarchyIndexes[contourIndex].Next;    }    return result;
}
عملیات صورت گرفته‌ی در این متد را با تصویر ذیل بهتر می‌توان توضیح داد:


ابتدا تصویر اصلی بارگذاری می‌شود؛ همان تصویر سمت چپ. سپس با استفاده از متد Threshold، شدت نور نواحی مختلف آن یکسان شده و آماده می‌شود برای تشخیص کانتورهای موجود در آن. در ادامه با استفاده از متد FindContours، شیء مرتبط با عدد جاری یافت می‌شود. سپس متد Cv2.BoundingRect مستطیل دربرگیرنده‌ی این شیء را تشخیص می‌دهد (تصویر سمت راست). بر این اساس می‌توان تصویر اصلی ورودی را به یک تصویر کوچکتر که صرفا شامل ناحیه‌ی عدد مدنظر است، تبدیل کرد. در ادامه برای کار با الگوریتم  CvKNearest نیاز است تا این تصویر بهبود یافته را تبدیل به یک ماتریس یک بعدی کردی که روش انجام کار توسط متد Reshape مشاهده می‌کنید.
از همین روش پردازش و بهبود تصویر ورودی، جهت پردازش اعداد یافت شده‌ی در یک تصویر با تعداد زیادی عدد نیز استفاده خواهیم کرد.


آموزش دادن به الگوریتم CvKNearest

تا اینجا تصاویر گروه بندی شده‌ای را خوانده و لیستی از آن‌ها را مطابق فرمت الگوریتم CvKNearest تهیه کردیم. مرحله‌ی بعد، معرفی این لیست به متد Train این الگوریتم است:
public CvKNearest TrainData(IList<ImageInfo> trainingImages)
{    var samples = new Mat();    foreach (var trainingImage in trainingImages)    {        samples.PushBack(trainingImage.Image);    }    var labels = trainingImages.Select(x => x.ImageGroupId).ToArray();    var responses = new Mat(labels.Length, 1, MatType.CV_32SC1, labels);    var tmp = responses.Reshape(1, 1); //make continuous    var responseFloat = new Mat();    tmp.ConvertTo(responseFloat, MatType.CV_32FC1); // Convert  to float    var kNearest = new CvKNearest();    kNearest.Train(samples, responseFloat); // Train with sample and responses    return kNearest;
}
متد Train دو ورودی دارد. ورودی اول آن یک تصویر است که باید از طریق متد PushBack کلاس Mat تهیه شود. بنابراین لیست تصاویر اصلی را تبدیل به لیستی از Matها خواهیم کرد.
سپس نیاز است لیست گروه‌های متناظر با تصاویر اعداد را تبدیل به فرمت مورد انتظار متد Train کنیم. در اینجا صرفا لیستی از اعداد صحیح را داریم. این لیست نیز باید تبدیل به یک Mat شود که روش انجام آن در متد فوق بیان شده‌است. کلاس Mat سازنده‌ی مخصوصی را جهت تبدیل لیست اعداد، به همراه دارد. این Mat نیز باید تبدیل به یک ماتریس یک بعدی شود که برای این منظور از متد Reshape استفاده شده‌است.


انجام عملیات OCR نهایی

پس از تهیه‌ی لیستی از تصاویر و آموزش دادن آن‌ها به الگوریتم CvKNearest، تنها کاری که باید انجام دهیم، یافتن اعداد در تصویر نمونه‌ی مدنظر و سپس معرفی آن به متد FindNearest الگوریتم CvKNearest است. روش انجام اینکار بسیار شبیه است به روش معرفی شده در متد processTrainingImage که پیشتر بررسی شد:
public void DoOCR(CvKNearest kNearest, string path)
{    var src = Cv2.ImRead(path);    Cv2.ImShow("Source", src);    var gray = new Mat();    Cv2.CvtColor(src, gray, ColorConversion.BgrToGray);    var threshImage = new Mat();    Cv2.Threshold(gray, threshImage, Thresh, ThresholdMaxVal, ThresholdType.BinaryInv); // Threshold to find contour    Point[][] contours;    HiearchyIndex[] hierarchyIndexes;    Cv2.FindContours(        threshImage,        out contours,        out hierarchyIndexes,        mode: ContourRetrieval.CComp,        method: ContourChain.ApproxSimple);    if (contours.Length == 0)    {        throw new NotSupportedException("Couldn't find any object in the image.");    }    //Create input sample by contour finding and cropping    var dst = new Mat(src.Rows, src.Cols, MatType.CV_8UC3, Scalar.All(0));    var contourIndex = 0;    while ((contourIndex >= 0))    {        var contour = contours[contourIndex];        var boundingRect = Cv2.BoundingRect(contour); //Find bounding rect for each contour        Cv2.Rectangle(src,            new Point(boundingRect.X, boundingRect.Y),            new Point(boundingRect.X + boundingRect.Width, boundingRect.Y + boundingRect.Height),            new Scalar(0, 0, 255),            2);        var roi = new Mat(threshImage, boundingRect); //Crop the image        var resizedImage = new Mat();        var resizedImageFloat = new Mat();        Cv2.Resize(roi, resizedImage, new Size(10, 10)); //resize to 10X10        resizedImage.ConvertTo(resizedImageFloat, MatType.CV_32FC1); //convert to float        var result = resizedImageFloat.Reshape(1, 1);        var results = new Mat();        var neighborResponses = new Mat();        var dists = new Mat();        var detectedClass = (int)kNearest.FindNearest(result, 1, results, neighborResponses, dists);        //Console.WriteLine("DetectedClass: {0}", detectedClass);        //Cv2.ImShow("roi", roi);        //Cv.WaitKey(0);        //Cv2.ImWrite(string.Format("det_{0}_{1}.png",detectedClass, contourIndex), roi);        Cv2.PutText(            dst,            detectedClass.ToString(CultureInfo.InvariantCulture),            new Point(boundingRect.X, boundingRect.Y + boundingRect.Height),            0,            1,            new Scalar(0, 255, 0),            2);        contourIndex = hierarchyIndexes[contourIndex].Next;    }    Cv2.ImShow("Segmented Source", src);    Cv2.ImShow("Detected", dst);    Cv2.ImWrite("dest.jpg", dst);    Cv2.WaitKey();
}
این عملیات به صورت خلاصه در تصویر ذیل مشخص شده‌است:


ابتدا تصویر اصلی که قرار است عملیات OCR روی آن صورت گیرد، بارگذاری می‌شود. سپس کانتورها و اعداد موجود در آن تشخیص داده می‌شوند. مستطیل‌های قرمز رنگ در برگیرنده‌ی این اعداد را در تصویر دوم مشاهده می‌کنید. سپس این کانتور‌های یافت شده را که شامل یکی از اعداد تشخیص داده شده‌است، تبدیل به یک ماتریس یک بعدی کرده و به متد FindNearest ارسال می‌کنیم. خروجی آن نام گروه یا پوشه‌ای است که این عدد در آن قرار دارد. در همینجا این خروجی را تبدیل به یک رشته کرده و در تصویر سوم با رنگ سبز رنگ نمایش می‌دهیم.
بنابراین در این تصویر، پنجره‌ی segmented image، همان اشیاء تشخیص داده شده‌ی از تصویر اصلی هستند.
پنجره‌ی با زمینه‌ی سیاه رنگ، نتیجه‌ی نهایی OCR است که نسبتا هم دقیق عمل کرده‌است.


کدهای کامل این مثال را از اینجامی‌توانید دریافت کنید.

‫ساخت یک بارکدخوان با استفاده از OpenCV و ZXing.Net

$
0
0
فرض کنید می‌خواهیم بارکد این قبض را یافته و سپس عدد متناظر با آن‌را در برنامه بخوانیم.


مراحل کار به این صورت هستند:


بارگذاری تصویر و چرخش آن در صورت نیاز

ابتدا تصویر بارکد دار را بارگذاری کرده و آن‌را تبدیل به یک تصویر سیاه و سفید می‌کنیم:
// load the image and convert it to grayscale
var image = new Mat(fileName);
if (rotation != 0)
{    rotateImage(image, image, rotation, 1);
}
if (debug)
{    Cv2.ImShow("Source", image);    Cv2.WaitKey(1); // do events
}
var gray = new Mat();
var channels = image.Channels();
if (channels > 1)
{    Cv2.CvtColor(image, gray, ColorConversion.BgrToGray);
}
else
{    image.CopyTo(gray);
}
در این بین ممکن است بارکد موجود در تصویر، دقیقا در زاویه‌ای که در تصویر ابتدای بحث قرار گرفته‌است، وجود نداشته باشد؛ مثلا منهای 90 درجه، چرخیده باشد. به همین جهت می‌توان از متد چرخش تصویر مطلب «تغییر اندازه، و چرخش تصاویر» ارائه شده در قسمت نهم این سری استفاده کرد.


تشخیص گرادیان‌های افقی و عمودی

یکی از روش‌های تشخیص بارکد، استفاده از روشی است که در تشخیص خودرو قسمت 16بیان شد. تعداد زیادی تصویر بارکد را تهیه و سپس آن‌ها را به الگوریتم‌های machine learning جهت تشخیص و یافتن محدوده‌ی بارکد موجود در یک تصویر، ارسال کنیم. هرچند این روش جواب خواهد داد، اما در این مورد خاص، قسمت بارکد، شبیه به گرادیانی از رنگ‌ها است. کتابخانه‌ی OpenCV برای یافتن این نوع گرادیان‌ها دارای متدی است به نام Sobel :
// compute the Scharr gradient magnitude representation of the images
// in both the x and y direction
var gradX = new Mat();
Cv2.Sobel(gray, gradX, MatType.CV_32F, xorder: 1, yorder: 0, ksize: -1);
//Cv2.Scharr(gray, gradX, MatType.CV_32F, xorder: 1, yorder: 0);
var gradY = new Mat();
Cv2.Sobel(gray, gradY, MatType.CV_32F, xorder: 0, yorder: 1, ksize: -1);
//Cv2.Scharr(gray, gradY, MatType.CV_32F, xorder: 0, yorder: 1);
// subtract the y-gradient from the x-gradient
var gradient = new Mat();
Cv2.Subtract(gradX, gradY, gradient);
Cv2.ConvertScaleAbs(gradient, gradient);
if (debug)
{    Cv2.ImShow("Gradient", gradient);    Cv2.WaitKey(1); // do events
}


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


کاهش نویز و یکی کردن نواحی تشخیص داده شده

در ادامه می‌خواهیم با استفاده از متدهای تشخیص کانتور (قسمت 12)، نواحی با بیشترین شدت گرادیان افقی را پیدا کنیم. اما تصویر حاصل از قسمت قبل برای اینکار مناسب نیست. به همین جهت با استفاده از متدهای کار با مورفولوژی تصاویر، این نواحی گرادیانی را یکی می‌کنیم (قسمت 8).
// blur and threshold the image
var blurred = new Mat();
Cv2.Blur(gradient, blurred, new Size(9, 9));
var threshImage = new Mat();
Cv2.Threshold(blurred, threshImage, thresh, 255, ThresholdType.Binary);
if (debug)
{    Cv2.ImShow("Thresh", threshImage);    Cv2.WaitKey(1); // do events
}
// construct a closing kernel and apply it to the thresholded image
var kernel = Cv2.GetStructuringElement(StructuringElementShape.Rect, new Size(21, 7));
var closed = new Mat();
Cv2.MorphologyEx(threshImage, closed, MorphologyOperation.Close, kernel);
if (debug)
{    Cv2.ImShow("Closed", closed);    Cv2.WaitKey(1); // do events
}
// perform a series of erosions and dilations
Cv2.Erode(closed, closed, null, iterations: 4);
Cv2.Dilate(closed, closed, null, iterations: 4);
if (debug)
{    Cv2.ImShow("Erode & Dilate", closed);    Cv2.WaitKey(1); // do events
}
این سه مرحله را در تصاویر ذیل مشاهده می‌کنید:


ابتدا با استفاده از متد Threshold، تصویر را به یک تصویر باینری تبدیل خواهیم کرد. در این تصویر تمام نقاط دارای شدت رنگ کمتر از مقدار thresh، به مقدار حداکثر 255 تنظیم می‌شوند.
سپس با استفاده از متدهای تغییر مورفولوژی تصویر، قسمت‌های مجاور به هم را می‌بندیم و یکی می‌کنیم. این مورد در یافتن اشیاء احتمالی که ممکن است بارکد باشند، بسیار مفید است.
متدهای Erode و Dilate در اینجا کار حذف نویزهای اضافی را انجام می‌دهند؛ تا بهتر بتوان بر روی نواحی بزرگتر یافت شده، تمرکز کرد.



یافتن بزرگترین ناحیه‌ی به هم پیوسته‌ی موجود در یک تصویر

تمام این مراحل را انجام دادیم تا بتوانیم بزرگترین ناحیه‌ی به هم پیوسته‌ای را که احتمال می‌رود بارکد باشد، در تصویر تشخیص دهیم. پس از این آماده سازی‌ها، اکنون با استفاده از متد یافتن کانتورها، تمام نواحی یکی شده را یافته و بزرگترین مساحت ممکن را به عنوان بارکد انتخاب می‌کنیم:
//find the contours in the thresholded image, then sort the contours
//by their area, keeping only the largest one
Point[][] contours;
HiearchyIndex[] hierarchyIndexes;
Cv2.FindContours(    closed,    out contours,    out hierarchyIndexes,    mode: ContourRetrieval.CComp,    method: ContourChain.ApproxSimple);
if (contours.Length == 0)
{    throw new NotSupportedException("Couldn't find any object in the image.");
}
var contourIndex = 0;
var previousArea = 0;
var biggestContourRect = Cv2.BoundingRect(contours[0]);
while ((contourIndex >= 0))
{    var contour = contours[contourIndex];    var boundingRect = Cv2.BoundingRect(contour); //Find bounding rect for each contour    var boundingRectArea = boundingRect.Width * boundingRect.Height;    if (boundingRectArea > previousArea)    {        biggestContourRect = boundingRect;        previousArea = boundingRectArea;    }    contourIndex = hierarchyIndexes[contourIndex].Next;
}
var barcode = new Mat(image, biggestContourRect); //Crop the image
Cv2.CvtColor(barcode, barcode, ColorConversion.BgrToGray);
Cv2.ImShow("Barcode", barcode);
Cv2.WaitKey(1); // do events
حاصل این عملیات یافتن بزرگترین ناحیه‌ی گرادیانی به هم پیوسته‌ی موجود در تصویر است:


خواندن مقدار متناظر با بارکد یافت شده

خوب، تا اینجا موفق شدیم، محل قرارگیری بارکد را تصویر پیدا کنیم. مرحله‌ی بعد خواندن مقدار متناظر با این تصویر است. برای این منظور از کتابخانه‌ی سورس بازی به نام http://zxingnet.codeplex.comاستفاده خواهیم کرد. این کتابخانه قادر است بارکد بسازد و همچنین تصاویر بارکدها را خوانده و مقادیر متناظر با آن‌ها را استخراج کند. برای نصب آن می‌توان از دستور ذیل استفاده کرد:
 PM> Install-Package ZXing.Net
پس از نصب این کتابخانه‌ی بارکدساز و بارکد خوان، اکنون تنها کاری که باید صورت گیرد، ارسال تصویر بارکد جدا شده‌ی توسط OpenCV به آن است:
private static string getBarcodeText(Mat barcode)
{    // `ZXing.Net` needs a white space around the barcode    var barcodeWithWhiteSpace = new Mat(new Size(barcode.Width + 30, barcode.Height + 30), MatType.CV_8U, Scalar.White);    var drawingRect = new Rect(new Point(15, 15), new Size(barcode.Width, barcode.Height));    var roi = barcodeWithWhiteSpace[drawingRect];    barcode.CopyTo(roi);    Cv2.ImShow("Enhanced Barcode", barcodeWithWhiteSpace);    Cv2.WaitKey(1); // do events    return decodeBarcodeText(barcodeWithWhiteSpace.ToBitmap());
}
private static string decodeBarcodeText(System.Drawing.Bitmap barcodeBitmap)
{    var source = new BitmapLuminanceSource(barcodeBitmap);    // using http://zxingnet.codeplex.com/    // PM> Install-Package ZXing.Net    var reader = new BarcodeReader(null, null, ls => new GlobalHistogramBinarizer(ls))    {        AutoRotate = true,        TryInverted = true,        Options = new DecodingOptions        {            TryHarder = true,            //PureBarcode = true,            /*PossibleFormats = new List<BarcodeFormat>                    {                        BarcodeFormat.CODE_128                        //BarcodeFormat.EAN_8,                        //BarcodeFormat.CODE_39,                        //BarcodeFormat.UPC_A                    }*/        }    };    //var newhint = new KeyValuePair<DecodeHintType, object>(DecodeHintType.ALLOWED_EAN_EXTENSIONS, new Object());    //reader.Options.Hints.Add(newhint);    var result = reader.Decode(source);    if (result == null)    {        Console.WriteLine("Decode failed.");        return string.Empty;    }    Console.WriteLine("BarcodeFormat: {0}", result.BarcodeFormat);    Console.WriteLine("Result: {0}", result.Text);    var writer = new BarcodeWriter    {        Format = result.BarcodeFormat,        Options = { Width = 200, Height = 50, Margin = 4},        Renderer = new ZXing.Rendering.BitmapRenderer()    };    var barcodeImage = writer.Write(result.Text);    Cv2.ImShow("BarcodeWriter", barcodeImage.ToMat());    return result.Text;
}
چند نکته را باید در مورد کار با ZXing.Net بخاطر داشت؛ وگرنه جواب نمی‌گیرید:
الف) این کتابخانه حتما نیاز دارد تا تصویر بارکد، در یک حاشیه‌ی سفید در اختیار او قرار گیرد. به همین جهت در متد getBarcodeText، ابتدا تصویر بارکد یافت شده، به میانه‌ی یک مستطیل سفید رنگ بزرگ‌تر کپی می‌شود.
ب) برای تبدیل Mat به Bitmap مورد نیاز این کتابخانه می‌توان از متد الحاقی ToBitmap استفاده کرد (قسمت 7).
ج) پس از آن وهله‌ای از کلاس BarcodeReader آماده شده و در آن پارامترهایی مانند بیشتر سعی کن (TryHarder) و اصلاح درجه‌ی چرخش تصویر (AutoRotate) تنظیم شده‌اند.
د) بارکدهای موجود در قبض‌های ایران عموما بر اساس فرمت CODE_128 ساخته می‌شوند. بنابراین برای خواندن سریعتر آ‌نها می‌توان PossibleFormats را مقدار دهی کرد. اگر این مقدار دهی صورت نگیرد، تمام حالت‌های ممکن بررسی می‌شوند.

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


همانطور که مشاهده می‌کنید، عدد تشخیص داده شده، با عدد شناسه‌ی قبض و شناسه‌ی پرداخت تصویر ابتدای بحث یکی است.


بهبود تصویر، پیش از ارسال آن به متد Decode کتابخانه‌ی ZXing.Net

در تصویر قبلی، سطر decode failed را هم ملاحظه می‌کنید. علت اینجا است که اولین سعی انجام شده، موفق نبوده است؛ چون تصویر تشخیص داده شده، بیش از اندازه نویز و حاشیه‌ی خاکستری دارد. می‌توان این حاشیه‌ی خاکستری را با دوبار اعمال متد Thresholdاز بین برد:
var barcodeClone = barcode.Clone();
var barcodeText = getBarcodeText(barcodeClone);
if (string.IsNullOrWhiteSpace(barcodeText))
{    Console.WriteLine("Enhancing the barcode...");    //Cv2.AdaptiveThreshold(barcode, barcode, 255,        //AdaptiveThresholdType.GaussianC, ThresholdType.Binary, 9, 1);    //var th = 119;    var th = 100;    Cv2.Threshold(barcode, barcode, th, 255, ThresholdType.ToZero);    Cv2.Threshold(barcode, barcode, th, 255, ThresholdType.Binary);    barcodeText = getBarcodeText(barcode);
}
Cv2.Rectangle(image,    new Point(biggestContourRect.X, biggestContourRect.Y),    new Point(biggestContourRect.X + biggestContourRect.Width, biggestContourRect.Y + biggestContourRect.Height),    new Scalar(0, 255, 0),    2);
if (debug)
{    Cv2.ImShow("Segmented Source", image);    Cv2.WaitKey(1); // do events
}
Cv2.WaitKey(0);
Cv2.DestroyAllWindows();


اعداد یافت شده، دقیقا از روی تصویر بهبود یافته‌ی توسط متدهای Threshold خوانده شده‌اند و نه تصویر ابتدایی یافت شده. بنابراین به این موضوع نیز باید دقت داشت.


کدهای کامل این مثال را از اینجامی‌توانید دریافت کنید.

‫بهبود کارآیی برنامه‌های قدیمی که از SQL Server استفاده می‌کنند

$
0
0
برنامه‌های قدیمی، الزاما خیلی قدیمی هم نیستند؛ برنامه‌هایی هستند پر از کوئری‌های ذیل:
 SELECT * FROM table1 WHERE OrderDate ='12 Mar 2004'

SET @SQL = 'SELECT * FROM table2 WHERE OrderDate = ' + '''' + @Var + ''''
EXEC (@SQL)
ویژگی مهم این نوع کوئری‌ها که با جمع زدن رشته‌ها و یا مقدار دهی مستقیم فیلدها تشکیل شده‌اند، «غیر پارامتری» بودن آ‌ن‌ها است.
این نوع مشکلات با بکار گیری ORMها به نحو قابل توجهی کاهش یافته‌است؛ زیرا این نوع واسط‌ها در اغلب موارد، در آخر کار کوئری‌هایی پارامتری را تولید می‌کنند.


مشکل کوئری‌های غیر پارامتری چیست؟

استفاده‌ی وسیع از کوئری‌های غیرپارامتری با SQL Server، مشکلی را پدید می‌آورد به نام «Cache bloat» یا «کش پُف کرده» و این «پُف» به این معنا است که کش کوئری‌های اجرا شده‌ی بر روی SQL Server بیش از اندازه با Query plan‌های مختلف حاصل از بررسی نحوه‌ی اجرای بهینه‌ی آن‌ها پر شده‌است. هر کوئری که به SQL Server می‌رسد، جهت اجرای بهینه، ابتدا پردازش می‌شود و دستور العملی خاص آن، تهیه و سپس در حافظه کش می‌شود. وجود این کش به این خاطر است که SQL Server هربار به ازای هر کوئری رسیده، این عملیات پردازشی را تکرار نکند. مشکل از زمانی شروع می‌شود که SQL Server کوئری‌هایی را که از نظر یک برنامه نویس مانند هم هستند را به علت عدم استفاده‌ی از پارامترها، یکسان تشخیص نداده و برای هر کدام یک Plan جداگانه را محاسبه و کش می‌کند. این مساله با حجم بالای کوئری‌های رسیده دو مشکل را ایجاد می‌کند:
الف) مصرف حافظه‌ی بالای SQL Server که گاهی اوقات این حافظه‌ی اختصاص داده شده‌ی به کش کوئری‌ها به بالای یک گیگابایت نیز می‌رسد.
ب) CPU Usage بالای سیستم


سیستم قدیمی است؛ امکان تغییر کدها را نداریم.

بدیهی است بهترین راه حلی که در اینجا وجود دارد، پارامتری ارسال کردن کوئری‌ها به SQL Server است تا به ازای هر تغییری در مقادیر آن‌ها، این کوئری‌ها باز هم یکسان به نظر برسند و SQL Server سعی در محاسبه‌ی مجدد Plan آن‌ها نکند. اما ... اگر این امکان را ندارید، خود SQL Server یک چنین قابلیت‌هایی را به صورت توکار تدارک دیده‌است که باید فعال شوند.


فعال سازی پارامتری کردن خودکار کوئری‌ها در SQL Server

اگر نمی‌توانید کدهای یک سیستم قدیمی را تغییر دهید، SQL Server می‌تواند به صورت خودکار این‌کار را برای شما انجام دهد. در این حالت فقط کافی است یکی از دو دستور ذیل را اجرا کنید:
  --Forced
ALTER DATABASE dbName SET PARAMETERIZATION FORCED

--Simple
ALTER DATABASE dbName SET PARAMETERIZATION SIMPLE
حالت simple بیشتر جهت پارامتری کردن خودکار کوئری‌های select بکار می‌رود. اگر می‌خواهید تمام کوئری‌های select, insert, update و delete را نیز پارامتری کنید، باید از حالت forced استفاده نمائید.



فعال سازی بهبود کارآیی SQL Server با کوئری‌های Ad-Hoc زیاد

به کوئری‌های غیرپارامتری، کوئری‌های Ad-Hoc نیز گفته می‌شود. اگر سیستم فعلی شما، تعداد زیادی کوئری Ad-Hoc تولید می‌کند، می‌توان فشار کاری SQL Server را برای این مورد خاص، تنظیم و بهینه سازی کرد.
فعال سازی گزینه‌ی ویژه‌ی «Optimize for Ad hoc Workloads» سبب می‌شود تا SQL Server پس از مدتی به صورت خودکار کش Plan کوئری‌هایی را که به ندرت استفاده می‌شوند، حذف کند. همین مساله سبب آزاد شدن حافظه و بهبود کارآیی کلی سیستم می‌گردد. همچنین باید درنظر داشت که کش Plan کوئری‌ها نامحدود نیست و سقفی دارد. به همین جهت آزاد شدن آن، کش کردن کوئری‌هایی را که بیشتر استفاده می‌شوند، ساده‌تر می‌کند.
برای اعمال آن به یک بانک اطلاعاتی خاص، نیاز است دستورات ذیل را اجرا کرد:
 use dbName;
-- Optimizing for Ad hoc Workloads
exec sp_configure 'show advanced options',1;
RECONFIGURE;
go
exec sp_configure 'optimize for ad hoc workloads',1;
RECONFIGURE;
Go


برای مطالعه‌ی بیشتر
Fixing Cache Bloat Problems With Guide Plans and Forced Parameterization
Optimizing ad-hoc workloads
Optimizing for Ad hoc Workloads

‫نرمال سازی اطلاعات کاربران در حین ثبت نام

$
0
0
شرایط دنیای واقعی، بسیار متفاوت است از طراحی‌های ساده‌ی اولیه‌ی ثبت نام. در طراحی‌های ساده، ایمیل، نام کاربری و بسیاری از اطلاعات دیگر باید منحصربفرد باشند. ایندکس منحصربفرد تعریف می‌کنید. قیود و اعتبار سنجی سمت سرور و سمت کاربر را اضافه می‌کنید. چقدر عالی! اما ... دنیای واقعی شکل دیگری را دارد!
یک روز با ایمیل username@gmail.com ثبت نام می‌کنند. فردا با ایمیل user.name@gmail.com ثبت نام خواهند کرد. پس فردا با ایمیل us.er.name@gmail.com و به همین ترتیب! امروز با نام «کاربر یک» ثبت نام می‌کنند. فردا با نام «کاربر  یک»! امروز با نام «مجید» ثبت نام می‌کنند، فردا با نام «مـجـــیـــد»! همچنین علاقه‌ی شدیدی هم به استفاده از ایمیل‌های fake دارند (راه حل).
بنابراین نیاز است اطلاعات کاربران را پیش از ثبت نام نرمال سازی کرد. برای مثال نقطه‌های ایمیل‌های جیمیل را حذف کرد؛ یا اگر اجازه داده‌اید که در بین کلمات نام کاربری، فاصله‌ای را وارد کنند، فقط یک فاصله مجاز باشد و یا اگر نامی را ثبت می‌کنید، به فکر حالت‌های کش آمده‌ی آن مانند «مـجـــــــــیــــــــــد» هم باید بود و آن‌را تبدیل به حالت اصلی‌اش کرد.


نرمال سازی ایمیل‌های gmail

تا جایی که اطلاع دارم، حداقل فیس بوک و جی‌میل، بکارگیری نقاط را در ایمیل‌ها مجاز می‌دانند. برای مثال ترکیب‌های زیر از دید gmail تنها یک ایمیل محسوب می‌شوند:
johndoe@gmail.com
john....doe@gmail.com
johndoe+spamsite@gmail.com

راه حل پیشنهادی:
public static string FixGmailDots(string email)
{    if (string.IsNullOrWhiteSpace(email))        return string.Empty;    email = email.ToLowerInvariant().Trim();    var emailParts = email.Split('@');    var name = emailParts[0].Replace(".", string.Empty).Replace("+", string.Empty);    var emailDomain = emailParts[1];    string[] domainsAllowedDots =    {        "gmail.com",        "facebook.com"    };    var isFromDomainsAllowedDots = domainsAllowedDots.Any(domain => emailDomain.Equals(domain));    return !isFromDomainsAllowedDots ? email : string.Format("{0}@{1}", name, emailDomain);
}
در اینجا بررسی می‌شود که آیا دومین ایمیل دریافتی از سمت جیمیل یا فیس بوک است؟ اگر بله، آنگاه نقطه‌ها و +‌های آن‌ها حذف می‌شوند.
این بررسی باید در حین ثبت نام و همچنین ویرایش اطلاعات کاربری جهت نرمال سازی اطلاعات اعمال شود.
اگر سایت‌های دیگری هم هستند که بکارگیری نقاط را مجاز می‌دانند، آرایه‌ی domainsAllowedDots را تکمیل کنید.


نرمال سازی ورود حروف ویژه

نرمال سازی ابتدایی ثبت نام کاربران در سایت جاری به صورت ذیل است:
 friendlyName = friendlyName.ApplyCorrectYeKe().RemoveDiacritics().CleanUnderLines().RemovePunctuation();
var trimmedFriendlyName = friendlyName.Trim().Replace(" ", "");
با ApplyCorrectYeKeآشنایی دارید؛ همان یک دست کردن ی و ک فارسی و عربی است.
RemoveDiacritics همان حذف اعراباز کلمات است است.
متد پاکسازی underlineهای ویژه یا همان نام‌های کش آمده، به صورت زیر است:
public static string CleanUnderLines(string text)
{    if (string.IsNullOrWhiteSpace(text))        return string.Empty;    const char chr1600 = (char)1600; //ـ=1600
    const char chr8204 = (char)8204; //‌=8204
 
    return text.Replace(chr1600.ToString(CultureInfo.InvariantCulture), "")               .Replace(chr8204.ToString(CultureInfo.InvariantCulture), "");
}
و متد RemovePunctuation :
 public static string RemovePunctuation(string text)
{
 return string.IsNullOrWhiteSpace(text) ? string.Empty : new string(text.Where(c => !char.IsPunctuation(c)).ToArray());
}

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

‫تخمین مدت زمان خوانده شدن یک مطلب

$
0
0
پس از انتشار مطلب «Pro Agile .NET Development With Scrum - قسمت اول» شاید این سؤال در ابتدای کار برای خواننده پیش بیاید که ... چقدر باید برای خواندن آن وقت بگذارم؟ برای پاسخ به این سؤال باید درنظر داشت که یک انسان معمولی، می‌تواند بین 200 تا 250 کلمه را در دقیقه، مطالعه کند. بنابراین در ابتدا باید محاسبه کرد که یک متن، چه تعدادی کلمه دارد؟
شاید عنوان کنید که کافی است متن ورودی را بر اساس فاصله‌ی بین کلمات تقسیم بندی کرده و سپس تعداد کلمات بدست آمده را محاسبه کنیم:
 var words = text.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
return words.Length;
این روش با آزمون زیر کار نکرده و با شکست مواجه می‌شود:
[TestMethod]
public void TestInvalidChars()
{    const string data = "To be . ! < > ( ) ! ! , ; : ' ? + -";    Assert.AreEqual(2, data.WordsCount());
}
در اینجا ! ، و امثال آن نیز یک کلمه درنظر گرفته می‌شوند. برای حل این مشکل کافی است آرایه‌ی split را کمی تکمیل‌تر کنیم تا حروف غیرمجاز را درنظر نگیرد:
 var words = text.Split(
    new[] { ' ', ',', ';', '.', '!', '"', '(', ')', '?', ':', '\'', '«' , '»', '+', '-' },
    StringSplitOptions.RemoveEmptyEntries);
return words.Length;
تا اینجا مشکل !، >< حل شد، اما در مورد متن ذیل چطور؟
[TestMethod]
public void TestSimpleHtmlSpacesWithNewLine()
{    const string data = "<b>this is&nbsp;a&nbsp;&nbsp;test.</b>\n\r<b>this is&nbsp;a&nbsp;&nbsp;test.</b>";    Assert.AreEqual(8, data.WordsCount());
}
مطالب ثبت شده، عموما توسط HTML Editorها ثبت می‌شوند. بنابراین دارای انواع و اقسام تگ‌ها بوده و همچنین ممکن است در این بین new line هم وجود داشته باشد که در این حالت، test\n\rtest باید دو کلمه محاسبه شود و نه یک کلمه.
اگر این موارد را در نظر بگیریم، به کلاس ذیل خواهیم رسید:
using System;
using System.Text.RegularExpressions;
namespace ReadingTime
{    public static class CalculateWordsCount    {        private static readonly Regex _matchAllTags =            new Regex(@"<(.|\n)*?>", RegexOptions.IgnoreCase | RegexOptions.Compiled);        public static int WordsCount(this string text)        {            if (string.IsNullOrWhiteSpace(text))            {                return 0;            }            text = text.cleanTags().Trim();            text = text.Replace("\t", " ");            text = text.Replace("\n", " ");            text = text.Replace("\r", " ");            var words = text.Split(                new[] { ' ', ',', ';', '.', '!', '"', '(', ')', '?', ':', '\'', '«' , '»', '+', '-' },                StringSplitOptions.RemoveEmptyEntries);            return words.Length;        }        private static string cleanTags(this string data)        {            return data.Replace("\n", "\n ").removeHtmlTags();        }        private static string removeHtmlTags(this string text)        {            return string.IsNullOrEmpty(text) ?                        string.Empty :                        _matchAllTags.Replace(text, " ").Replace("&nbsp;", " ");        }    }
}
در اینجا حذف تگ‌های HTML و همچنین پردازش خطوط جدید و حروف غیرمجاز درنظر گرفته شده‌اند.

پس از اینکه موفق به شمارش تعداد کلمات یک متن HTML ایی شدیم، اکنون می‌توان این تعداد را تقسیم بر 180 (یک عدد معمول و متداول) کرد تا زمان خواندن کل متن بدست آید. سپس با استفاده از متد toReadableString می‌توان آن‌را به شکل قابل خواندن‌تری نمایش داد.
using System;
namespace ReadingTime
{    public static class CalculateReadingTime    {        public static string MinReadTime(this string text, int wordsPerMinute = 180)        {            var wordsCount = text.WordsCount();            var minutes = wordsCount / wordsPerMinute;            return minutes == 0 ? "کمتر از یک دقیقه" : TimeSpan.FromMinutes(minutes).toReadableString();        }        private static string toReadableString(this TimeSpan span)        {            var formatted = string.Format("{0}{1}{2}{3}",                span.Duration().Days > 0 ? string.Format("{0:0} روز و ", span.Days) : string.Empty,                span.Duration().Hours > 0 ? string.Format("{0:0} ساعت و ", span.Hours) : string.Empty,                span.Duration().Minutes > 0 ? string.Format("{0:0} دقیقه و ", span.Minutes) : string.Empty,                span.Duration().Seconds > 0 ? string.Format("{0:0} ثانیه", span.Seconds) : string.Empty);            if (formatted.EndsWith("و "))            {                formatted = formatted.Substring(0, formatted.Length - 2);            }            if (string.IsNullOrEmpty(formatted))            {                formatted = "0 ثانیه";
            }            return formatted.Trim();        }    }
}

کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید:
ReadingTime.zip
 

Roslyn #1

$
0
0
معرفی Roslyn

سکوی کامپایلر دات نت یا Roslyn (با تلفظ «رازلین») بازنویسی مجدد کامپایلرهای VB.NET و #C توسط همین زبان‌ها است. این سکوی کامپایلر به همراه یک سری کتابخانه و اسمبلی ارائه می‌شود که امکان آنالیز زبان‌های مدیریت شده را به صورت مستقل و یا یکپارچه‌ی با ویژوال استودیو، فراهم می‌کنند. برای نمونه در VS.NET 2015 تمام سرویس‌های زبان‌های موجود، با Roslyn API جایگزین و بازنویسی شده‌اند. نمونه‌هایی از این سرویس‌های زبان‌ها، شامل  Intellisense و مرور کدها مانند go to references and definitions، به همراه امکانات Refactoring می‌شوند. به علاوه به کمک Roslyn می‌توان یک کامپایلر و ابزارهای مرتبط با آن، مانند FxCop را تولید کرد و یا در نهایت یک فایل اسمبلی نهایی را از آن تحویل گرفت.


چرا مایکروسافت Roslyn را تولید کرد؟

پیش از پروژه‌ی Roslyn، کامپایلرهای VB.NET و #C با زبان ++C نوشته شده بودند؛ از این جهت که در اواخر دهه‌ی 90 که کار تولید سکوی دات نت در حال انجام بود، هنوز امکانات کافی برای نوشتن این کامپایلرها با زبان‌های مدیریت شده وجود نداشت و همچنین زبان محبوب کامپایلر نویسی در آن دوران نیز ++C بود. این انتخاب در دراز مدت مشکلاتی مانند کاهش انعطاف پذیری و productivity تیم کامپایلر نویس را با افزایش تعداد سطرهای کامپایلر نوشته شده به همراه داشت و افزودن ویژگی‌های جدید را به زبان‌های VB.NET و #C سخت‌تر و سخت‌تر کرده بود. همچنین در اینجا برنامه نویس‌های تیم کامپایلر مدام مجبور بودند که بین زبان‌های مدیریت شده و مدیریت نشده سوئیچ کنند و امکان استفاده‌ی همزمان از زبان‌هایی را که در حال توسعه‌ی آن هستند، نداشتند.
این مسایل سبب شدند تا در طی بیش از یک دهه، چندین نوع کامپایلر از صفر نوشته شوند:
- کامپایلرهای خط فرمانی مانند csc.exe و vbc.exe
- کامپایلر پشت صحنه‌ی ویژوال استودیو (برای مثال کشیدن یک خط قرمز زیر مشکلات دستوری موجود)
- کامپایلر snippet‌ها در immediate window ویژوال استودیو

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

با توجه به این مسایل، مایکروسافت از بازنویسی سکوی کامپایلر دات نت این اهداف را دنبال می‌کند:
- بالا بردن سرعت افزودن قابلیت‌های جدید به زبان‌های موجود
- سبک کردن حجم کاری کامپایلر نویسی و کاهش تعداد آن‌ها به یک مورد
- بالا بردن دسترسی پذیری به API کامپایلرها
برای مثال اکنون برنامه نویس‌ها بجای اینکه یک فایل cs را به کامپایلر csc.exe ارائه کنند و یک خروجی باینری دریافت کنند، امکان دسترسی به syntax trees، semantic analysis و تمام مسایل پشت صحنه‌ی یک کامپایلر را دارند.
- ساده سازی تولید افزونه‌های مرتبط با زبان‌های مدیریت شده.
اکنون برای تولید یک آنالیز کننده‌ی سفارشی، نیازی نیست هر توسعه دهنده‌ای شروع به نوشتن امکانات پایه‌ای یک کامپایلر کند. این امکانات به صورت یک API عمومی در دسترس برنامه نویس‌ها قرار گرفته‌اند.
- آموزش مسایل درونی یک کامپایلر و همچنین ایجاد اکوسیستمی از برنامه نویس‌های علاقمند در اطراف آن.
همانطور که اطلاع دارید، Roslyn به صورت سورس باز در GitHub در دسترس عموم است.


تفاوت Roslyn با کامپایلرهای سنتی

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


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


خلاصه‌ی مراحل رخ داده در کامپایلر سی‌شارپ را در تصویر فوق ملاحظه می‌کنید. در اینجا ابتدا کار parse اطلاعات متنی دریافتی شروع می‌شود و از روی آن syntax tree تولید می‌شود. در مرحله‌ی بعد مواردی مانند ارجاعاتی به mscorlib و امثال آن پردازش می‌شوند. در مرحله‌ی binder کار پردازش حوزه‌ی دید متغیرها، اشیاء و اتصال آن‌ها به هم انجام می‌شود. در مرحله‌ی آخر، کار تولید کدهای IL و اسمبلی باینری نهایی صورت می‌گیرد.
با معرفی Roslyn، این جعبه‌ی سیاه، به صورت یک API عمومی در دسترس برنامه نویس‌ها قرار گرفته‌است:


همانطور که مشاهده می‌کنید، هر مرحله‌ی کامپایل جعبه‌ی سیاه، به یک API عمومی Roslyn نگاشت شده‌است. برای مثال Parser به Syntax tree API نگاشت شده‌است. به علاوه این API صرفا به موارد فوق خلاصه نمی‌شود و همانطور که پیشتر نیز ذکر شد، برای اینکه بتواند جایگزین سه نوع کامپایلر موجود شود، به همراه Workspace API نیز می‌باشد:


Roslyn امکان کار با یک Solution و فایل‌های آن را دارد و شامل سرویس‌های زبان‌های مورد نیاز در ویژوال استودیو نیز می‌شود. برفراز Workspace API، یک مجموعه API دیگر به نام Diagnostics API تدارک دیده شده‌است تا برنامه نویس‌ها بتوانند امکانات Refactoring جانبی را توسعه داده و یا در جهت بهبود کیفیت کدهای نوشته شده، اخطارهایی را به برنامه نویس‌ها تحت عنوان Code fixes و آنالیز کننده‌ها، ارائه دهند.

Roslyn #2

$
0
0
شروع به کار با Roslyn

Roslyn از زمان ارائه‌ی نگارش Visual Studio 14 CTP3 با ویژوال استودیو یکپارچه شد. بنابراین اگر از نگارش نهایی آن یعنی Visual Studio 2015 استفاده می‌کنید، اولین پیشنیاز کار با آن را در اختیار دارید. البته نگارش پیش نمایش آن نیز برای VS 2013 ارائه شد که در این تاریخ منسوخ شده درنظر گرفته می‌شود و دیگر به روز رسانی نخواهد شد. بنابراین نیاز است حتما Visual Studio 2015 را نصب کنید.
در حین نصب Visual Studio 2015 نیز باید گزینه‌ی نصب SDK آن‌را انتخاب کرده باشید، تا امکان تولید فایل‌های VSIX مرتبط را پیدا کنید. از این جهت که قالب‌های پروژه‌ی Roslyn، امکان تولید افزونه‌های آنالیز کدها را نیز میسر می‌کنند.
علاوه بر این‌ها نیاز است Syntax Visualizer و قالب‌های پروژه‌ی مخصوص Roslyn را نیز جداگانه نصب کنید. برای این منظور به آدرس ذیل مراجعه کرده و افزونه‌ی آن‌را نصب کنید.
NET Compiler Platform SDK.

پس از نصب این افزونه، دو قابلیت جدید به Visual studio اضافه می‌شوند:
الف) در منوی view، ذیل گزینه‌ی other windows، گزینه‌ی جدیدی به نام Syntax visualizer اضافه شده‌است.
ب) ذیل گزینه‌ی extensibility پنجره‌ی new project، تعدادی قالب پروژه‌ی جدید مخصوص Roslyn نصب شده‌اند.


البته باید دقت داشت که این قالب‌های جدید برای نمایش در این لیست، حداقل نیاز به انتخاب دات نت 4.5.2 را دارند.

از Syntax visualizer جهت مشاهده‌ی ریز جزئیات ساختار دستوری کدهای جاری استفاده می‌شود:


زمانیکه گزینه‌ی View->Other windows->Syntax visualizer را اجرا کردید، اندکی صبر کنید تا بارگذاری شود و سپس اشاره‌گر ویرایشگر را به قسمتی دلخواه حرکت دهید. در این حالت می‌توان ساختار کدها را بر اساس تفسیر Roslyn مشاهده کرد. همچنین اگر در Syntax visualizer یک نود را انتخاب کنید، بلافاصله معادل آن در ادیتور ویژوال استودیو انتخاب می‌شود. از این ابزار جهت تولید آنالایزرهای مبتنی بر Roslyn زیاد استفاده می‌شود.


تغییرات خط فرمان Visual Studio 2015 نسبت به نگارش‌های پیشین آن

خط فرمان Visual Studio 2015 به همراه کامپایلر قدیمی خط فرمان C# 5 و همچنین کامپایلر جدید مبتنی بر Roslyn مخصوص C# 6 است. این کامپایلرها را در مسیرهای ذیل می‌توانید پیدا کنید:
کامپایلر جدید مبتنی بر Roslyn در مسیر C:\Program Files (x86)\MSBuild\14.0\Bin قرار دارد و کامپایلر قدیمی C# 5 در مسیر نصب دات نت فریم ورک یعنی C:\Windows\Microsoft.NET\Framework\v4.0.30319 قرار گرفته‌است.


همانطور که مشاهده می‌کنید، نگارش کامپایلر مبتنی بر Roslyn، یک است و شماره build آن، بیانگر تاریخ کامپایل آن است:
50618=2015/06/18

اکنون شاید این سؤال مطرح شود که کامپایلر پیش فرض کدام است؟ برای یافتن آن، به منوی استارت ویندوز مراجعه کرده و گزینه‌ی developer command prompt for vs 2015 را اجرا کنید. در اینجا اگر دستور csc را اجرا کنید، خروجی آن همان کامپایلر مبتنی بر Roslyn است:


در همینجا اگر سوئیچ ? را برای مشاهده‌ی راهنمای کامپایلر خط فرمان Roslyn صادر کنید، یکی از گزینه‌‌های جدید آن، سوئیچ analyzer است:
 C:\Program Files (x86)\Microsoft Visual Studio 14.0>csc /?
Microsoft (R) Visual C# Compiler version 1.0.0.50618
Copyright (C) Microsoft Corporation. All rights reserved.


Visual C# Compiler Options

 /analyzer:<file list> Run the analyzers from this assembly
(Short form: /a)
آنالایزرها اسمبلی‌ها و افزونه‌هایی هستند که قابلیت وارد شدن به pipeline کامپایلر را داشته و امکان دخالت در صدور خطاها و اخطارهای صادر شده‌ی توسط آن‌را دارند. به عبارتی این کامپایلر خط فرمان جدید، افزونه پذیر است.
همچنین این کامپایلر نسبت به نگارش قبلی آن، دارای سوئیچ و گزینه‌ی parallel نیز می‌باشد که به کمک ساختارهای داده‌ی immutable جدید دات نت مسیر شده‌است.
Viewing all 271 articles
Browse latest View live