Ideen zur Beschleunigung des Codes (Camera Bild einlesen)



  • @Nukularfüsiker: Mit deinen Anmerkungen ist der Code insgesamt um einen Faktor 2 schneller geworden. Wo genau die Zeit eingespart wurde habe ich aber nicht nicht versucht zu untersuchen.

    Ich hätte noch ein paar Rückfragen:

    3DH schrieb:

    Hi,

    habe bereits seit ein paar Jahren die uEye-Kameras unter Qt (Linux+Win32) laufen.

    Du kannst dir das aufwendige Konvertieren sparen, indem du das Farbformat richtig wählst und ein QImage direkt aus den Rohdaten erstellst:

    kannst du mir auch sagen welche Farbformate dabei geeignet sind?

    1. Schritt: Farbformat der Kamera setzen und für später merken

    3DH schrieb:

    is_SetImageSize(m_hCamera, m_iImageWidth, m_iImageHeight);
        is_SetColorMode(m_hCamera, nColorMode);
    

    das mache ich sowieso schon.

    3DH schrieb:

    2. Schritt: Speicher (RingBuffer) für die Kamera einrichten

    m_pFrameBuffer = new UEYE_IMAGE[m_iBufferSize];
    
        UEYE_IMAGE *pFrame = m_pFrameBuffer;
        m_pLastFrame       = NULL;
    
        ret = is_ClearSequence(m_hCamera);
    
        for (int i = 0; i < m_iBufferSize; ++i, ++pFrame) {
            if (is_AllocImageMem(m_hCamera, m_iImageWidth, m_iImageHeight, m_iPixelDepth, &pFrame->pBuf, &pFrame->nImageID) != IS_SUCCESS)
                return false;
            if (is_AddToSequence(m_hCamera, pFrame->pBuf, pFrame->nImageID) != IS_SUCCESS)
                return false;
    
            pFrame->nImageSeqNum = i + 1;
            pFrame->nBufferSize = m_iImageWidth * m_iImageHeight * m_iPixelDepth / 8;
        }
    

    Warum ein RingBuffer? Ich habe bislang ohne gearbeitet. Den Buffer des Bildes kann ich ja auch mit einem einzelnen Bild auslesen.

    3DH schrieb:

    3. Schritt: auf Bild warten (is_FreezeImage()) und QImage erstellen

    INT   dummy;
        char *pLast = NULL, *pMem = NULL;
        // wait for new image
        is_FreezeVideo(m_hCamera, IS_WAIT);
        // get current frame
        is_GetActSeqBuf(m_hCamera, &dummy, &pMem, &pLast );
        // set current frame
        for (int i = 0; i < m_iBufferSize; ++i) {
            if (m_pFrameBuffer[i].pBuf == pLast) {
                m_pLastFrame = &m_pFrameBuffer[i];
                break;
            }
        }
    
        // create QImage directly from uEye buffer    
        QImage image((uchar*)m_pLastFrame->pBuf, m_iImageWidth, m_iImageHeight, m_iBytesPerLine, m_ImageFormat);
        image.setColorTable( m_RgbTable );
    }
    

    Wofür brauchst du das 'is_GetActSeqBuf(m_hCamera, &dummy, &pMem, &pLast );'
    Bei mir lese ich die Daten aus dem Buffer 'd->ImageMemory' den ich so zuweise

    int result =  is_AllocImageMem(	d->handleCamera,
                                    d->SizeX,
                                    d->SizeY,
                                    d->BitsPerPixel,
                                    &d->ImageMemory,
                                    &d->MemoryId);
        if( result == IS_SUCCESS )
        {
            is_SetImageMem (d->handleCamera, d->ImageMemory, d->MemoryId);	// set memory active
    

    Um das QImage mit deiner Methode zusammenzustellen fehlt mir noch eine Vorschrift zur Berechnung von 'm_iBytesPerLine' und ich weiß nicht wie sich 'm_RgbTable' zusammensetzt. Kannst du mir dazu weitere Informationen zukommen lassen?



  • Die Berechnung der aktuellen Pixelposition ist wg. multiplikation nicht besonders effizient:

    for ( int x = 0; x < m_nSizeX; x++ ) {
                for ( int y = 0; y < m_nSizeY; y++ ) {
                    // (x*bytes) + (y * sizex)
                    long lMemoryPixelOffset = ((long)x * m_nBytesPerPixel) + ((long)y * nPitch );
                    pPixelPointer = (reinterpret_cast<char*> (pMemVoid) + (lMemoryPixelOffset)) ;
    
                    pixelToRGB(pPixelPointer, r, g, b);
                    d->rgbMatrix.setPixel(x,y,r,g,b);
                }
            }
    

    Für jedes Pixel werden zwei Multiplikationen und zwei Additionen ausgeführt (plus die Inkremente der Schleifen). Besser ist es, wenn Du eine Datenstruktur bereit hälst, die Dein aktuelles Pixelformat abbildet:

    // Diese struct muss der organisation eines Pixels im Speicher entsprechen
    struct RGBPixel
    {
        unsigned char r;
        unsigned char g;
        unsigned char b;
        unsigned char x; // unused, 32bit alignment
    };
    
    // jetzt der Zugriff
    RGBPixel* imageIterator = getImageStart(); // Hier den Start des Bildes im Speicher bestimmen
    for( int y = 0; y < rows; y++ )
    {
        for( int x = 0; x < columns; x++ ) // hier wird imageStart immer um ein ganzes Pixel weiter geschoben
        {
            setPixel(imageIterator.r, imageIterator.g, imageIterator.b);
            imageIterator++;
        }
    }
    

    Hier ist jetzt nur noch eine Addition plus Addressbildung mit offset (in der Struktur). Das ist in der Regel deutlich schneller. Die setPixel Methode muss nicht geändert werden. DAs funktioniert so natürlich nur, wenn die Daten hintereinander im Speicher liegen.



  • Das dumme ist nur, dass die Daten sehr unterschiedlich organisiert sein können.
    Der Hersteller kennt ~20 Verfahren wie die Daten organisiert sein können.
    Selbst wenn ich es auf 3 sinnvolle reduziere (8 mono, 10 mono, 8 rgb),
    dann ist es nicht möglich das mit nur einer Struktur zu umzusetzen.
    und insbesondere die 10 Bit Strukturen machen Problem. Ansonsten hast du natürlich recht.



  • Du kannst Dir ja entsprechende Strukturen mit Zugriffsoperatoren anlegen. Dann hast Du eine einheitliche Schnittstelle und der Rest des Codes bleibt gleich. Das gilt für den von Dir beschriebenen Code ja ähnlich. Da muss ja in pixelToRGB() im switch-Statement jeweils eigener Code implementiert werden.

    Statt dessen könntest Du Dir eine Factory bauen, die die entsprechenden Klassen zusammenbaut und Dir zurückliefert. Das macht natürlich nur dann Sinn, wenn der Code im Moment noch nicht schnell genug ist.

    Ich habe beide Versionen bei mir mal ausprobiert (VisualStudio2008, ReleaseBuild):
    Mit Multiplikation auf 5MPixel großem Int-Array: 110ms mit Addition und Strukturen: 15ms

    Hier der Code:

    const int m_size_x = 1024;
    const int m_nSizeX = 1024;
    const int m_nSizeY = 1024;
    
    const int chunkSz = 5*m_nSizeX*m_nSizeY;
    int largeChunk[chunkSz];
    
    char red[chunkSz];
    char green[chunkSz];
    char blue[chunkSz];
    
    void pixelToRGB(char* pPixelPointer, int  & r, int & g, int & b)
    {
        r = static_cast<int>(*(reinterpret_cast<char*>(pPixelPointer+2)));
        g = static_cast<int>(*(reinterpret_cast<char*>(pPixelPointer+1)));
        b = static_cast<int>(*(reinterpret_cast<char*>(pPixelPointer+0)));
    }
    
    int ArrPos(int x, int y)
    {
        // y + height * x
        return (x + m_size_x * y);
    } 
    
    void setPixel(int x, int y, int r, int g, int b)
    {
        int pos = ArrPos(x,y);
        red[pos] = r;
        green[pos] = g;
        blue[pos] = b;
    }
    
    void checkPerfMult(void)
    {
        int m_nBytesPerPixel = 4;
        int nPitch = 4;
        void* pMemVoid = (void*)largeChunk;
        char* pPixelPointer;
        for ( int x = 0; x < m_nSizeX; x++ ) 
        {
            for ( int y = 0; y < m_nSizeY; y++ ) 
            {
                // (x*bytes) + (y * sizex)
                int r;
                int g;
                int b;
                long lMemoryPixelOffset = ((long)x * m_nBytesPerPixel) + ((long)y * nPitch );
                pPixelPointer = (reinterpret_cast<char*> (pMemVoid) + (lMemoryPixelOffset)) ;
    
                pixelToRGB(pPixelPointer, r, g, b);
                setPixel(x,y,r,g,b);
            }
        } 
    }
    
    void* getImageStart(void)
    {
        return largeChunk;
    }
    
    void checkPerfAdd(void)
    {
        // Diese struct muss der organisation eines Pixels im Speicher entsprechen
        struct RGBPixel
        {
            unsigned char r;
            unsigned char g;
            unsigned char b;
            unsigned char x; // unused, 32bit alignment
        };
    
        // jetzt der Zugriff
        RGBPixel* imageIterator = reinterpret_cast<RGBPixel*>(getImageStart()); // Hier den Start des Bildes im Speicher bestimmen
        for( int y = 0; y < m_nSizeY; y++ )
        {
            for( int x = 0; x < m_nSizeX; x++ ) // hier wird imageStart immer um ein ganzes Pixel weiter geschoben
            {
                setPixel(x, y, imageIterator->r, imageIterator->g, imageIterator->b);
                imageIterator++;
            }
        } 
    }
    
    int main( void )
    {
    	for(int i = 0; i<sizeof(testCases)/sizeof(testPattern); i++)
    	{
    		testCases[i].tstPowOfTwo();
    	}
        {
            Timer t;
            checkPerfMult();
        }
        {
            Timer t;
            checkPerfAdd();
        }
    }
    

    Die Timer-Klasse misst die Zeit per clock() . Bei Bedarf kann ich den Code posten.



  • Habs mal mit 100 Durchläufen nachgemessen:

    11,027s zu 0,313s. Das ist doch signifikant.



  • Hi,..

    Für so etwas gibt es die "Streaming Single Instruction Multible Data Extension" kurz SSE. Oder per GPGPU mittels VBO's.

    Damit kannst du Multiplikationen um etliches beschleunigen.
    Bei gängigen Graka's kommst Du damit bis auf 300 GFlops,... ( 300 * 10^9 floating point operations per second)

    Grüße



  • Das ist für Adressberechnungen aber mit Eulen auf Spatzen geschossen (oder Kanonen nach Athen getragen)

    Intelligente Algortihmen bringen mehr (s. mein Benchmark)



  • gastgast schrieb:

    Intelligente Algortihmen bringen mehr (s. mein Benchmark)

    nicht ganz weil:
    Wenn du es kombinierst, erreichst du einen deutlich höheren Faktor mehr weil mit SSE(1,2,3,4.x) und insbesondere GPGPU deutlich mehr aus gleichbleibendem System und Code herauszuholen ist.



  • Wenn du es kombinierst, erreichst du einen deutlich höheren Faktor mehr weil mit SSE(1,2,3,4.x) und insbesondere GPGPU deutlich mehr aus gleichbleibendem System und Code herauszuholen ist.

    Für allgemeine Verarbeitung gilt das ja, aber im Thread geht es nur um Adress- und Kopieroperationen.



  • ogni42 schrieb:

    Für allgemeine Verarbeitung gilt das ja, aber im Thread geht es nur um Adress- und Kopieroperationen.

    Ich könnte wetten dass durch zuschalten von SSE sich doch mehr rausholen lässt.



  • ogni42 schrieb:

    Wenn du es kombinierst, erreichst du einen deutlich höheren Faktor mehr weil mit SSE(1,2,3,4.x) und insbesondere GPGPU deutlich mehr aus gleichbleibendem System und Code herauszuholen ist.

    Für allgemeine Verarbeitung gilt das ja, aber im Thread geht es nur um Adress- und Kopieroperationen.

    Ich erinnere mich an einen Artikel in der Ct wo über die Performance von GPU Algorithmen geredet wurde. Und ein maßgeblicher Faktor war dabei die Zeit die benötigt wird die Daten in die GPU zu bringen. Solange sich hier nicht die gesamte Arithmetik auf Matrizen und massive parallel Verarbeitung abbilden lässt lohnt es sich daher gar nicht GPU zu verwenden.

    Bei SSE weiß ich nichteinmal wie man das programmiert.

    Da ich aber in wesentlichen Datenstrukturen in andere Datenstrukturen abbilde und eigentlich gar nicht rechnen will, denke ich das Vektor/Matrizen Algorithmen der falsch Ansatz sind.



  • pospiech schrieb:

    Ich erinnere mich an einen Artikel in der Ct wo über die Performance von GPU Algorithmen geredet wurde. Und ein maßgeblicher Faktor war dabei die Zeit die benötigt wird die Daten in die GPU zu bringen. Solange sich hier nicht die gesamte Arithmetik auf Matrizen und massive parallel Verarbeitung abbilden lässt lohnt es sich daher gar nicht GPU zu verwenden.

    Matrizen und Vektoren sind ja nichts anderes als Schleifenkonstrukte.

    for( int y = 0; y < m_nSizeY; y++ )
        {
            for( int x = 0; x < m_nSizeX; x++ ) // hier wird imageStart immer um ein ganzes Pixel weiter geschoben
            {
                setPixel(x, y, imageIterator->r, imageIterator->g, imageIterator->b);
                imageIterator++;
            }
        }
    

    Dieser Teil lässt sich mMn sehr gut parallelisieren.



  • Dieser Teil lässt sich mMn sehr gut parallelisieren.

    Ja, aber nicht mit SSE und schon gar nicht mit GPGPU.

    Mit SEE macht es keinen Sinn, da in die SSE Register ein komplettes Wort gelegt werden muss und als Ergebnis aber kein weiteres vollständiges Datenwort heraus kommt, sondern auf drei unterschiedliche Worte verteilt wird.

    Mit GPGPU macht das keinen Sinn, weil es ja nur um das Umkopieren der Daten geht und die liegen nun mal im Hauptspeicher des Rechners und nicht im Speicher der GPU. GPGPU beschleunigt im Wesentlich Matrixoperationen wie MulAdd, Inversion etc.

    Alternative wäre, die Schleife zu entrollen (z.B. mit OpenMP). Das kann, muss aber nicht zu schnellerer Verarbeitung führen. Das hängt von der Organisation der Caches ab. Dazu gibt es ein gutes Paper "What every programmer should know about memory".



  • ogni42 schrieb:

    Dieser Teil lässt sich mMn sehr gut parallelisieren.

    ....

    Diese Antwort bezog sich nur auf die Schleifen nicht auf SSE oder GPGPU

    Ja muss man halt durchprobieren, gibt ja nicht nur openmp..



  • Hi,...

    Also die doppelten schleifen durchläufe brauchen Vieeeel zu viel zeit,...
    Die Zeilen liegen bei beiden doch so oder so hintereinander im Speicher
    Und wie ich das verstanden habe muss nur ein Byte, und zwar x, entfernt werden.

    struct RGBX
    { 
      u8 r;
      u8 g;
      u8 b;
      u8 x;
    };
    
    struct RGB
    {
      u8 r;
      u8 g;
      u8 b;
    };
    
    RGBX* startmemRGBX= GetStartOfRGBX();
    RGB*  startmemRGB = GetStartOfRGB();
    
    size_t RGBXIndex =0;
    size_t RGBIndex  =0;
    
    for(int i=0; i<pixelanzahl; i++)
    {
    memcpy_s(startmemRGB+RGBIndex,sizeof(RGB),startmemRGBX+RGBXIndex,sizeof(RGBX));
    
    RGBXIndex++;
    RGBIndex++;
    };
    

    grüüße



  • Nein, im Ursprungscode wird das Bild in drei separate Felder geschrieben. Die Daten sind auch 24 bit weise organisiert.



  • Ja mein gott,..
    dann halt:

    u8* redmemory  = getStartOfRedMemory();
    u8* bluememory = getStartOfBlueMemory();
    u8 greenmemory = getStartOfGreenMemory();  
    
    RGBX* RGBXMemory = getStartOfRGBXMemory();
    
    size_t ColorIndex=0;
    size_t RGBXIndex=0;
    
    for( int i=0; i<number_of_pixels; i++)
    {
      memcpy_s(redmemory  + ColorIndex,1,((char*)RGBXMemory + RGBIndex) + 2,1);
      memcpy_s(bluememory + ColorIndex,1,((char*)RGBXMemory + RGBIndex) + 1,1);
      memcpy_s(greenmemory+ ColorIndex,1,((char*)RGBXMemory + RGBIndex) + 0,1);
    RGBXIndex++;
    ColorIndex++;
    }
    

    grüüße



  • zeusosc schrieb:

    Ja mein gott,..
    dann halt:

    memcpy_s(redmemory  + ColorIndex,1,((char*)RGBXMemory + RGBIndex) + 2,1);
      memcpy_s(bluememory + ColorIndex,1,((char*)RGBXMemory + RGBIndex) + 1,1);
      memcpy_s(greenmemory+ ColorIndex,1,((char*)RGBXMemory + RGBIndex) + 0,1);
    

    memcpy_s braucht man nicht 🙂
    Dann läuft das ganze noch einen Tacken schneller.



  • Die memcpy_s Version ist bei mir deutlich langsamer, die mit memcpy ein Drittel schneller:

    `

    Copying with Mul based address calculation: Time elapsed 11.236s

    Copying with Add based address calculation: Time elapsed 0.313s

    Copying with memcpy_s based address calculation: Time elapsed 4.844s

    Copying with memcpy based address calculation: Time elapsed 0.203s

    `

    Also macht memcpy das Rennen. Hab' ich wieder was gelernt, ich hatte vermutet, dass bei wortweisem Kopieren da kein Unterschied ist. Danke für den Tipp!



  • ogni42 schrieb:

    ...

    Könntest du grad den Code nochmal posten, so langsam verliert man den Überblick wo welche Veränderungen enthalten sind.

    0.2s je Frame ist doch ein wenig langsam.
    uEye cams können doch wohl mehr als 5frames/s?
    Oder ist es die Messung im Debug Modus?


Anmelden zum Antworten