OpenCV Frame Farbanteile (channels)

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Benutzeravatar
Felix92
User
Beiträge: 133
Registriert: Mittwoch 7. November 2018, 17:57

Huhu,
gibt es eine Möglichkeit aus einem Video jeden Frame auszulesen ob er grüner, blauer oder roter ist ?
Also quasi wenn Frame mehr grüne Anteile als blaue oder rote hat dann do ...
Meine Idee:
green =100 * channel[1] / (channel[0] + channel[1] + channel[2])
if green > 50:
do...
Oder gibt es da vlt. sogar schon eine Methode welche mir den prozentualen Anteil (RGB) aus einem Frame zurück gibt ?
In der docu bin ich leider nicht fündig geworden.
MfG Felix
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das klingt nach einem x/y-Problem. Was willst du eigentlich erreichen?
Benutzeravatar
Felix92
User
Beiträge: 133
Registriert: Mittwoch 7. November 2018, 17:57

Naja ich hab ein Video wo manchmal auf die "Schultafel" gezoomt wird dementsprechend höherer grün Anteil wenn das der Fall ist soll halt etwas anderes passieren als wenn nicht auf die Tafel gezoomt wird bzw wenn Tafel wird mit openshot ein Clip über die Zeit wo er an der Tafel ist erstellt und wenn das nicht der Fall ist passiert nichts. Dient quasi dazu um ein Video zu analysieren.
Benutzeravatar
ThomasL
User
Beiträge: 1378
Registriert: Montag 14. Mai 2018, 14:44
Wohnort: Kreis Unna NRW

wie wäre es mit der Berechnung und Vergleich des mean() über den jeweiligen RGB-Channel?
Ich bin Pazifist und greife niemanden an, auch nicht mit Worten.
Für alle meine Code Beispiele gilt: "There is always a better way."
https://projecteuler.net/profile/Brotherluii.png
Benutzeravatar
Felix92
User
Beiträge: 133
Registriert: Mittwoch 7. November 2018, 17:57

Danke ThomasL das sollte funktionieren :)
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ich würde das so nicht machen. Für Betrachtungen von Farben würde ich in HSV wechseln, und dann mit der passenden Farbe in einem gewissen Bereich filtern. Der entstehende Blob wird dann per kontour extraction ausgemessen in seiner Größe.

Arbeiten auf den RGB-Werten zur Farbbeurteilung ist nahezu nie erfolgreich, weil der Farbraum schlecht Distanzen zu anderen Farben berechenbar macht.
Benutzeravatar
Felix92
User
Beiträge: 133
Registriert: Mittwoch 7. November 2018, 17:57

Huhu deets,
das ganze Problem dabei ist das relativ häufig und umständlich gezoomt wird weshalb es nicht möglich ist einen festen Bereich zu setzen.
(Das hatte ich davor hat auch super geklappt bis ich dann erfahren habe das dort auch gezoomt wird :idea: )
Also so wie ich es jetzt habe klappt es (auch mit zoom) bin natürlich gerne offen für Vorschläge und alternative Ideen. :D
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Wenn’s klappt, schön. Mein Ansatz hat mit zoomen an sich erstmal nix zu tun und ist robuster bei beleuchtungsveränderungen (in gewissem Rahmen), aber wenn du was hast, das tut - benutz es.
Benutzeravatar
Felix92
User
Beiträge: 133
Registriert: Mittwoch 7. November 2018, 17:57

Nun habe ich allerdings gemerkt das nen 90min Video beim "einlesen" echt ewig braucht gibt es dort einen simpleren Weg als das Video in einem Thread "vorzuladen" ?
Nur jeden 2ten Frame oder ähnliches zu nehmen ist leider keine Alternative.
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Nein. Du kannst probieren andere Tools wir ffmpeg du benutzen, wenn der Frame dann ein numpy Array ist, ist OpenCv zufrieden.
Benutzeravatar
Felix92
User
Beiträge: 133
Registriert: Mittwoch 7. November 2018, 17:57

Ok ich habe dazu gerade etwas gefunden allerdings verstehe ich von dem ffmpeg Ausdruck fast nichts :/
import subprocess

c = 'ffmpeg -y -i ' + video_input_path + ' -r 30 -s 112x112 -c:v libx264 -b:v 3M -strict -2 -movflags faststart '+video_output_path

subprocess.call(c, shell=True)

Könnte mir dazu vlt. jemand kurz erklären wie ich diese ffmpeg Ausdrücke zu handhaben habe :D
Letztendlich möchte ich ja nur die fps eines Videos welches ich schon eingelesen habe erhöhen Standard sind 21fps ich würde diese wenn möglich gerne auf 500-1000 fps drücken oder noch höher wenn möglich ohne Verluste.
Danke für eure Antworten :)
MfG Felix
Sirius3
User
Beiträge: 18270
Registriert: Sonntag 21. Oktober 2012, 17:20

@Felix92: Du hast also ein Programm, das wir nicht kennen, das zu lange für die Analyse eines Videos braucht. Dann hast Du entweder ein Problem, das lange braucht, oder einen ungeeigneten Algorithmus benutzt, oder diesen ganz einfach ineffizient programmiert.
Da hilft ein schnellerer Rechner, oder in den zwei letzten Fällen, geschickter Programmieren.
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Es geht um vorspulen. Um beim testen denke ich mal bestimmte stellen anzufahren.
Benutzeravatar
Felix92
User
Beiträge: 133
Registriert: Mittwoch 7. November 2018, 17:57

Also hier mal wie ich das Video "erstelle":

Code: Alles auswählen

def large_video_cut(self, fps):
        """
        a method to get the part of the speaker from the "main video" and save it in the project folder
        and create a object of this
        """

        video_file = self.video_data
        folder = Path(self.folder_path, self.folder_name)

        cap = cv2.VideoCapture(str(video_file))

        large_video_name = 'board_video.mp4'
        fourcc = cv2.VideoWriter_fourcc(*'mp4v')
        out = cv2.VideoWriter(os.path.join(folder,str(large_video_name)), fourcc , fps, (938, 530))

        if(cap.isOpened() == False):
            print("Error opening video stream or file")

        while(cap.isOpened()):
            ret, frame = cap.read()
            if ret == True:
                frame = frame[275:805, 17:955]
                out.write(frame)

            else:
                break
        cap.release()
        out.release()
        cv2.destroyAllWindows()
        new_large_video_path = Path(folder, large_video_name)
        self.files.append(new_large_video_path)
        return BoardVideo(out)
Und die Analyse:

Code: Alles auswählen

def board_area(self, clip_prefix):
        """
        a method that analyse the video frame per frame and save the Clips (Board) in a list
        """
        video = cv2.VideoCapture(str(self.file_path))
        try:
            times = list()
            clip_numbers = count()

            for frame_number in count():
                is_ok, frame = video.read()

                if not is_ok:
                    break

                average = cv2.mean(frame)
                sum = average[0] + average[1] + average[2]
                percentage_green = (100 * average[1]) / sum

                if percentage_green > 40:
                    times.append(video.get(cv2.CAP_PROP_POS_MSEC) / 1000)
                else:
                    if times:
                        clip = openshot.Clip(
                                '{}{}'.format(clip_prefix, next(clip_numbers))
                            )
                        clip.Start(times[0])
                        clip.End(times[-1])
                        self.subvideos.append(clip)
                        times.clear()

        finally:
            video.release()
            cv2.destroyAllWindows()
In der zweiten Methode würde ich das Video gerne wesentlich schneller "abarbeiten"
Benutzeravatar
__blackjack__
User
Beiträge: 14044
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Felix92: Bezüglich des `ffmpeg`-Aufrufs: Das ``shell=True`` sollte man nicht verwenden, sondern den Befehl und die Argumente als Liste übergeben:

Code: Alles auswählen

    command = [
        'ffmpeg',
        '-y',
        '-i', video_input_path,
        '-r', '30',
        '-s', '112x112',
        '-c:v', 'libx264',
        '-b:v', '3M',
        '-strict', '-2',
        '-movflags', 'faststart',
        video_output_path,
    ]
    subprocess.run(command)
`video_data` ist kein guter Name für einen Dateinamen/Pfad – da erwartet man die Videodaten selbst oder Daten über ein Video. Und das Attribut dann lokal an einen anderen Namen zu binden ist auch nicht schön. Zumal `video_file` auch etwas verwirrend ist, denn um ein Dateiobjekt handelt es sich ja auch nicht.

Der Zieldateiname in `large_video_cut()` – was eher `cut_large_video()` heissen sollte – wird über die Methode verteilt und zwei mal auf unterschiedliche Arten erstellt. Das sollte nur *einmal* passieren und entweder mit `Path` oder mit `os.path.join()`.

Weder ``if`` noch ``while`` sind Funktionen, also sollte man die auch nicht so schreiben als wären es welche. Die Klammern gehören da nicht hin.

Vergleiche mit literalen Wahrheitswerten macht man nicht. Da kommt nur wieder ein Wahrheitswert heraus, also kann man auch gleich den Wert nehmen mit dem man vergleicht oder mit dessen Negation mit ``not``. Bei `isOpened()` machst Du das ja auch schon einmal richtig. Warum diese Inkonsistenz?

Der Test ob `cap` geöffnet werden konnte, sollte eigentlich stattfinden bevor man mit allem weiteren weiter macht und vor allem auch verhindern das weiter gemacht wird, wenn das Öffnen fehlgeschlagen ist. Die `release()`-Aufrufe sollten durch ``try``:``finally`` sichergestellt werden. Und bei `VideoWriter` prüfst Du gar nicht, ob der funktioniert oder nicht.

Der ``return``-Wert sieht unsinnig aus: Ein `BoardVideo`-Objekt das einen geschlossenes `VideoWriter`-Objekt übergeben bekommt? Was soll denn damit noch angefangen werden?

Zwischenstand (ungetestet):

Code: Alles auswählen

    def cut_large_video(self, fps):
        """Get the part of the speaker from the "main video" and save it in the
        project folder and create an object of this.
        """
        capture = cv2.VideoCapture(str(self.video_data))
        try:
            if not capture.isOpened():
                raise RuntimeError('Error opening video stream or file')
            
            new_filename = Path(
                self.folder_path, self.folder_name, 'board_video.mp4'
            )
            fourcc = cv2.VideoWriter_fourcc(*'mp4v')
            writer = cv2.VideoWriter(str(new_filename), fourcc, fps, (938, 530))
            try:
                if not writer.isOpened():
                    raise RuntimeError('Error opening video stream or file')

                while True:
                    is_ok, frame = capture.read()
                    if not is_ok:
                        break
                    writer.write(frame[275:805, 17:955])
            finally:
                writer.release()
            
            self.files.append(new_filename)
            # 
            # TODO Passing a closed `VideoWriter` object looks wrong‽
            # 
            return BoardVideo(writer)
        finally:
            capture.release()
            cv2.destroyAllWindows()
`board_area()` ist kein guter Name für eine Funktion oder Methode. Solche Namen sollten die Tätigkeit beschreiben und `board_area` beschreibt keine Tätigkeit.

Du machst IMHO zu viel kopieren und einfügen. Hier ist jetzt ein `frame_number` was nicht benötigt wird, was nur da ist, weil diese Methode eine Kopie eines bereits vorhandenen Versuchs ist.

`sum` ist der Name einer eingebauten Funktion, den sollte man nicht an etwas anderes binden. Die Funktion kann man hier sogar verwenden!

Auch in dieser Methode wird nicht sinnvoll darauf reagiert wenn die Videodatei nicht geöffnet werden konnte.

Zwischenstand:

Code: Alles auswählen

    # 
    # TODO Come up with a better method name.
    # 
    def board_area(self, clip_prefix):
        """Analyse the video frame by frame and save the Clips (Board) in a
        list.
        """
        video = cv2.VideoCapture(str(self.file_path))
        try:
            if not video.isOpened():
                raise RuntimeError('Error opening video stream or file')
            
            times = list()
            clip_numbers = count()

            while True:
                is_ok, frame = video.read()
                if not is_ok:
                    break

                average = cv2.mean(frame)
                percentage_green = 100 * average[1] / sum(average)
                if percentage_green > 40:
                    times.append(video.get(cv2.CAP_PROP_POS_MSEC) / 1000)
                else:
                    if times:
                        clip = openshot.Clip(
                            '{}{}'.format(clip_prefix, next(clip_numbers))
                        )
                        clip.Start(times[0])
                        clip.End(times[-1])
                        self.subvideos.append(clip)
                        times.clear()
        finally:
            video.release()
            cv2.destroyAllWindows()
Ich sehe nicht wo diese Methode Zeit vertrödelt. Wenn die zu langsam ist, dann ist das halt so langsam. Sollte es tatsächlich an Python liegen, müsstest Du es in C++ umschreiben. Sowohl OpenCV als auch OpenShot haben da ja eine entsprechende API.
„A life is like a garden. Perfect moments can be had, but not preserved, except in memory. LLAP” — Leonard Nimoy's last tweet.
Benutzeravatar
Felix92
User
Beiträge: 133
Registriert: Mittwoch 7. November 2018, 17:57

Huhu vielen Dank für deine Antwort :)
Das Problem war das die Daten nicht weiter verwendet wurden bis dato und deshalb alles so ewig gebraucht hat (Arbeitsspeicher) jetzt wo sie weiter verarbeitet werden ist die Geschwindigkeit recht angenehm :)
Vielen Dank nochmal
Antworten