گزیر

::گاهنوشتهای حمیدرضا محمدی::



دربارهٔ من:

آخرین نظردهندگان:

مشترک شوید:

ایمیل خود را در جعبهٔ زیر وارد کنید و دکمهٔ اشتراک را بزنید.

جستجو:

Valid XHTML 1.0 Transitional RSS Feed

کتابخانه‌ی تشخیص گوینده

فصل هشتم

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

۱- کتابخانه‌ی تشخیص صحبت – گوینده‌ی جیالانگ هی۱

کتابخانه‌ی مزبور که از طریق مرجع شماره‌ی ۱ قابل دستیابی است به زبان C++ نوشته شده و شامل حدود ۲۰ کلاس است که الگوریتمهای رایج استخراج خصیصه‌ها و تکنیکهای رایج برای تشخیص صحبت و تأیید هویت گوینده را پیاده‌سازی می‌نماید.

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

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

روشهای متداول مدلسازی صحبت شامل چندین روش مبتنی بر مدل نهان مارکف۶، مدل مقدار‌گزینی برداری۷، مدل گاوسی۸ و مدلسازی به شیوه‌ی چشمپوشی زمانی پویا۹ نیز در این کتابخانه وجود دارند.

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

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

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

۲- پایگاه داده‌های عبارتهای عبور- شمای کلی۱۰

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

برای نیل به هدف یاد شده ما کلاسی به نام HSpeaker برای نمایش هر عبارت گفته شده توسط کاربر خاص تعریف کرده‌ایم و وظیفه‌ی پردازش دسته‌ای از اشیاء از نوع این کلاس را به کلاسی به نام HSpeakersDB واگذار نموده‌ایم که البته این کلاس از یک کلاس و دو ساختار میانی برای پردازش اعمال مورد نظر استفاده می‌نماید. برنامه‌ی نهایی از کلاس HSpeakersDB برای پردازش نهایی استفاده می‌کند هر چند در بعضی موقعیتها ارجاعهای مستقیمی نیز به کلاس HSpeaker صورت پذیرفته است.

بخشهای بعدی ساختار درونی کلاسهای یاد شده را توضیح می‌دهد.

۳- عبارت عبور و پردازشهای مربوط به آن

هر عبارت عبور به همراه نام گوینده‌ی آن توسط شیئی از نوع کلاس HSpeaker قابل نمایش است:

class HSpeaker
{
public:
     HSpeaker();
     HSpeaker(SV_Model_DTW* pModel);
     ~HSpeaker();
     BOOL LoadFrom(CFile* pFile);
     BOOL SaveTo(CFile* pFile);
     void SetUserName(CString strName);
     CString GetUserName();
     void SetPassphrase(int nSamples, short* pSamples);
     double Verify(int nSamples, short* pSamples);
     double Verify(HSpeaker* pSpeaker);
     void PrepareFeature(SV_Feature_MFCC &Feature);
     void PrepareFeature(SV_Feature_Pitch &Feature);
     void PrepareFeature(SV_Feature_LPCC &Feature);
private:
     CString m_strName;
     SV_Data* m_pPassphrase;
     SV_Model_DTW* m_pModel;
};

کلاس SV_Data یکی از کلاسهای کتابخانه‌ی SVLib۱۱ است که نمایشگر یک ماتریس می‌باشد. نتیجه‌ی پردازشها و الگوهای به دست آمده از آنها در نهایت به داده‌هایی از این نوع تبدیل می‌شوند. عضو داده‌ی m_pPassphrase الگوی به دست آمده یا ذخیره شده را که اعمال مقایسه‌ای روی آن صورت می‌پذیرد نشان می‌دهد. علت تعریف این عضو به صورت اشاره‌گر آن است که مقدار خروجی متدهای استخراج خصیصه اشاره‌گر به این کلاس است و در ضمن در خود این متدها فضای حافظه‌ی لازم به داده‌ی مورد نظر تخصیص داده می‌شود.

کلاس SV_Model_DTW عمل مقایسه‌ی دو الگو را که به صورت اشیائی از نوع SV_Data نمایش داده می‌شوند انجام می‌دهد و عضو داده‌ی مورد نظر برای چنین امری در نظر گرفته شده است. علت تعریف این عضو داده به صورت اشاره‌گر و اختصاص فضای حافظه بدون آزاد کردن آن مشکلی بود که در استفاده از این عضو داده در محیط برنامه‌نویسی غیر متنی به واسطه‌ی خطای مراجعه به فضای حافظه‌ی غیر مجاز به وجود می‌آمد که به نظر می‌رسد ناشی از تلاش برای آزاد کردن حافظه‌ای که قبلاً آزاد شده در متد ویرانگر این کلاس می‌باشد و به لحاظ عدم وجود کد کتابخانه به این صورت رفع شده است.۱۲

عضو داده‌ی m_strName نام گوینده‌ی عبارت را ذخیره می‌کند که توسط متدهای SetUserName و GetUserName اعمال مقدارگذاری و دریافت مقدار آن صورت می‌پذیرد:

void HSpeaker::SetUserName(CString strName)
{
     m_strName=strName;
}
CString HSpeaker::GetUserName()
{
     return m_strName;
}

کلاس سازنده دارای دوشکل متفاوت است. علت آن است که در عمل مقایسه لازم نیست هر دو کلاس دارای عضو m_pModel باشند و تخصیص حافظه به هر دو نوعی هدر دادن حافظه است لذا اگر برای یک شیء برای این عضو داده حافظه اختصاص داده شود دیگری می‌تواند از حافظه‌ی تخصیص داده شده‌ی دیگری استفده نماید:

HSpeaker::HSpeaker()
{
     m_pModel=new SV_Model_DTW;
     m_pPassphrase=NULL;
     m_strName=“”;
}
HSpeaker::HSpeaker(SV_Model_DTW* pModel)
{
     m_pModel=pModel;
     m_pPassphrase=NULL;
     m_strName=“”;
}

در کلاس ویرانگر اگر حافظه‌ای به m_pPassphrase اختصاص داده شده باشد آزاد می‌گردد:

HSpeaker::~HSpeaker()
{
     if(m_pPassphrase)
              delete m_pPassphrase;
}

قبل از هر گونه عمل مقایسه بایستی عبارت عبور الگویابی شود این عمل با دریافت داده‌های صوتی که به صورت یک آرایه از نوع short دریافت می‌شود و تبدیل آن به الگوی مورد نظر در متد SetPassphrase صورت می‌پذیرد:

void HSpeaker::SetPassphrase(int nSamples, short* pSamples)
{
     SV_Feature_MFCC Feature;
     PrepareFeature(Feature);
     Feature.CopySignal(pSamples, nSamples);
     m_pPassphrase=Feature.ExtractFeature();
}

کلاس SV_Feature_MFCC نیز مربوط به کتابخانه‌ی SVLib است و پردازشهای لازم برای استخراج الگوهای MFCC از سیگنال صوت در آن قرار داده شده است. قبل از فراخوانی متدهای آن می‌بایست انتخابهای لازم انجام شود و این عمل با استفاده از متد PrepareFeature صورت می‌پذیرد:

void HSpeaker::PrepareFeature(SV_Feature_MFCC &Feature)
{
     Feature.Para.MFCC_Order = 12;
     Feature.Para.NFilter    = 60;
     Feature.Para.FFTSz      = 512;
     Feature.Para.DEnergy    = 1;
     Feature.Para.WinSz = 512;
     Feature.Para.StpSz = 256;
     Feature.Para.Alpha = 0.97;
     Feature.Para.HammingWin = 1;
     Feature.Para.RmvSilence = 1;
}

در این متد انتخابهای مربوط به نوع پنجره‌بندی (همینگ یا پنجره‌بندی مستطیلی)، اندازه‌ی پنجره، اندازه‌ی بازه‌ی fft ، انتخاب این که آیا انرژی سیگنال هم به الگو ضمیمه شود یا نه، انتخاب این که آیا سکوت از سیگنال حذف شود یا نه و… با مقدارگذاری اعضای داده‌ی عمومی کلاس SV_Feature انجام می‌پذیرد.

پس از مقدارگذاری موارد یاد شده متد CopySignal برای ورود سیگنال به شیء استخراج کننده‌ی الگو فراخوانی می‌شود. از آنجا که این متد و سایر متدهای مشابه کلاس SV_Featue_MFCC (وکلاسهای مشابه برای استخراج خصیصه‌ها از روشهای دیگر) یک آرایه از نوع short را به عنوان سیگنال ورودی می‌پذیرند همچنان که در فصل چهارم اشاره شد اعضای داده‌ی کلاسهای پردازش صوت را از این گونه انتخاب نمودیم. لذا در این مرحله نیازی به هیچ گونه تغییر نداریم.

بعد از انجام تمامی مقدار گذاریها متد ExtractFeature الگوهای خواسته شده را استخراج نموده حاصل را به صورت اشاره‌گر بازمی‌گرداند. همچنان که قبلاً اشاره شد اختصاص حافظه به صورت درونی صورت می‌پذیرد و نیازی به انجام این کار پیش از فراخوانی این متد وجود ندارد.

بعد از استخراج الگوها یا فراخوانی یک متد ساده از کلاس SV_Model_DTW می‌توان عمل مقایسه را انجام داد و حاصل را که یک عدد اعشاری نشان دهنده‌ی میزان فاصله‌ی دو الگو از یک دیگر است به محل فراخوانی بازگرداند:

double HSpeaker::Verify(HSpeaker* pSpeaker)
{
     return m_pModel->DTW_Comp(*m_pPassphrase, *pSpeaker->m_pPassphrase);
}

شکل دیگر این متد برای انعطاف‌پذیری بیشتر نوشته شده است:

double HSpeaker::Verify(int nSamples, short* pSamples)
{
     HSpeaker Temp(m_pModel);
     Temp.SetPassphrase(nSamples, pSamples);
     return Verify(&Temp);
}

برای ذخیره‌ی شیء از نوع HSpeaker ابتدا نام کاربر ذخیره می‌گردد و سپس از یک کلاس دیگر به نام SV_DataIO از کتابخانه‌ی SVLib برای ذخیره‌ی موقت الگو در یک فایل و به دست آوردن اندازه‌ی آن بر حسب بایت و سپس ذخیره‌ی این اندازه و الگوی به دست آمده در فایل مقصد استفاده می‌گردد و در نهایت این فایل موقتی نیز حذف می‌گردد:

BOOL HSpeaker::SaveTo(CFile* pFile)
{   
     BYTE bNameLength=(BYTE)m_strName.GetLength();
     pFile->Write(&bNameLength,sizeof(BYTE));
     for(int bc=0;bc<bNameLength;bc++)
     {
              char c=m_strName[bc];
              pFile->Write(&c,sizeof(char));
     }
     SV_DataIO Temp;
     Temp.OpenFile(“temp.sv”,WRITE_REC);
     Temp.PutDataRec(*m_pPassphrase);
     Temp.CloseFile();
     CFile TempFile(“temp.sv”, CFile::modeRead);
     BYTE byte;
     int iSize=0;
     while(TempFile.Read(&byte, sizeof(byte))==sizeof(byte))
                       iSize++;
     TempFile.Close();
     pFile->Write(&iSize, sizeof(int));
     TempFile.Open(“temp.sv”, CFile::modeRead);
     int i=0;
     while(i<iSize)
     {
              TempFile.Read(&byte, sizeof(byte));
              pFile->Write(&byte, sizeof(byte));
              i++;
     }
     TempFile.Close();
     DeleteFile(“temp.sv”);
     return TRUE;
}

برای بازیابی نیز ابتدا نام کاربر، سپس اندازه‌ی الگو و در نهایت خود الگو با استفاده از یک فایل موقتی بازیابی می‌گردد:

BOOL HSpeaker::LoadFrom(CFile* pFile)
{
     BYTE bNameLength;
     if(pFile->Read(&bNameLength, sizeof(BYTE)) != sizeof(BYTE))
              return FALSE;
     char* name=new char[bNameLength+1];
     pFile->Read(name,bNameLength*sizeof(char));
     name[bNameLength]=\0;
     m_strName=name;
     delete []name;
     int iSize;
     pFile->Read(&iSize, sizeof(int));
     Cfile TempFile(“temp.sv”, CFile::modeWrite|CFile::modeCreate);
     BYTE byte;
     for(int i=0; i<iSize; i++)
     {
              pFile->Read(&byte,sizeof(byte));
              TempFile.Write(&byte, sizeof(byte));
     }
     TempFile.Close();
     SV_DataIO Temp;
     Temp.OpenFile(“temp.sv”, READ_REC);
     m_pPassphrase=Temp.GetDataRec();
     Temp.CloseFile();
     DeleteFile(“temp.sv”);
     return TRUE;
}

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

۴- چند عبارت عبور برای یک کاربر و پردازشهای مربوط به آن

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

struct SpeakerPasses
{
          SpeakerPasses(){pNext=NULL;}
          HSpeaker* pSpeaker;
          SpeakerPasses* pNext;
          double Verify(HSpeaker* pSpeaker);
};

متد Verify متد هم نام خود را متعلق به عضو داده‌ی pSpeaker فراخوانی می‌کند و مقدار بازگشتی آن را می‌گرداند و با فراخوانی مستقیم این متد چندان تفاوتی ندارد.

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

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

struct SpeakersPasses
{
          CString strName;
          SpeakersPasses(){pNext=NULL;}
          SpeakerPasses* pFirst;
          SpeakersPasses* pNext;
          double Identify(HSpeaker* pSpeaker);
          double Find(HSpeaker* pSpeaker,int &Index);
};

هر عضو این زنجیره دارای نامی منحصر به فرد است که در عضو داده‌ی strName نگهداری می‌شود. یک اشاره‌گر به آغاز زنجیره‌ی عبارات عبور که با pFirst نشان داده می‌شود و یک اشاره‌گر به عضو بعدی در لیست کاربران که با pNext نشان داده می‌شود این ساختار را تشکیل می‌دهند.

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

double SpeakersPasses::Identify(HSpeaker* pSpeaker)
{
          double fMin=10;
          double d;
          int i=0;
          for(SpeakerPasses* p=pFirst; p!=NULL; p=p->pNext,i++)
          if((d=p->Verify(pSpeaker))<fMin)
                             fMin=d;
          return fMin;
}

این متد امکان مقایسه‌ی کلیه‌ی عبارات عبور تعریف شده به نام یک کاربر را با یک عبارت عبور فراهم می‌آورد.

در بخشهایی از کار ما می‌دانیم که یک عبارت متعلق به یک کاربر است و نیاز به این داریم که بدانیم دقیقاُ کدام عبارت متعلق به کاربر است. مثلاً زمانی که بخواهیم یک عبارت پذیرفته شده از یک کاربر را از لیست عبارتهای مورد نظر او حذف کنیم به چنین امکانی نیاز داریم. متد زیر چنین امکانی را فراهم می‌آورد:

double SpeakersPasses::Find(HSpeaker* pSpeaker, int &Index)
{
          double fMin=10;
          double d;
          int i=0;
          for(SpeakerPasses* p=pFirst; p!=NULL; p=p->pNext,i++)
                   if((d=p->Verify(pSpeaker))<fMin)
                   {
                             Index=i;
                             fMin=d;
                   }
          return fMin;
}

کد متد مزبور کاملاُ شبیه به متد Identify است و تنها یک پردازش اضافی در آن انجام می‌شود.

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

۵- ایجاد یک جدول از کاربران

در مرحله‌ی بعد نیاز به آن داریم که کلیه‌ی پردازشهای اشاره شده برای کاربران منفرد را در کنار هم قرار دهیم و به مجموعه‌ی کاربران به عنوان یک کل نگاه کنیم.

به این منظور کلاسی به نام HSpeakersTable با ساختار زیر طراحی شد:

class HSpeakersTable
{
public:
          HSpeakersTable(){m_pFirst=NULL;}
          void Insert(HSpeaker* pSpeaker);
          HSpeaker* Get(int iMain, int iSub=0);
          int GetSubsNum(int iMain);
          int GetIndex(CString str);
          int GetNum();
          void RemoveUser(int nIndex);
          BOOL RemPass(int nIndex, HSpeaker* pSpeaker, double fThreshold);
          int Identify(HSpeaker* pSpeaker, double fThreshold);
private:
          SpeakersPasses* m_pFirst;
};

تنها عضو داده‌ی این کلاس اشاره‌گر به آغاز لیست کاربران است که در حالتی که لیست خالی است مقدار آن برابر با NULL خواهد بود.

متدهای بعدی اعمال کار با لیست کاربران را پیاده‌سازی می‌نمایند. عملی که متد Insert انجام می‌دهد شامل مقایسه‌ی نام کاربران با نام وارد شده برای عبارت مورد نظر است. در صورتی که لیست تهی باشد مقایسه‌ای انجام نمی‌شود و تنها نود ابتدایی ایجاد می‌گردد:

void HSpeakersTable::Insert(HSpeaker* pSpeaker)
{
if(m_pFirst==NULL)
{
     m_pFirst=new SpeakersPasses;
     m_pFirst->strName=pSpeaker->GetUserName();
     m_pFirst->pFirst=new SpeakerPasses;
     m_pFirst->pFirst->pSpeaker=pSpeaker;
     return;
}
else
{
     int index;
          if( (index=GetIndex(pSpeaker->GetUserName())) !=  –1)
      {
          int i=0;
          for(SpeakersPasses* p=m_pFirst;  i<index; p=p->pNext,i++);
          for(SpeakerPasses* q=p->pFirst; q->pNext!=NULL; q=q->pNext);
          q->pNext=new SpeakerPasses;
          q->pNext->pSpeaker=pSpeaker;
     }
     else
          {
          for(SpeakersPasses* p=m_pFirst; p->pNext!=NULL; p=p->pNext);
          p->pNext=new SpeakersPasses;
          p->pNext->strName=pSpeaker->GetUserName();
          p->pNext->pFirst=new SpeakerPasses;
          p->pNext->pFirst->pSpeaker=pSpeaker;
    }
}
}

متد GetIndex که در متد قبلی فراخوانی شده است یک عمل مقایسه‌ی ساده را انجام می‌دهد و در صورت یافتن نام داده شده در لیست نام کاربران اندیس کاربر مورد نظر را برمی‌گرداند و در غیر این صورت مقدار ۱ – در خروجی این تابع نشانگر آن است که کاربر مورد نظر پیدا نشده و باید یک نود جدید ایجاد گردد:

int HSpeakersTable::GetIndex(CString str)
{
   int index=0;
   for(SpeakersPasses* p=m_pFirst; p!=NULL; p=p->pNext,index++)
        {
          if(str==p->strName)
                   return index;
        }
          return1;
}

تعداد کاربران را متد زیر با یک سری پردازش ساده روی لیست به دست می‌آید (متد GetNum) . اگر لازم داشته باشیم تعداد عبارات عبور یک کاربر را بدانیم از متد GetSubsNum کمک می‌گیریم و اگر بخواهیم با یک عبارت به طور مستقیم کار کنیم از متد Get کمک می‌گیریم.کد این متدها که یک سری پردازش ساده روی لیست انجام می‌دهند در ادامه آمده است.

int HSpeakersTable::GetNum()
{
          if(m_pFirst==NULL)
                   return 0;
          int Num=1;
          for(SpeakersPasses* p=m_pFirst; p->pNext!=NULL; p=p->pNext,Num++);
          return Num;
}
int HSpeakersTable::GetSubsNum(int iMain)
{
          if(m_pFirst==NULL)
                   return 0;
          int index=0;
          for(SpeakersPasses* p=m_pFirst; index<iMain; p=p->pNext,index++);
          index=1;
          for(SpeakerPasses* q=p->pFirst; q->pNext!=NULL; q=q->pNext,index++);
          return index;
}
HSpeaker* HSpeakersTable::Get(int iMain, int iSub)
{
          int index=0;
          for(SpeakersPasses* p=m_pFirst; index<iMain; p=p->pNext,index++);
          index=0;
          for(SpeakerPasses* q=p->pFirst; index<iSub; q=q->pNext,index++);
          return q->pSpeaker;
}

عمل حذف یک کاربر که اندیس آن را در آرایه‌ی لیست کاربران می‌دانیم نیز یک عمل ساده‌ی پردازش لیست است:

void HSpeakersTable::RemoveUser(int nIndex)
{
          if(nIndex==0)
          {
                   SpeakersPasses *p=m_pFirst;
                   m_pFirst=m_pFirst->pNext;
                   SpeakerPasses* q=p->pFirst;
                   for(SpeakerPasses* r=q->pNext; r!=NULL; r=r->pNext)
                   {
                             delete q;
                             q=r;
                   }
                   delete p;
                   return;
          }
          int i=1;
          for(SpeakersPasses* p=m_pFirst; i<nIndex; p=p->pNext,i++);
          SpeakersPasses *pp=p->pNext;
          p->pNext=pp->pNext;
          SpeakerPasses* q=pp->pFirst;
          for(SpeakerPasses* r=q->pNext; r!=NULL; r=r->pNext)
          {
                   delete q;
                   q=r;
          }
          delete pp;
}

متد Identify عمل بازشناسی کاربر را انجام می‌دهد. برای بازشناسی یک کاربر عبارت عبور داده شده به کمک متد Verify ساختار SpeakersPasses با تمامی لیست کاربران مقایسه می‌شود و مقدار تفاوت نزدیک‌ترین کاربر با مقدار آستانه‌ی داده شده مقایسه می‌گردد و در صورتی که از آن میزان کمتر باشد اندیس کاربر و در غیر این صورت مقدار -1 به محل فراخوانی بازگردانده می‌شود:

int HSpeakersTable::Identify(HSpeaker* pSpeaker, double fThreshold)
{
          double fMin=10,d;
          int index;
          int i=0;
          for(SpeakersPasses* p=m_pFirst; p!=NULL; p=p->pNext,i++)
          {
                   if((d=p->Identify(pSpeaker))<fMin)
                   {
                             fMin=d;
                             index=i;
                   }
          }
          if(fMin<=fThreshold)
                   return index;
          return1;
}

متد RemPass متدی است که به وسیله‌ی آن برنامه‌نویس می‌تواند کاربری را که دارای عبارت عبور داده شده است حذف کند. در صورتی که عمل موفقیت‌آمیز باشد یعنی کاربری با عبارت داده شده یافته شود و حذف گردد مقدار منطقی TRUE و در غیر این صورت مقدار FALSE به محل فراخوانی بازگردانده می‌شود. ملاحظه‌ی این که عبارت مزیور از ابتدای لیست حذف می‌شود یا نه از پردازشهای این متد است. همچنین فرض بر این است که هیچگاه تقاضای حذف عبارت عبور کاربری که تنها یک عبارت عبور دارد نخواهد شد. این متد برای یافتن عبارت مزبور از متد Find ساختار SpeakersPasses سود می‌جوید:

BOOL HSpeakersTable::RemPass(int nIndex, HSpeaker* pSpeaker, double fThreshold)
{
          int i=0;
          for(SpeakersPasses* p=m_pFirst; i<nIndex; p=p->pNext,i++);
          int Index=-1;
          if(p->Find(pSpeaker,Index)<=fThreshold)
          {
                   if(Index==0)
                   {
                             SpeakerPasses *t=p->pFirst;
                             p->pFirst=t->pNext;
                             delete t;
                   }
                   else
                   {
                             int i=0;
                             for(SpeakerPasses *q=p->pFirst; i<Index-1; q=q->pNext,i++);
                             SpeakerPasses *t=q->pNext;
                             q->pNext=t->pNext;
                             delete t;
                   }
                   return TRUE;
          }
          return FALSE;
}

۶- تشکیل و کار با پایگاه داده‌ها

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

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

class HSpeakersDB
{
public:
          int GetUsersNum();
          void AddUser(HSpeaker* pSpeaker);
          CString GetUserName(int index);
          int GetPassesNum(int index);
          void RemoveUser(int nIndex);
          int Identify(HSpeaker* pSpeaker);
          BOOL RemPass(int index, HSpeaker* pSpeaker);
          HSpeakersDB(BOOL bAutoLoad=FALSE);
          void LoadUsers();
          void StoreUsers();
private:
          void CreateUsersDB();
private:
          HSpeakersTable* m_pTable;
          CString m_strDBFileName;
private:         //user identification options:
          int m_nRepetitions;
          float m_fMinPassLength;
          double m_fThreshold;
public:
          void SetRepsNum(int iNum);
          int GetRepsNum();
          void SetMinPassLen(float fMin);
          float GetMinPassLen();
          void SetSecurityLevel(int nLevel);
          int GetSecurityLevel();
};

هفت متد ابتدایی تنها متدهای متناظر خود در عضو داده‌ی m_pTable را فراخوانی می‌کنند و دو متد آخر مقدار پارامتر fThreshold در متدهای متناظر را با عضو داده‌ی m_fThreshold که توسط متد SetSecurityLevel مقدار گذاری می‌شود جایگزین می‌کنند:

int HSpeakersDB::GetUsersNum()
{
          return m_pTable->GetNum();
}
void HSpeakersDB::AddUser(HSpeaker *pSpeaker)
{
          m_pTable->Insert(pSpeaker);
}
CString HSpeakersDB::GetUserName(int index)
{
          return m_pTable->Get(index)->GetUserName();
}
int HSpeakersDB::GetPassesNum(int index)
{
          return m_pTable->GetSubsNum(index);
}
void HSpeakersDB::RemoveUser(int nIndex)
{
          m_pTable->RemoveUser(nIndex);
}
int HSpeakersDB::Identify(HSpeaker* pSpeaker)
{
          return m_pTable->Identify(pSpeaker, m_fThreshold);
}
BOOL HSpeakersDB::RemPass(int index, HSpeaker* pSpeaker)
{
          return m_pTable->RemPass(index, Speaker,m_fThreshold);
}

متد SetSecurityLevel مقدار m_fThreshold را با انتخاب آن از یک لیست از مقادیر به دست آمده از تجربه مقدارگزینی می‌نماید و متد GetSecurityLevel عکس این عمل را انجام می‌دهد:

void HSpeakersDB::SetSecurityLevel(int nLevel)
{
          switch(nLevel)
          {
          case VERYHIGH:
                   m_fThreshold=0.3;
                   break;
          case HIGH:
                   m_fThreshold=0.5;
                   break;
          case MEDIUM:
                   m_fThreshold=0.7;
                   break;
          case LOW:
                   m_fThreshold=0.9;
                   break;
          case VERYLOW:
                   m_fThreshold=1.1;
                   break;
          }
}
int HSpeakersDB::GetSecurityLevel()
{
          int nLevel;
          switch(int(m_fThreshold*10))
          {
          case 3:
                   nLevel=VERYHIGH;
                   break;
          case 5:
                   nLevel=HIGH;
                   break;
          case 7:
                   nLevel=MEDIUM;
                   break;
          case 9:
                   nLevel=LOW;
                   break;
          case 11:
                   nLevel=VERYLOW;
                   break;
          }
          return nLevel;
}

اعضای داده‌ی m_nRepetitions و m_fMinPassLength در هیچکدام از متدهای کلاس (به غیر از متدهای مقدارگذار و بازگرداننده‌ی مقدار آنها) کاربرد عملی ندارند و تنها برای آن که در متدهای ذخیره و بازیابی در پایگاه داده‌های عبارات رمز ذخیره و یا از آن بازیابی شوند عضو این کلاس هستند.

متد StoreUsers کاربران و انتخابهای مربوط به آنها را درپایگاه داده‌ها ثبت می‌نماید:

void HSpeakersDB::StoreUsers()
{
   CFile UsersDB;
   if(UsersDB.Open(“USERS.DB”, CFile::modeWrite|CFile::modeCreate))
          {
                    UsersDB.Write(&m_nRepetitions, sizeof(m_nRepetitions));
          UsersDB.Write(&m_fMinPassLength, sizeof(m_fMinPassLength));
          UsersDB.Write(&m_fThreshold, sizeof(m_fThreshold));
                   int iNum=m_pTable->GetNum();
                   for(int i=0; i<iNum; i++)
                   {
                             int iSubs=m_pTable->GetSubsNum(i);
                             for(int j=0; j<iSubs; j++)
                                      m_pTable->Get(i,j)->SaveTo(&UsersDB);
                   }
                   UsersDB.Close();
          }
}

متد LoadUsrers کاربران ذخیره شده را بارگذاری می‌کند و در صورت عدم وجود، آن را به وجود می‌آورد:

void HSpeakersDB::LoadUsers()
{
          CFile UsersDB;
          m_nRepetitions=2;
          m_fMinPassLength=2.0;
          m_fThreshold=0.7;
          if(UsersDB.Open(“USERS.DB”,CFile::modeRead))
          {
                   if(UsersDB.Read(&m_nRepetitions, sizeof(m_nRepetitions))==sizeof(m_nRepetitions))
          if(UsersDB.Read(&m_fMinPassLength, sizeof(m_fMinPassLength))==sizeof(m_fMinPassLength))
                   UsersDB.Read(&m_fThreshold, sizeof(m_fThreshold));
                   BOOL Finished=FALSE;
                   while(!Finished)
                   {
                             HSpeaker* pSpeaker=new HSpeaker;
                             if(pSpeaker->LoadFrom(&UsersDB))
                                      m_pTable->Insert(pSpeaker);
                             else
                             {
                                      delete pSpeaker;
                                      Finished=TRUE;
                             }
                   }
          }
          else
                   CreateUsersDB();
}

متد CreateUsersDB فقط یک فایل خالی ایجاد می‌کند. انتخابها بعداً در این فایل نوشته خواهند شد.

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

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

۷- منابع فصل

1) Jialong He, SVLib Library, downloadable from http://tiger.la.asu.edu/personal.htm


*Jialong He

*raw

*Mel-scaled FFT based cepstrum [MFCC]

*LPC based cepstrum [LPCC]

*pitch

*Hidden Markov Model [HMM]

*Vector Quantization

*Gaussian Model

*Dynamic Time Wrapping

*pass phrase

*نامی که جیالانگ هی برای کتابخانه‌اش انتخاب کرده و ما از این پس با این نام از آن یاد خواهیم کرد.

*البته تلاش شد که با تماس با به وجود آوردنده‌ی این کتابخانه از خود وی برای رفع این مشکل کمک گرفته شود که متأسفانه پاسخی از ایشان دریافت نشد.