"Odświeżanie" po wątku, Kamera Java.

0

Witam Koledzy!

Proszę o pomoc bo już nie wiem co robię źle. Otóż w wątku przechwytuje obraz z kamery. I teraz: po przyciśnięciu buttona chciałbym aby był taki niby podgląd kamerki na żywo w jPanelu (załóżmy 5 klatek przy 1 kl/s). Ale niestety po wywołaniu w pętli z opóźnieniem tego wątku z repaintem wyświetla tylko i wyłącznie ostatnie zdjęcie (z ostatniego przebiegu pętli). Nie wiem jak sobie z tym poradzić. kombinowałem też z break'iem no to po pierwszej iteracji wyświetla obraz ale wiadomo że dalej już nie idzie. Kombinowałem z continue ale niestety bez rezultatu. Chciałbym również uprzedzić, że jestem amatorem w programowaniu ;/

Za wszelkie podpowiedzi z góry dziękuję

private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {                                         
for(int i=0;i<5;i++)
     {
                watek w = new watek(player);
                new Thread(w).start();
                System.out.print("Klatka: " + i + "\n\r");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException ex) {
                Logger.getLogger(KamerkaView.class.getName()).log(Level.SEVERE, null, ex);
            }
                jPanel1.removeAll();
                jPanel1.add(KamerkaApp.getApplication().zmienna);
                jPanel1.repaint();
                new Thread(w).stop();
      }
}
0

Tak naprawdę to nie wiadomo co tu robisz dobrze. Ciężko więc od czegoś zacząć. Polecałbym najpierw po prostu porobienie zadań z jakiegoś podręcznika bo brakuje Ci podstaw rozumienia idei wątków. I nie nazywaj wątkiem zadania implementującego Runnable.

0

Heh, dzięki pocieszyłeś mnie... ;/ no ale dobrze. Czytałem, szperałem i zrobiłem jak pokazywali co do wątków więc nie wiem czym różni się zadanie Runnable od wątku. Tak jak mówiłem jestem amatorem więc też mi się trochę nie dziwcie.

0

No dobra to może inaczej:

Image fot;
Image  zdjecie()
    {
                FrameGrabbingControl frameGrabber = (FrameGrabbingControl)player.getControl("javax.media.control.FrameGrabbingControl");
                Buffer buf = frameGrabber.grabFrame();
                Image img = (new BufferToImage((VideoFormat)buf.getFormat()).createImage(buf));
                BufferedImage buffImg = new BufferedImage(img.getWidth(null), img.getHeight(null), BufferedImage.TYPE_INT_RGB);
                Graphics2D g = buffImg.createGraphics();
                g.drawImage(img, null, null);
                return fot = img;
                
    }

   
    private void jButton2ActionPerformed(java.awt.event.ActionEvent evt) {                                         
        

    for(int licznik =0;licznik <4 ; licznik++)
    {
            
                zdjecie();
                rysuj r=new rysuj(fot, 640, 480); 
                jPanel1.removeAll();
                jPanel1.add(r); 
                jPanel1.repaint();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException ex) {
                Logger.getLogger(KamerkaView.class.getName()).log(Level.SEVERE, null, ex);
            }
           }
    }              
1

Co robisz źle:

  1. Nie createImage, a createCompatibleImage jeżeli chcesz wydajnie wyświetlać klatki na bieżącym urządzeniu graficznym. Najlepiej jest spróbować zmusić grabbera, żeby ściągał obraz w planie zgodnym z aktualnym planem graficznym urządzenia na którym chcesz wyświetlać klatki. Jeżeli jest to niemożliwe (np. bo grabber potrafi ściągać obraz tylko w formacie źródła, albo narzuconym z góry), to przeróbka na format zgodny z obecną grafiką jest Twoim zadaniem. I to właśnie może być ten dodatkowy krok z drawImage kopiującym jeden obraz w formacie źródła na obraz w planie zgodnym z planem graficznym.
  2. Dalej tworzysz obraz BufferedImage z planem, który wcale nie musi być zgodny z bieżącą grafiką. Dlaczego zakładasz, że twój obecny plan graficzny, to 24-bitowy BufferedImage.TYPE_INT_RGB? Jeżeli plany są niezgodne, to dorabiasz mnóstwo zbędnej roboty systemowi/grafice (a wtedy zaczyna brakować czasu i o wydajnym renderowaniu możesz zapomnieć). Obecnie niemal każdy ekran pracuje w 32-bitowym planie ARGB lub RGBA. Ale i tak zakładanie tego na sztywno, to jak bawić się rosyjską ruletką więcej niż 6 razy. Trzeba po prostu sprawdzać i reagować.
  3. Kiedy już teoretycznie miałbyś obraz klatki gotowy do wyświetlenia, to po rzekomym wyrysowaniu jej zamiast pobrać z grabbera następną klatkę zabierasz się za likwidowanie komponentu na którym ostatnią klatkę wyrysowałeś. To tak jakbyś zrywał z samochodu karoserię, żeby wyjąć bagaże i włożyć inne. :)

Właściwy sposób działania jest taki:

  1. Pobierasz z grabbera obraz klatki i jeżeli jego plan jest niezgodny z obecnym planem graficznym, to przerysowujesz klatkę na nowy obraz z planem zgodnym. Możesz też przerysowywać zawsze ponieważ to co dostajesz z grabbera zazwyczaj jest zagrożone bardzo szybką likwidacją (i to poza Twoją kontrolą). Jest to szczególnie istotne jeżeli odebrany obraz chcesz jeszcze poza wyświetlaniem jakoś przetwarzać, a dokumentacja grabbera nie gwarantuje (lub nie wspomina) trwałości przekazywanych danych.
  2. Tworzysz jeden panel na którym klatki mają być wyświetlane. Nie robisz żadnego usuwania paneli, ani innych komponentów. Obiekty Swinga nie służą do animacji. Po pierwsze są asynchroniczne, po drugie takie operacje są stosunkowo powolne. Twoim problemem jest w którym miejscu panela i z jakimi wymiarami będziesz przerzucał obraz klatki na ten panel. Najszybsze generowanie obrazu jest wtedy kiedy plan jest identyczny (co sobie zagwarantowałeś) i nie ma żadnego skalowania, czyli przerysowywany jest piksel do piksela. Jednak skalowanie za pomocą drawImage nie jest aż takie wolne, więc można sobie na to pozwolić. Trzeba tylko się zdecydować czy skalowanie ma być za zachowaniem proporcji czy nie (jeżeli tak, to albo sam musisz sobie przeliczać prawidłowe wymiary - tak jak np. obraz "z paskami" lub "obcięty" w TV).
  3. Tworzysz sobie timera, którego jedynym zadaniem jest wyświetlenie ostatniej pobranej z grabbera klatki (tylko tyle). Zwykle nie powinien on robić nic więcej poza pluciem kilkadziesiąt razy na sekundę repaintami na panel, który ma obrazy klatek renderować. Odstęp czasu to zazwyczaj 1/60 sekundy, czyli co 16,6 ms. Dla najszybszych monitorów odstęp mógłby wynosić 1/85 sekundy, ale zwykle grabbery nie podają obrazów częściej niż 30 klatek na sekundę. Tak więc mógłbyś równie dobrze zejść do 1/30 lub 1/24 sekundy. W przypadku tanich kamerek, które podają 15 obrazów/s okres na poziomie 1/30 = 33,3 ms jest zupełnie wystarczający.
    Oczywiście wiele zależy od rozdzielczości i planu. Jeżeli będziesz miał grabbera, który będzie podawać 60 pełnych obrazów 1920x1080, to Twoja aplikacja będzie miała ogromne problemy, żeby się wyrobić z wyświetlaniem wszystkich obrazów niezależnie od tego jak mały będzie panel wyjściowy. Java SE zwykle nie obsłuży takiego przypadku bez akceleracji (lub zgodności planów).
0

Chyba porwałem się z motyką na słońce...
Pomału muszę stwierdzić że mnie to przerasta ponieważ dotychczas uważałem że mam mały problem a teraz pokazujesz mi że mam ich od groma z czym ciężko mi się zgodzić ale pewnie masz trochę racji.
Otóż optymalizacja będzie później póki co to ma wywołać maks. 2 klatki na sekundę. więc będzie tzw. klatkowanie i ok to mi nie przeszkadza.

rysuj r=new rysuj(fot, 320, 240); // po prostu wyświetli obraz przeskalowany. Obraz wychodzący z kamery którą posiadam jest zawsze 640x480.

A tu klasa rysuj jak coś:

public class rysuj extends JComponent{
    Image im;
    int x,y;
    public rysuj(Image im,int x,int y) {
        this.im=im;
        this.x=x;
        this.y=y;
        this.setSize(640,480);
    }
    @Override
    protected void paintComponent(Graphics g)
    {
        super.paintComponent(g);
        g.drawImage(im,0,0,x,y,null);
    }
}

poza tym wywołana poniższa funkcja przyciskiem (klikam parę razy na sekundę przycisk) wyświetla "tak jakby" obrazy na żywo a już w pętli for z opóźnieniem wątku wyświetla ostatnie zdjęcie - dlaczego?! :

void  zdjecie()
    {
                FrameGrabbingControl frameGrabber = (FrameGrabbingControl)player.getControl("javax.media.control.FrameGrabbingControl");
                Buffer buf = frameGrabber.grabFrame();
                Image img = (new BufferToImage((VideoFormat)buf.getFormat()).createImage(buf));
                BufferedImage buffImg = new BufferedImage(img.getWidth(null), img.getHeight(null), BufferedImage.TYPE_INT_RGB);
                Graphics2D g = buffImg.createGraphics();
                g.drawImage(img, null, null);
                rysuj r=new rysuj(img, buffImg.getWidth(), buffImg.getHeight());
                jPanel1.removeAll();
                jPanel1.add(r);
                jPanel1.repaint();
                fot=img;
    }
0

Po pierwsze "rysuj" jest klasą, a rysuj() jest konstruktorem. Oznacza to, że każde jego wywołanie powoduje czasochłonne przydzielanie pamięci na stercie. Jeżeli chcesz sobie przeskalowywać klatkę na istniejącym komponencie, to zrób sobie do tego metodę, która będzie zmieniała wartość pól x i y (swoją drogą pola źle nazwane - przecież to u ciebie rozmiar). W samym konstruktorze x i y powinny mieć rozmiar nadawany taki sam jak wymiary okna, czyli 640 i 480.
Po drugie to co robisz, to próbujesz animować komponentem - dodawać i usuwać komponent "rysuj" zamiast po prostu przerysowywać w paintComponent tego obiektu klatkę z kolejnego obrazu, którego referencja jest (na chwilę) przechowywana w Im.
Po trzecie cała metoda "zdjęcie" jest do rozwalenia. Za jej pomocą próbujesz na złym planie graficznym i na coraz to innych komponentach animować, ściągnięty z grabbera obrazek w rytm odczytywania danych z grabbera. Jeżeli z grabbera dostaniesz 100 obrazów na sekundę, to taka pętla będzie próbowała renderować 100 FPS, a jeżeli dostaniesz tylko 5 to będziesz renderować z 5 FPS. Nie tędy droga. Kwestia synchronizacji i wydajności jest w takim przypadku nie sprawą optymalizacji, ale jest sprawą kluczową aby w ogóle mogło cokolwiek działać.
Jeżeli "zdjęcie" wywołujesz w pętli to robisz dokładnie to:

  1. Ściągasz klatkę z grabbera do obiektu którego referencja jest w Img
  2. Tworzysz nowy 24-bitowy obrazek do którego referencję ma buffImg i przepisujesz na niego klatkę z grabbera
  3. Tworzysz nowy komponent Swinga, którego referencję masz w lokalnej zmiennej "r" w taki sposób, że najbliższe rysowanie (jedyne) wyrysuje fragment klatki z obiektu Img (a nie buffImg), która zmieści się w 640x480. Jeżeli klatka jest mniejsza, to mieści się w nim, a więc nawet nie widzisz problemu. Gdyby była większa, to klatka byłaby obcinana przez Swinga do rozmiarów komponentu. System próbuje uratować sytuację zajmując się niejawnym przekonwertowaniem Img na bieżący plan graficzny w operacji drawImage w r.paintComponent().
  4. Usuwasz z panelu poprzedni komponent i przyłączasz do niego nowo stworzony komponent. Swing od razu wywołuje na nim stan invalidate (efekt dodania komponentu), a następne po nim repaint nie będzie miało żadnego efektu dopóki Swing nie odzyska kontroli nad swoim wątkiem EDT (nie wiem skąd wywołujesz - prawdopodobnie z jakiegoś actionPerformed - dopóki się ona nie zakończy nic więcej nie zobaczysz).
  5. Za każdy razem kiedy klikasz, to metoda actionPerformed wywołuje się i kończy, więc po jej zakończeniu widzisz rezultat w postaci wyświetlenia kolejnej klatki. Kiedy to samo robisz w pętli nic takiego nie może nastąpić.

Krótko mówiąc pomieszałeś Swinga z animacją, tworzysz zupełnie nie używane obiekty takie jak buffImg, a renderowania w ogóle nie przeprowadzasz. W obecnej chwili jest to kod rozpaczliwy, który działa częściowo tylko dzięki temu, że architekci AWT/Swinga próbowali sprawić, żeby ich metody jakoś działały nawet w zupełnie poronionych warunkach. ;)

0

Dobra poddaje się nie wiem jak to zrobić. :/

Dzięki za pomoc ale moje umiejętności są chyba jeszcze zbyt małe.
Pozdrawiam.

0

Po prostu poczytaj o Swingu i przeglądnij przykłady różnych animacji. To nie kwestia poddawania się czy nie, ale czasu i nauki.

1 użytkowników online, w tym zalogowanych: 0, gości: 1