قسمت بعدي

قسمت قبلي

 

فهرست مطالب

صفحة فعاليتها

 

 

 

 

 

 

 

فصل هشتم

 

كتابخانة تشخيص گوينده

 

 

1- كتابخانة تشخيص صحبت – گويندة جيالانگ هي

2- پايگاه داده‌هاي عبارتهاي عبور – شماي كلي

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

4- چند عبارت عبور براي يك كاربر و پردازشهاي مربوط به آن

5- ايجاد يك جدول از كاربران

6- تشكيل و كار با پايگاه داده ها

7- منابع فصل

 

 

 

 

 

 

 

1-     كتابخانة تشخيص صحبت – گويندة جيالانگ هي[1]

 

كتابخانة مزبور كه از طريق مرجع شمارة 1 قابل دستيابي است به زبان C++ نوشته شده و شامل حدود 20 كلاس است كه الگوريتمهاي رايج استخراج خصيصه‌ها و تكنيكهاي رايج براي تشخيص صحبت و تأييد هويت گوينده را پياده‌سازي مي‌نمايد.

محتويات اين كتابخانه امكان پردازش صحبت به صورت بدون سرباره[2] و با سرباره در فرمتي خاص را به كاربر مي‌دهد و امكانات مختلفي را براي كار با فايلهاي حاصل كه حاصل اعمال يك الگوريتم به صورت يك الگو و يا يك مدل بر داده‌هاي ورودي است فراهم مي‌آورد.

در بحث مدلسازي سيگنال استخراج خصيصه‌ها خصيصه‌هاي مبتني بر الگوريتم فورية سريع با اندازة مل[3]، خصيصه‌هاي به دست آمده از روش پيشگويانة خطي[4]،  خصايص پوياي مبتني (دلتاها كه در فصل ششم توضيح داده شد) را به همراه زيري و بمي[5] صدا را با استفاده از اين كتابخانه به دست آورد.

روشهاي متداول مدلسازي صحبت شامل چندين روش مبتني بر مدل نهان ماركف[6]، مدل مقدار‌گزيني برداري[7]، مدل گاوسي[8] و مدلسازي به شيوة چشمپوشي زماني پويا[9] نيز در اين كتابخانه وجود دارند.

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

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

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

 

2-     پايگاه داده‌هاي عبارتهاي عبور[10]- شماي كلي

 

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

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

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

 

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

 

هر عبارت عبور به همراه نام گويندة آن توسط شيئي از نوع كلاس 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[11] است كه نمايشگر يك ماتريس مي‌باشد. نتيجة پردازشها و الگوهاي به دست آمده از آنها در نهايت به داده‌هايي از اين نوع تبديل مي‌شوند. عضو دادة m_pPassphrase الگوي به دست آمده يا ذخيره شده را كه اعمال مقايسه‌اي روي آن صورت مي‌پذيرد نشان مي‌دهد. علت تعريف اين عضو به صورت اشاره‌گر آن است كه مقدار خروجي متدهاي استخراج خصيصه اشاره‌گر به اين كلاس است و در ضمن در خود اين متدها فضاي حافظة لازم به دادة مورد نظر تخصيص داده مي‌شود.

كلاس SV_Model_DTW عمل مقايسة دو الگو را كه به صورت اشيائي از نوع SV_Data نمايش داده مي‌شوند انجام مي‌دهد و عضو دادة مورد نظر براي چنين امري در نظر گرفته شده است. علت تعريف اين عضو داده به صورت اشاره‌گر و اختصاص فضاي حافظه بدون آزاد كردن آن مشكلي بود كه در استفاده از اين عضو داده در محيط برنامه‌نويسي غير متني به واسطة خطاي مراجعه به فضاي حافظة غير مجاز به وجود مي‌آمد كه به نظر مي‌رسد ناشي از تلاش براي آزاد كردن حافظه‌اي كه قبلاً آزاد شده در متد ويرانگر اين كلاس مي‌باشد و به لحاظ عدم وجود كد كتابخانه به اين صورت رفع شده است[12].

عضو دادة 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;

}

 

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

 

4-    چند عبارت عبور براي يك كاربر و پردازشهاي مربوط به آن

 

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

 

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 است و تنها يك پردازش اضافي در آن انجام مي‌شود.

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

 

5-    ايجاد يك جدول از كاربران

 

در مرحلة بعد نياز به آن داريم كه كلية پردازشهاي اشاره شده براي كاربران منفرد را در كنار هم قرار دهيم و به مجموعة كاربران به عنوان يك كل نگاه كنيم.

به اين منظور كلاسي به نام 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 كه در متد قبلي فراخواني شده است يك عمل مقايسة ساده را انجام مي‌دهد و در صورت يافتن نام داده شده در ليست نام كاربران انديس كاربر مورد نظر را برمي‌گرداند و در غير اين صورت مقدار -1 در خروجي اين تابع نشانگر آن است كه كاربر مورد نظر پيدا نشده و بايد يك نود جديد ايجاد گردد:

 

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;

        }

          return -1;

}

 

تعداد كاربران را متد زير با يك سري پردازش ساده روي ليست به دست مي‌آيد (متد 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;

          return -1;

}

 

متد 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;

}

 

 

6-    تشكيل و كار با پايگاه داده‌ها

 

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

اعمال كار با فايل كه نهايت استفاده از اين كتابخانه است در كلاس 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();

}

 

متد CreatUsersDB فقط يك فايل خالي ايجاد مي‌كند. انتخابها بعداً در اين فايل نوشته خواهند شد.

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

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

 

7-    منابع فصل

 

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

 

 

 

 

 

 



[1] Jialong He

[2] raw

[3] Mel-scaled FFT based cepstrum (MFCC)

[4] LPC based cepstrum (LPCC)

[5] pitch

[6] Hidden Markov Model (HMM)

[7] Vector Quantization

[8] Gaussian Model

[9] Dynamic Time Wrapping

[10] pass phrase

1 نامي كه جيالانگ هي براي كتابخانه‌اش انتخاب كرده و ما از اين پس با اين نام از آن ياد خواهيم كرد.

2 البته تلاش شد كه با تماس با به وجود آوردندة اين كتابخانه از خود وي براي رفع اين مشكل كمك گرفته شود كه متأسفانه پاسخي از ايشان دريافت نشد.