Skip to main content

ჰაფმენის კოდირება

Huffman Coding

        ჰაფმენის მეთოდი განეკუთვნება უდანაკარგო მონაცემთა შეკუმშვის ჯგუფს ( ის ასევე განეკუთვნება ენტროპიული კოდერების ჯგუფსაც ). მეთოდი იყენებს მეხსიერებაში მონაცემთა განაწილების სტატისტიკურ მაჩვენებლებს. მოვიყვანოთ მაგალითი: ვთქვათ მოცემული გვაქვს ტექსტი, რომელიც არის შენახული ტრადიციული მეთოდით(მაგალითად ASCII კოდებით), თითოეული სიმბოლო ჩაწერილია ერთ ბაიტში(რვა ბიტში). ამ შემთხვევაში ჩვენ მეხსიერებაში ვმოძრაობთ თანაბარი სიგრძის ნახტომებით და ამოგვაქვს მეხსიერებიდან შესაბამისი სიმბოლო ერთი ოპერაციით. მონაცემების შენახვის ასეთი მიდგომა ხშირ შემთხვევაში იწვევს მეხსიერების გაფლანგვას. ჩვენ შეგვიძლია დავაკვირდეთ ტექსტს და დავადგინოთ რომელი სიმბოლო გვხვდება უფრო ხშირად და რომელი ნაკლებად. მაშინ შევძლებთ რომ იმ სიმბოლოებს რომელიც ხშირად გვხვდება გამოვუყოთ უფრო მცირე ზომის ბიტების მიმდევრობა და რომელიც იშვიათად გვხვდება იმათ უფრო დიდი. კოდირების ამ ჯგუფს რომელიც თანაბარი ზომის ელემენყებისტან შემდგარ მიმდევრობას უსაბამებს, ცვლადი ზომის ელემენტების მიმდევრობას უწორებენ ცვლადი სიგრძით კოდირებას. ჰაფმენის მეთოდი გვეხმარება სწორედ ყოველი სიმბოლოსთვის ცვლადი სიგრძის ბიტური მიმდევრობების დადგენაში. ამ შემთხვევეში უკვე მეხსიერებიდან სიმბოლოს ამოღება ერთი ოპერაციით ვერ ხერხდება. ჩვენთვის ცნობილი არაა სიმბოლო, შესაბამისად არაა ცნობილი მისი სიგრძეც ამის გამო ჩვენ გვიწევს ბიტების მიმდევრობის განხილვა სათითაოდ. უნდა ავიღოთ პირველი ბიტი და მოვძებნოთ ჩვენს ანბანში შეესაბამება თუ არა ამ ერთ ბიტიან მიმდევრობას რაიმე სიმბოლო, თუ ვერ მოიძებნა შემდეგ უნდა დავამატოთ შემდეგი ბიტი და ვნახოთ ამ ორ ბიტიან მიმდევრობას შეესაბამება თუ არა სიმბოლო და ასე მანამ, სანამ არ ვიპოვით შესაბამისი მიმდევრობის მქონე სიმბოლოს და შემდეგ გადავალთ შემდეგი სიმბოლოს ამოცნობაზე. აქედან გამომდინარე ჩვენ ვერ ამოვიცნობთ i-ურ სიმბოლოს მანამ სანამ არ გვექნება ამოცნობილი ყველა სიმბოლო 0-დან i-მდე, რადგან ჩვენ ვერ ვიპოვით i-ური სიმბოლოს დასაწყისს ბიტების მიმდევრობაში. ასევე უნდა აღინიშნოს ის ფაქტი რომ არცერთი სიმბოლოს შესაბამისი ბიტური მიმდევრობა არ უნდა იყოს სხვა მიმდევრობის პრეფიქსი. აქვე განვმართოთ რას ვუწოდებთ პრეფიქსს. ბიტური მიმდევრობა A არის ბიტური მიმდევრობა B-ს  პრეფიქსი თუ B მიმდევრობა მიიღება A მიმდევრობის ბოლოში ბიტების დამატებით. ყველა ამ პირობის გათვალისწინებით ჩვენ შევძლებთ მოცემული ანბანის ორგანიზებას ორობითი ხის სახით. როგორც უკვე ვთქვით ჩვენი ამოცანაა შევადგინოთ ახალი ანბანი, ბაიტის ყველა შესაძლო მნიშვნელობას შევუსაბამოთ ბიტების გარკვეული უნიკალური მიმდევრობა. ალგორითმის მუშაობის პირველ ეტაპზე ხდება მოცემული საწყისი ბაიტების მიმდევრობის ანალიზი და ბაიტის(სიმბოლოს) ყველა მნიშვნელობისათვის სიხშირეების დადგენა, რომელიც შეგვიძლია წარმოვადგინოთ ცხრილის სახით სადაც ბაიტის თითოეულ მნიშვნელობას შეესაბამება გარკვეული სიხშირის მახასიათებელი (ის შეიძლება ასევე შევინახოთ ფარდობითი სიხშირის სახით თუმცა ამას რპინციპული მნიშვნელობა არ გააჩნია)
        ჰაფმენის მეთოდის საშუალებით ჩვენ ვახდენთ ორობითი ხის აგებას, რომლის ყოველი ფოთოლი შეიცავს ელემენტის საწყის მნიშვნელობას, ხოლო გზა ფოთლიდან ხის ძირამდე გვაძლევს მის ცვლადი სიგრძის ორობით კოდს. ინფორმაციის შენახვა ხის სახით გვეხმარება ლოგარითმულ დროში მივწვდეთ კოდირებული სიმბოლოს რეალურ მნიშვნელობას. ორობითი ხე არაბალანსირებულია.
სურათზე მოცემულია ჰაფმენის ორობითი ხე(სურათი აღებულია ვიკიპედიიდან), რომლის ფოთლებშიც შენახულია ელემენტები და მათი სიხშირეები.
        განვიხილოთ ჰაფმენის ორობითი ხის აგების მეთოდი. პირველ ეტაპზე შემოსული სიმბოლოები უნდა დავალაგოთ სიხშირის მაჩვენებლის მიხედვით(კლებადობით).
ავიღოთ ყველაზე დაბალი ალბათობის მქონე ორი სიმბოლო, შევქმნათ მათი მშობელი შიდა კვანძი მათი სიხშირეების ჯამით, ჩავამატოთ შვილი კვანძების მაგივრად და გადავალაგოთ სია ისე რომ ისევ კლებადობით იყოს დალაგებული. ეს პროცესი უნდა გავიმეოროთ სანამ ერთი კვანძი(ხის ფესვი) არ დაგვრჩება სიაში.
სურათზე ნაჩვენებია 4 სიმბოლო თავისი სიხშირეებით და ხის აგების პროცესი  ჰაფმენის მეთოდით.
პროგრამული იმპლემენტაცია
        პროგრამული კოდი დაწერილია C++-ზე:
#include <malloc.h>
#include <memory.h>
#include <search.h>
#include <string.h>

#pragma pack(push,1)

class HuffmanNode8
{
public:
        HuffmanNode8()
        {
                nFrequency = 0;
                byAscii = 0;
                dwCode = 0;
                nCodeLength = 0;
                pParent = pLeft = pRight = 0;
        }

        unsigned int nFrequency;
        unsigned char byAscii;
        unsigned int dwCode;
        unsigned short nCodeLength;
        HuffmanNode8 *pParent, *pLeft, *pRight;
};

#pragma pack(pop)


class Huffman
{
public:
        Huffman(){}
        ~Huffman(){}

        bool Compress( unsigned char* pSrc, int nSrcLen, unsigned char*& pDes, int& nDesLen );
        bool Decompress( unsigned char* pSrc, int nSrcLen, unsigned char*& pDes, int& nDesLen );
private:
        //ადარებს 2 ელემენტს სიხშირეების მიხედვით
        static int __cdecl frequencyCompare( const void* elem1, const void* elem2 );
        //ადარებს 2 ელემენტს მნიშვნელობების მიხედვით
        static int __cdecl CompareSymbol8( const void* elem1, const void* elem2 );
        HuffmanNode8* PopNode( HuffmanNode8* pNodes[], int nIndex, bool bRight );
        void SetNodeCode( HuffmanNode8* pNode );
        //აგებს ჰაფმენის ხეს
        int GetHuffmanTree( HuffmanNode8 nodes[], bool bSetCodes = true );
};




int __cdecl Huffman::frequencyCompare( const void* elem1, const void* elem2 )
{
        HuffmanNode8* pNodes[2] = { (HuffmanNode8*)elem1, (HuffmanNode8*)elem2 };

        if( pNodes[0]->nFrequency == pNodes[1]->nFrequency )
                return 0;

        return ( pNodes[0]->nFrequency < pNodes[1]->nFrequency )? 1 : -1;
}

int __cdecl Huffman::CompareSymbol8(const void* elem1, const void* elem2 )
{
        return ( ( (HuffmanNode8*)elem1 )->byAscii > ( (HuffmanNode8*)elem2 )->byAscii )? 1 : -1;
}

HuffmanNode8* Huffman::PopNode( HuffmanNode8* pNodes[], int nIndex, bool bit )
{
        HuffmanNode8* pNode = pNodes[ nIndex ];
        pNode->dwCode = bit;
        pNode->nCodeLength = 1;
        return pNode;
}

void Huffman::SetNodeCode( HuffmanNode8* pNode )
{
        HuffmanNode8* pParent = pNode->pParent;

        while( pParent && pParent->nCodeLength )
        {
                pNode->dwCode <<= 1;
                pNode->dwCode |= pParent->dwCode;
                pNode->nCodeLength++;
                pParent = pParent->pParent;
        }
}

int Huffman::GetHuffmanTree( HuffmanNode8 nodes[], bool bSetCodes )
{
        HuffmanNode8* pNodes[ 256 ], *pNode;

        //დავამატოთ შემოსული სიმბოლოები ჰაფმენის მიმდევრობაში
        int nNodeCount=0, nCount=0;
        for( nCount = 0; nCount < 256 && nodes[ nCount ].nFrequency; nCount++ )
                pNodes[ nNodeCount++ ] = &nodes[ nCount ];

        int nParentNode = nNodeCount, nBackNode = nNodeCount - 1;

        while( nBackNode > 0 )
        {
                //მშობელი კვანძი
                pNode = &nodes[ nParentNode++ ];

                //ამოვაგდოთ პირველი შვილი
                pNode->pLeft = PopNode(pNodes, nBackNode--, false);

                //ამოვაგდოთ მეორე შვილი
                pNode->pRight = PopNode(pNodes, nBackNode--, true);

                // adjust parent of the two poped nodes
                pNode->pLeft->pParent = pNode->pRight->pParent = pNode;

                // adjust parent frequency
                pNode->nFrequency = pNode->pLeft->nFrequency + pNode->pRight->nFrequency;

                // insert parent node depending on its frequency
                int i = 0;
                for( i = nBackNode; i >= 0; i-- )
                        if( pNodes[ i ]->nFrequency >= pNode->nFrequency )
                                break;

                memmove( pNodes+i+2, pNodes+i+1, (nBackNode-i)*sizeof(int) );
                pNodes[i+1] = pNode;
                nBackNode++;
        }

        //ხის ფოთლებს ჩავუწეროთ კოდები
        if( bSetCodes )
                for( nCount = 0; nCount < nNodeCount; nCount++ )
                        SetNodeCode( &nodes[ nCount ] );

        return nNodeCount;
}



bool Huffman::Compress( unsigned char* pSrc, int nSrcLen, unsigned char*& pDes, int &nDesLen )
{
        HuffmanNode8 nodes[ 511 ];

        //შევავსოთ ცხრილში მნიშვნელობები
        int nCount = 0;
        for( nCount = 0; nCount < 256; nCount++ )
                nodes[nCount].byAscii = (unsigned char)nCount;

        //დავითთვალოთ თითოეული მნიშვნელობის მოსვლის სიხშირეები
        nCount = 0;
        for( nCount = 0; nCount < nSrcLen; nCount++ )
                nodes[ pSrc[ nCount ] ].nFrequency++;

        //დავალაგოთ ჰაფმენის ცხრილი სიხშირეების კლების მიხედვით
        qsort( nodes, 256, sizeof(HuffmanNode8), Huffman::frequencyCompare );

        //ავაგოთ ჰაფმენის ხე ცხრილის მიხედვით
        int nNodeCount = GetHuffmanTree( nodes );

        int nNodeSize = sizeof(unsigned int) + sizeof(unsigned char);
        nDesLen = nSrcLen + nNodeCount*nNodeSize;

        //გამოვყოთ გამომავალი მეხსიერება pDes = (unsigned char*)malloc( nDesLen );
        unsigned char* pDesPtr = pDes;
        memset( pDesPtr, 0, nDesLen );

        //შევინახოთ საწყისი მონაცემების ზომა გამომავალი მონაცემების პირველ 4 ბაიტში
        ((unsigned int*)pDesPtr)[0] = (unsigned int)nSrcLen;
        pDesPtr += sizeof(unsigned int);

        //შევინახოთ ჰაფმენის ხის ფოთლების რაოდენობა
        pDesPtr[0] = (unsigned char)(nNodeCount-1);
        pDesPtr += sizeof(unsigned char);

        //ჩავწეროთ ჰაფმენის ხის ფოთლები
        for( nCount = 0; nCount < nNodeCount; nCount++ )
        {
                //კვანძები მასივში დალაგებულია სიხშირეების კლებით
                memcpy( pDesPtr, &nodes[ nCount ], nNodeSize );
                pDesPtr += nNodeSize;
        }

        //დავალაგოთ კვანძები მნიშვნელობების ზრდის მიხედვით
        //რათა შევძლოთ ინდექსით მიმართვა მასივში
        qsort(nodes, 256, sizeof(HuffmanNode8), Huffman::CompareSymbol8);


        //ჩავწეროთ შემოსული სიმბოლოების მიმდევრობა ახალი კოდებით
        unsigned int nDesIndex = 0;
        for( nCount = 0; nCount < nSrcLen; nCount++ )
        {
                *(unsigned int*)(pDesPtr+(nDesIndex>>3)) |= nodes[pSrc[nCount]].dwCode << (nDesIndex&7);
                nDesIndex += nodes[ pSrc[ nCount ] ].nCodeLength;
        }

        //დავითვალოთ შეკუმშული ინფორმაციის ზომა
        nDesLen = ( pDesPtr - pDes ) + ( nDesIndex + 7 )/8;

        //მეხსიერების რეალოკაცია მოვახდინოთ რეალურ ზომაზე
        pDes = (unsigned char*)realloc( pDes, nDesLen );

        return true;
}


bool Huffman::Decompress( unsigned char* pSrc, int nSrcLen, unsigned char* &pDes, int &nDesLen)
{
        //წავიკითხოთ მონაცემების სიგრძე რომელიც შენახულია მონაცემების დასწყისში
        nDesLen = *(unsigned int*)pSrc;

        //გამოვყოთ მეხსიერება გამომავალი მონაცემებისათვის
        pDes = (unsigned char*)malloc( nDesLen+1 );

        //სიგრძის შემდგომ მოდის კვანძების რაოდენობა
        int nNodeCount = *( pSrc + sizeof(unsigned int) ) + 1;

        //ინიციალიზაცია გავუკეთოთ ჰაფმენის ელემნტებს
        HuffmanNode8 nodes[511], *pNode;
        int nNodeSize = sizeof(unsigned int) + sizeof(unsigned char), nSrcIndex = nNodeSize;
        for( int nCount = 0; nCount < nNodeCount; nCount++ )
        {
                memcpy( &nodes[nCount], pSrc+nSrcIndex, nNodeSize );
                nSrcIndex += nNodeSize;
        }

        //ავაგოთ ჰაფმენის ხე
        GetHuffmanTree( nodes, false );

        //მოვძებნოთ ხის ძირი
        HuffmanNode8 *pRoot = &nodes[0];
        while( pRoot->pParent )
                pRoot = pRoot->pParent;

        int nDesIndex = 0;
        unsigned int nCode;

        //გარდავქმნათ ბაიტების ზომა ბიტების ზომაში
        nSrcIndex <<= 3;

        while( nDesIndex < nDesLen )
        {
                //წავიკითხოთ ბიტების მიმდევრობა (მხოლოდ პირველი 4 ბაიტი)
                nCode = ( *(unsigned int*)( pSrc + ( nSrcIndex >> 3 ) ) ) >> ( nSrcIndex&7 );

                //დავიწყოთ ძებნა ხეში ძირიდან და საკითხულ 4 ბაიტის თავში ვნახოთ
                //ჰაფმენის ორობითი კოდი
                pNode = pRoot;

                //ჩავიდეთ შვილებში სანამ მას ყავს მარცხენა შვილი
                //თუ კვანძს ყავს მარცხენა შვილი, მას ეყოლება მარჯვენაც
                while( pNode->pLeft )
                {
                        //თუ პირველი ბიტი არის 1 ჩავდივართ მარჯვენა შვილში, თუ 0 მარცხენაში
                        pNode = (nCode&1) ? pNode->pRight : pNode->pLeft;
                        //ვშლით პირველ ბიტს
                        nCode >>= 1;
                        //წაკითხული ბიტების მთვლელს ვზრდით 1-ით
                        nSrcIndex++;
                }
                //ნაპოვნი კვანძიდან(ფოთლიდან) წავიკითხოთ რეალური მნიშვნელობა
                //და გავზარდოთ ჩაწერილი ბაიტების მთვლელი
                pDes[ nDesIndex++ ] = pNode->byAscii;
        }

        return true;
}
        ჰაფმენის კოდირების მეთოდი ძალიან ფართოდ გამოიყენება როგორც ვიდეო, ასევე აუდიო კოდეკებში. მონაცემთა შეკუმშვის გარეშე უბრალოდ შეუძლებელი იქნებო ამხელა მოცულობის მონაცემების შენახვა.

Comments

Popular posts from this blog

CPU GPU და ჰიბრიდული რენდერერები

წყარო         დღემდე აქტუალურია თემა CPU რენდერერი ჯობია თუ GPU . იმისათვის რომ ამ კითხვას მეტნაკლებად ამომწურავი პასუხი გავცეთ განვიხილოთ რენდერერის სტრუქტურა და მოცემულ პლათფორმებზე იპმლემენტაციასთან დაკავშირებული პრობლემები. რენდერერი შედგება რამოდენიმე დიდი კომპონენტისგან როგორიცაა ხილვადობის ამოცანა შეფერადება ინტეგრატორები ფუნქციონალი ხილვადობის ამოცანა         ხილვადობის ამოცანა ერთერთი ყველაზე რთულია გამოთვლითი რესურსის კუთხით. გარდა იმისა, რომ სხივის გეომეტრიასთან თანაკვეთის დათვლას საკმაოდ დიდი დრო ჭირდება, ასევე საჭიროა ამაჩქარებელ სტრუქტურების განახლება კადრიდან კადრზე დინამიური სცენებისათვის. კარგი ისაა, რომ რენდერერის ეს ნაწილი საკმაოდ ადვილად ენკაპსულირებადია და შესაბამისად გვხვდება ბიბლიოთეკები მაგალითად embree(intel), fireRays(AMD), OptiX prime(nvidia), ... რომლებიც ამ ამოცანას საკმაოდ ეფექტურად ხსნიან და რენდერერებშიც მეტნაკლებად ადვილად ინტეგრირდებიან.  სხივების მიდევნების პროცესში ძალიან მნიშვნელოვანია მსგავსი გამოთვლების ლოკალიზება და არსებული SIMD

სინათლის ხილული სპექტრი და სხივის თვისებები

Visible Spectrum სურათზე ნაჩვენებია პრიზმაში გამავალი თეთრი სხივის სპექტრულად გაშლის პროცესი.         სინათლე წარმოადგენს ელექტრომაგნიტურ ტალღას, რომელსაც როგორც ყველა ელექტრომაგნიტურ ტალღას გააჩნია რამოდენიმე მნიშვნელოვანი მახასიათებელი. ერთერთი მნიშვნელოვანი მახასიათებელი არის ტალღის სიგრძე, რომელიც განსაზღვრავს სხივის სპექტრულ ფერს. ელექტრომაგნიტური ტალღები ბუნებაში და თანამედროვე სამყაროში მრავლად გვხვდები. სხვადასხვა ტალთის სიგრძის(სიხშირის) ტალღებს იყენებენ როგორც საყოფაცხოვრებო(რადიო, მობილური ტელეფონი) დანიშნულების, ასევე სამედიცინო(რენდგენის სხივები) და სამხედრო(რადარები) მოწყობილობებში. ადამიანის თვალისთვის ხილული სინათლის ელექტრომაგნიტური ტალღების ტალღის სიგრძე იწყება დაახლოებით 400 ნანომეტრიდან და მთავრდება 700 ნანომეტრზე. ამ დიაპაზონს ქვემოთ ექცევა ულტრაიისფერი ტალღები და დიაპაზონს ზემოთ ექცევა ინფრაწითელი, რომელსაც ადამიანის თვალი ვერ აღიქვამს(იხილეთ ქვემოთ მოცემული სურათი). სინათლის თეთრი სხივი შედგება სხვადასხვა სიხშირის ტალღების ერთობლიობისგან.        

ფერების RGB მოდელი

RGB Color Model         ფერების RGB მოდელი წარმოადგენს ისეთ მოდელს რომელშიც სამი ძრირითადი ფერის წითელი, მწვანე და ლურჯის საშუალებით მიიღება ფერების ფართო სპექტრი. მისი დასახელებაც მოდის სწორედ ძირითადი ფერების ინგლისური სახელწოდების ინიციალებიდან(Red, Green, Blue).         ფერთა სპექტრის ამდაგვარი წარმოდგენა დაკავშირებულია იმასთან, რომ გამოსახულების გამოტანის მოწყობილობებში რომელიც გააჩნიათ კომპიუტერებს, ტელევიზორებს ფერის მიღება ფიზიკურად ხდება სწორედ ამ სამი ძირითადი ფერის შეზავებით. დღესდღეობით ყველაზე გავრცელებული არის 24 ბიტიანი RGB მოდელი, სადაც თითოეულ კომპონენტს ეთმობა ერთი ბაიტი და შესაბამისად შეუძლია მიიღოს ნებისმიერი მნიშვნელობა [0, 255] დიაპაზონში, რაც საბოლოოდ გვაძლევს 16777216 განსხვავებულ ფერს.