Threads mit QtConcurrent



  • Hallo,

    zu meiner aktuellen Frage hatte ich bereits hier ein Thema eröffnet. Ich habe nun eine Frage zum Threading und der Performance.

    Ich konvertiere in meinem Programm einen Videostream von einer Kamera den ich mit der DeckLink SDK abgreife in den Typ cv::Mat von OpenCV. Ziel ist es mit cv::Mat die RGB-Werte auszulesen.

    Ich habe eine Funktion die diese Formatkonvertierung durchführen soll:

    cv::Mat ConvertVideoFormat::DeckLinkToCv(IDeckLinkVideoFrame* videoFrame) {
    
    	void* data; //void* means pointer to an undefined type
    	videoFrame->GetBytes(&data); //allows direct access to the data buffer of a video frame
    
    	int height = static_cast<int>(videoFrame->GetHeight());
    	int width = static_cast<int>(videoFrame->GetWidth());
    	//size_t step = static_cast<size_t>(inputFrame->GetRowBytes());
    
    	cv::Mat uyvy = cv::Mat(height, width, CV_8UC2, data);
    	cv::Mat bgr = cv::Mat(height, width, CV_8UC4);
    
    	cv::cvtColor(uyvy, bgr, cv::COLOR_YUV2RGB_UYVY);
    
    	qDebug() << "ConvertVideoFormat: DeckLinkToCv Thread-ID: " << QThread::currentThreadId();
    	return bgr;
    }
    

    DeckLinkToCv wird dann in der Slot-Funktion aufgerufen um hier den Funktionsparameter zu übergeben:

    void VideoScreen::ReceiveFrame(CComPtr<IDeckLinkVideoFrame> theFrame) {
    	if (m_previewHelper != nullptr) {
    		m_previewHelper->SetFrame(theFrame);
    
    		ConvertVideoFormat cvf;
    		m_future = QtConcurrent::run(&cvf, &ConvertVideoFormat::DeckLinkToCv, theFrame);
    
    		QWidget::update();
    	}
    }
    

    Damit die Formatkonvertierung nicht im main-Thread läuft, lagere ich diese in einen separten Thread aus. Das scheint im Grunde soweit auch zu funktionieren. Es gibt nur ein Problem. DeckLinkToCv läuft erst eine Zeit lang in einem Thread, teilt sich dann aber noch mal auf weitere Threads auf, so dass am Ende mehrere Threads parallel zueinander laufen. Ist das eine Eigenschaft von QtCurrent? Soll das so oder geschiet das weil die CPU voll ausgelastet ist (weil die Konvertierung evtl nicht ideal ist)?



  • Ich habe gerade herausgefunden das m_futuere.waitForFinished() das öffnen weiterer Threads verhindert.



  • @makopo Ich bin jetzt nicht der größte QT Experte, aber ich denke, du rufst ReceiveFrame mehrfach auf (auf jedem neuen Frame von der Kamera). Und jeder Aufruf von ReceiveFrame startet einen Thread (bzw. holt sich einen Thread aus dem globalen Qt Threadpool) und lässt DeckLinkToCv da drinnen laufen. Das können dann natürlich unterschiedliche Threads sein und es ist nicht garantiert, dass der letzte schon fertig ist.
    Aber DeckLinkToCv spaltet sich nicht in verschiedene Threads auf.

    Wenn es wichtig ist, dass der letzte Thread fertig ist, musst du vor der neuen Ausführung darauf warten, dass der abgeschlossen wird. Das kann man, wie du herausgefunden hast, mit waitForFinished()machen.



  • @makopo sagte in Threads mit QtConcurrent:

    Ich habe gerade herausgefunden das m_futuere.waitForFinished() das öffnen weiterer Threads verhindert.

    Wo rufst du das auf? Zeig etwas mehr Kontext. Kommt mir noch nicht so optimal vor.



  • @Schlangenmensch

    Oh stimmt, das habe ich nicht bedacht. Der Slot (und damit ReceiveFrame) wird ja jedes mal aufgerufen. Aber dort kann ich am Einfachsten die Frames abgreifen.

    Aber ja, das ist nicht optimal.
    waitForFinish() rufe ich mit der Instanz m_future auf.

    void VideoScreen::ReceiveFrame(CComPtr<IDeckLinkVideoFrame> theFrame) {
    	if (m_previewHelper != nullptr) {
    		m_previewHelper->SetFrame(theFrame);
    
    		ConvertVideoFormat cvf;
    		m_future = QtConcurrent::run(&cvf, &ConvertVideoFormat::DeckLinkToCv, theFrame);
                    m_future.waitForFinished();
    
    		QWidget::update();
    	}
    }
    


  • @makopo So bringt dir der neue Thread nix! Du startest den neuen Thread und wartest dann direkt auf die Beendigung. So kannst du das direkt im Hauptthread machen.



  • @Schlangenmensch

    Ok, danke. Tatsächlich fängt der Stream auch an zu ruckeln wenn ich waitForFinished aufrufe. Ohne Aufruf ist das nicht so und der Stream läuft einigermaßen flüssig.

    ReceiveFrame scheint dann ja nicht der beste Ort zu sein, um die empfangenen Frames an die Konvertierungsfunktion zu übergeben. Allerdings schaffe ich es auch nicht IDeckLinkVideoFrame* theFrame als Parameter an einer anderen Stelle zu übergeben, da theFrame sonst in jedem Fall immer 0 ist.

    Ich hatte es per Signal/Slot probiert, aber auch hier wird die Konvertierungsfunktion nicht aufgerufen.

    GraphicScreen::GraphicScreen(QWidget* parent) :
        QWidget(parent)
    {
        m_videoScreenHelper = new VideoScreenHelper(parent);
        QObject::connect(m_videoScreenHelper, &VideoScreenHelper::SendFrame, this, &GraphicScreen::DeckLinkToCv, Qt::QueuedConnection);
    }
    

    SendFrame sendet dabei IDeckLinkVideoFrame* theFrame an den Slot. Kann man das so umsetzten oder gibt es bessere Lösungen? Stehe da gerade echt auf dem Schlauch...

    EDIT: Sende IDeckLinkVideoFrame* theFrame nun an den Slot zur Konvertierung. Jetzt muss die Konvertierung meiner Meinung nach in einen anderen Thread ausgelagert werden?



  • DeckLinkToCv bekommt einen Zeiger als Parameter, das passt nicht. Das Frame muss am Leben erhalten werden. Also evtl. den CComPtr als Kopie übergeben.
    Ich würde spontan eher zu einem dedizierten Thread tendieren. Evtl. ein Thread, der einliest, konvertiert, und die Frames in einer Queue ablegt. Und der Hauptthread stellt sie dar oder wie auch immer.

    Kann es sein, dass du einen ziemlich langsamen Rechner hast? Wundert mich, dass das nicht in Echtzeit geht.



  • @Mechanics

    Ja, der Rechner ist ein wenig langsamer. Aber selbst wenn er das nicht wäre, würde es doch Sinn machen die Formatwandlung zwecks Perfomance in einem anderen Thread durchzuführen?
    Ich meine 25 Bilder die Sekunde nach cv::Mat konvertieren und dann auch noch die Pixelwerte auslesen lassen, dürfte doch für eine ganz gute Auslastung sorgen?



  • @makopo sagte in Threads mit QtConcurrent:

    Ich meine 25 Bilder die Sekunde nach cv::Mat konvertieren und dann auch noch die Pixelwerte auslesen lassen, dürfte doch für eine ganz gute Auslastung sorgen?

    Glaub ich eher nicht... Ich kann jetzt grad nichts mit Sicherheit behaupten. Ist schon paar Jahre her, dass ich was ähnliches gemacht habe, und ich kann mich auch nicht erinnern, was das jetzt ganz genau war und wie schnell das war. Aber so wie ich mich dran erinnere, war da auch Konvertierung nach OpenCv, dann ein bisschen Verarbeitung und paar einfache Effekte, und Ausgabe. Und das ging in Echtzeit.
    Das Einlesen/Konvertieren hatte ich aber über ffmpeg gemacht, mit Hardware-Unterstützung.



  • @Mechanics

    Okay, bin für jede Mini-Erfahrung dankbar...
    Also wenn ich die Pixelwerte für RGB von einer 300 x 200 Pixelgrafik auslesen lassen dauert das über 5 Sekunden bis jeder Pixel auf der Konsole ausgegeben wurde. Für einen 1080p25 Videostream ist das nicht ganz so cool.
    Schreit nach einem neuen Rechner, wobei ich meine mal gelesen zu haben das das auch am Compiler liegen könnte.



  • @makopo sagte in Threads mit QtConcurrent:

    bis jeder Pixel auf der Konsole ausgegeben wurde.

    Das hört sich doch eher nach dem Flaschenhals an. Was machst du da genau?



  • @makopo sagte in Threads mit QtConcurrent:

    @Mechanics

    Okay, bin für jede Mini-Erfahrung dankbar...
    Also wenn ich die Pixelwerte für RGB von einer 300 x 200 Pixelgrafik auslesen lassen dauert das über 5 Sekunden bis jeder Pixel auf der Konsole ausgegeben wurde. Für einen 1080p25 Videostream ist das nicht ganz so cool.
    Schreit nach einem neuen Rechner, wobei ich meine mal gelesen zu haben das das auch am Compiler liegen könnte.

    Das hört sich komisch an: Ich habe mal ein bisschen Bildverstehen auf 'ner 800 Mhz CPU gemacht, die in so 'n Fußballroboter eingebaut war. Da musste man ein bisschen aufpassen und hat z.B. nicht jeden Pixel verarbeitet, weil man auch noch andere Sachen machen musste, Aber 300 * 200 * 3 Werte auslesen und ausgeben über 5 Sekunden... Ist das die Ausgabe auf der Konsole? Das dauert schon mal. Wie lange dauert es, wenn du die alle, sagen wir, aufaddierst und nur einen Wert ausgibst? (Ich will die Ausgabe minimieren 😉 )

    Was für eine CPU hast du denn? Welchen Compiler benutzt du? Und, mit welchen Flags kompilierst du? Wie sieht der Code aus, mit dem du deine Messung gemacht hast?



  • @Mechanics sagte in Threads mit QtConcurrent:

    Das hört sich doch eher nach dem Flaschenhals an. Was machst du da genau?

    void GraphicScreen::ReadPixelData() {
    
        unsigned char r, g, b;
        cv::Mat frame = cv::imread("C://ImageTest/test.jpg");
    
        for (int x = 0; x < frame.rows; ++x)
        {
            cv::Vec3b* pixel = frame.ptr<cv::Vec3b>(x);
            for (int y = 0; y < frame.cols; ++y)
            {
                r = pixel[y][2];
                g = pixel[y][1];
                b = pixel[y][0];
    
                qDebug() << "R" << r  << "G" << g << "B" << b;
            }
        }
    }
    

    Das ist die Funktion mit der ich die Pixel auslese. Vielleicht ist die auch nicht besonders effizient. Und parallel läuft in meinem Programm kein anderer Prozess (also auch nicht das einlesen des Videostreams)

    @Schlangenmensch sagte in Threads mit QtConcurrent:

    Was für eine CPU hast du denn? Welchen Compiler benutzt du? Und, mit welchen Flags kompilierst du?
    CPU: i3 mit 3 GHz (ist mir ein wenig peinlich, aber der Rechner wurde mir gestellt, habe vorher ehrlich gesagt auch nicht darauf geachtet)
    Compiler: MSVC2019 C++17
    Flags: Bewusst habe ich keine Flags gesetzt. Ich habe über Qt ein makefile erstellt und in VS die Libraries eingebunden. Das war mein Setup. Laufen lassen tue ich das Programm im Debug Mode.

    Am Anfang des Projekts dachte ich ja auch das sowas theoretisch in Echtzeit funktionieren müsste. Aber dann setzt man die ersten Sachen um und merkt das es doch nicht so funktioniert wie geplant und kommt ins Zweifeln und in Erklärungsnot ^^.



  • @makopo sagte in Threads mit QtConcurrent:

    Das ist die Funktion mit der ich die Pixel auslese. Vielleicht ist die auch nicht besonders effizient.

    Lass mal das qDebug zum Test weg. Ich vermute, das braucht sehr viel mehr Zeit, als das Auslesen.



  • @makopo sagte in Threads mit QtConcurrent:

    Was für eine CPU hast du denn? Welchen Compiler benutzt du? Und, mit welchen Flags kompilierst du?

    CPU: i3 mit 3 GHz (ist mir ein wenig peinlich, aber der Rechner wurde mir gestellt, habe vorher ehrlich gesagt auch nicht darauf geachtet)
    Compiler: MSVC2019 C++17
    Flags: Bewusst habe ich keine Flags gesetzt. Ich habe über Qt ein makefile erstellt und in VS die Libraries eingebunden. Das war mein Setup. Laufen lassen tue ich das Programm im Debug Mode.

    Die CPU sollte für das bisschen ausreichen.
    Visual Studio setzt von sich aus ganz viele verschiedene Compilerflags, je nach verwendeter Konfiguration.

    Nachdem du QDebug, wie von @Mechanics vorgeschlagen, rausgenommen hast, kannst du die Konfiguration mal auf Release umstellen, kompilieren und ausführen. Das führt dazu, dass Visual Studio die Optimierungsflags setzt und ich meine auch, per Default Debugsymbole ausschaltet.

    Ich habe jetzt nicht in die Doku von qDebug geschaut, aber ich würde vermuten, dass das in der Release Konfiguration von QT aus, schon abgeschaltet wird.

    Dann ist noch interessant, wie OpenCV und QT kompiliert wurden. Da sollte man dann nämlich auch gegen die "Release" Libs linken.

    Generell gilt: Performance im Debug Build zählt nicht 😉



  • @Schlangenmensch sagte in Threads mit QtConcurrent:

    Ich habe jetzt nicht in die Doku von qDebug geschaut, aber ich würde vermuten, dass das in der Release Konfiguration von QT aus, schon abgeschaltet wird.

    Das sollte auch im Release Build funktionieren.



  • Am Anfang des Projekts dachte ich ja auch das sowas theoretisch in Echtzeit funktionieren müsste

    zwei schleifen übern memory um Bildpunkte einzeln auslesen .... glaub nicht.
    OpenCV kann Formatkonvertierungen in Echtzeit auch nur mit "Tricks" (simd / multimedia Befehlssätze (AVX) / GPU)

    Darf ich fragen für was die RGBWerte auslesen willst/musst? "Normal" macht man, wenn es echtzeit sein muss, alles blockweisse .....

    Aber wenn die Anforderung heisst, lese die Bildpunkte von einem Image einzeln aus ... wie soll das in Echtzeit gehen?

    Das sind bei dir schon mal 300 * 200 * 3 * 30 ? Aufrufe in der sekunde (mit einzelnem Farbkanal) .... wenn nur einen Aufruf pro pixel hasst ... und den rest wegoptimieren könntest.



  • @RHBaum sagte in Threads mit QtConcurrent:

    Das sind bei dir schon mal 300 * 200 * 3 * 30 ? Aufrufe in der sekunde (mit einzelnem Farbkanal)

    Ja und? Das sind nur 5.4 Mio Aufrufe. Bei mehreren Ghz Takt und 10-30 GB/s Ram Bandbreite.



  • Ich denke mal das das Problem war das qDebug() zeilenweise in der Schleife aufgerufen wurde. Stattdessen habe ich versucht, die Pixelwerte für jeden Kanal jeweils in einem Vector zu speichern. Diese Funktion rufe ich dann in der Funktion DrawGraphauf, die mir aus den Werten einen Graph zeichnen soll.
    Draw Graph wird dann über das Anklicken eines Button aufgerufen und der Graph wird sofort gezeichnet.
    Mit einem einzelnen Bild funktioniert es sehr schnell. Wie es mit 25 Bildern die Sekunde im HD Format aussieht kann ich noch nicht sagen, da ich mit der Formatkonvertierung noch nicht weitergegekommen bin.

    void GraphicScreen::ReadPixelData() {
        uint redPx, greenPx, bluePx;
        uint redSum, greenSum, blueSum;
    
        //hier wird ein in cv::Mat RGB konvertiertes Format übergeben
        cv::Mat frame = cv::imread("C://ImageTest/test.jpg");
    
        if (frame.empty())
            qDebug() << "No image found in ReadPixelData().";
    
        for (int r = 0; r < frame.rows; ++r) {
            cv::Vec3b* pixel = frame.ptr<cv::Vec3b>(r); //Vec3b defines a vector with 3 byte entries
    
            for (int c = 0; c < frame.cols; ++c) {
                redPx = pixel[c][2]; 
                greenPx = pixel[c][1];
                bluePx = pixel[c][0];
    
                //push single channel pixel into a vector
                m_redValues.push_back(redPx);
                m_greenValues.push_back(greenPx);
                m_blueValues.push_back(bluePx);
            }
        }
    }
    
    cv::Mat GraphicScreen::DrawGraph(int w, int h) {
        ReadPixelData();
        cv::Mat data(m_redValues);
        qDebug() << "Red:" << m_redValues;
        cv::Ptr<cv::plot::Plot2d> plot = cv::plot::Plot2d::create(data);
        cv::Mat image;
        plot->setPlotBackgroundColor(cv::Scalar(30, 30, 30));
        plot->setPlotAxisColor(cv::Scalar(30, 30, 30));
        plot->setPlotGridColor(cv::Scalar(30, 30, 30));
        plot->setPlotLineColor(cv::Scalar(0, 0, 255));
        plot->setShowText(false);
        plot->render(image);
        return image;
    }
    

    So etwas in der Art programmiere ich übrigens (nur ohne Manipulationsmöglichkeit) : Link


Anmelden zum Antworten