فصل پنجم - شناسایی الگو

بَک پروجکشن

بَک پروجکشن روشی برای ارزیابی میزان تطبیق توزیع بافت‌نگار یک تصویر با توزیع بافت‌نگار مبنا است.

به بیان ساده‌تر، به منظور اجرای بَک پروجکشن، بافت‌نگار ویژگی مورد نظر را حساب می‌کنیم و سپس از آن برای پیدا کردن این ویژگی در تصاویر جدید استفاده می‌کنیم. مثالا اگر اگر بافت‌نگار رنگ پوست را داشته باشیم، آن وقت می‌توانیم از آن برای پیدا کردن نواحی‌ای که احتمال دارد پوست باشد، در تصاویر جدید استفاده کنیم.

فرض کنید از روی تصویر زیر یک بافت‌نگار H-S مربوط به پوست به دست آورده‌ایم. این بافت‌نگار را بافت‌نگار مبنا قرار می‌دهیم. برای اینکه فقط بافت‌نگار قسمت پوست را به دست آوریم، چند ماسک به تصویر اعمال می‌کنیم و سپس بافت‌نگار را حساب می‌کنیم.

سمت چپ تصویر دست و سمت راست تصویر بافت‌نگار آن
سمت چپ تصویر دست و سمت راست تصویر بافت‌نگار آن

حالا فرض کنید تصویر زیر را به عنوان تصویر آزمایشی به کار ببریم:

سمت چپ تصویر آزمایشی دست و سمت راست تصویر بافت‌نگار آن
سمت چپ تصویر آزمایشی دست و سمت راست تصویر بافت‌نگار آن

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

  1. هر کدام از پیکسل‌های تصویر آزمایشی (یعنی $p(i,j)$) را در سطل متناظر با آن پیکسل ($h_{i,\ j},s_{i,\ j}$) قرار می دهیم.

  2. سطل ($h_{i,\ j},s_{i,\ j}$) در بافت‌نگار مبنا را جستجو می‌کنیم و مقدار آن را می‌خوانیم.

  3. این مقدار را در مکان $(i,j)$ تصویر جدید ذخیره می‌کنیم (این تصویر همان نتیجه بَک پروجکشن است). همچنین برای اینکه تصویر نهایی قابل دیدن باشد، باید آن را نرمال سازی کنیم.

با انجام مراحل بالا روی تصویر آزمایشی نتیجهٔ زیر را به دست می‌آوریم:

نتیجه بَک پروجکشن روی تصویر آزمایشی دست
نتیجه بَک پروجکشن روی تصویر آزمایشی دست

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

کد

#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"

#include <iostream>

using namespace cv;
using namespace std;

/// Global Variables
Mat src; Mat hsv; Mat hue;
int bins = 25;

/// Function Headers
void Hist_and_Backproj(int, void* );

/** @function main */
int main( int argc, char** argv )
{
    /// Read the image
    src = imread( argv[1], 1 );
    /// Transform it to HSV
    cvtColor( src, hsv, CV_BGR2HSV );

    /// Use only the Hue value
    hue.create( hsv.size(), hsv.depth() );
    int ch[] = { 0, 0 };
    mixChannels( &hsv, 1, &hue, 1, ch, 1 );

    /// Create Trackbar to enter the number of bins
    char* window_image = "Source image";
    namedWindow( window_image, CV_WINDOW_AUTOSIZE );
    createTrackbar("* Hue  bins: ", window_image, &bins, 180, Hist_and_Backproj );
    Hist_and_Backproj(0, 0);

    /// Show the image
    imshow( window_image, src );

    /// Wait until user exits the program
    waitKey(0);
    return 0;
}


/**
 * @function Hist_and_Backproj
 * @brief Callback to Trackbar
 */
void Hist_and_Backproj(int, void* )
{
    MatND hist;
    int histSize = MAX( bins, 2 );
    float hue_range[] = { 0, 180 };
    const float* ranges = { hue_range };

    /// Get the Histogram and normalize it
    calcHist( &hue, 1, 0, Mat(), hist, 1, &histSize, &ranges, true, false );
    normalize( hist, hist, 0, 255, NORM_MINMAX, -1, Mat() );

    /// Get Backprojection
    MatND backproj;
    calcBackProject( &hue, 1, 0, hist, backproj, &ranges, 1, true );

    /// Draw the backproj
    imshow( "BackProj", backproj );

    /// Draw the histogram
    int w = 400; int h = 400;
    int bin_w = cvRound( (double) w / histSize );
    Mat histImg = Mat::zeros( w, h, CV_8UC3 );

    for( int i = 0; i < bins; i ++ )
    { 
    rectangle( histImg, Point( i*bin_w, h ),
        Point( (i+1)*bin_w, h - cvRound( hist.at<float>(i)*h/255.0 ) ), 
        Scalar( 0, 0, 255 ), -1 );
    }

    imshow( "Histogram", histImg );
}

توضیح

در خط 22 تصویر ورودی را به HSV تبدیل می‌کنیم.

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

  1. &hsv: آرایه مبدأ که کانال‌ها از آن کپی می‌شوند.
  2. 1: تعداد کانال‌های آرایه مبدأ.
  3. &hue: آرایهٔ مقصد که کانال‌ها در آن کپی می‌شوند.
  4. 1: تعداد کانال‌های آرایه مقصد.
  5. ch[]={0,0}: آرایه از جفت اندیس‌ها که نشان دهندهٔ طریقه کپی کانال‌ها از آرایه مبدأ به آرایه مقصد است. در اینجا کانال صفر &hsv در کانال صفر &hue کپی می‌شود.
  6. 1: تعداد جفت اندیس‌ها است.

در سطر 48، تابع Hist_and_Backproj قرار دارد. این تابع آرگومان‌های مورد نیاز تابع calcHist را مقدار دهی می‌کند. تعداد سطل‌ها هم از طریق ترک بار ارسال می‌شوند.

در خطوط 56 و 57، ابتدا با استفاده از تابع calcHist بافت‌نگار را حساب و سپس آن را با استفاده از تابع normalize در بازهٔ 0 تا 255 نرمال می‌کنیم.

در خط 61 بَک پروجکشن تصویر hue را با استفاده از تابع calcBackProject به دست می‌آوریم. این تابع 8 آرگومان دارد که شما با همه آنها آشنا هستید (مشابه همان‌هایی هستند که برای محاسبهٔ بافت‌نگار به کار می‌بردیم)، فقط ماتریس backproj اضافه شده است که نتیجهٔ بَک پروجکشن تصویر ورودی (یعنی &hue) در آن ذخیره می‌شود.

خروجی

به عنوان ورودی از تصویر یک دست استفاده می‌کنیم. خروجی به صورت زیر خواهد بود. می‌توانید مقدار سطل را تغییر دهید و تأثیر آن را روی خروجی ببینید.

سمت چپ: تصویر ورودی- وسط: بافت‌نگار ناحیه دست- سمت راست: بَک پروجکشن تصویر ورودی
سمت چپ: تصویر ورودی - وسط: بافت‌نگار ناحیه دست- سمت راست: بَک پروجکشن تصویر ورودی