Śledzenie kuli - openCV

Śledzenie kuli - openCV
D3
  • Rejestracja:ponad 6 lat
  • Ostatnio:11 dni
  • Postów:58
0

Siemka. Możecie mi pomóc zwiększyć dokładność programu lub zaproponować jakieś inne pomysły?

Potrzebuję programu do śledzenia położenia kuli na płaszczyźnie za pomocą kamery. Wymaganiami jest optymalność, chciałbym to zaimplementować na mikrokontrolerze STM32, oraz dokładność i czas reakcji. Nie mogę dopuścić do sytuacji aby kula spadła z płaszczyzny, dlatego muszę szybko znajdować jej położenie by odpowiednio zareagować silnikami, najlepiej min. 30fps.

Napisałem na kompie przykładowy program realizujący takie zadanie, lecz przy ruchu kamery czasami program nie znajduje kuli lub znajduje je tam gdzie ich nie ma. Dopasowanie okręgu nie zawsze jest też precyzyjne. Problem stanowi również rodzaj oświetlenia, a im szybszy ruch tym większe problemy.

Może moglibyście mi doradzić co mógłbym zmienić by poprawić jakość znajdowania położenia kuli lub wskazać coś co warto byłoby przeczytać :D
Poniżej wstawiam kod.

Kopiuj
int main(int argc, char** argv) {

    // Load input image
    VideoCapture capture = VideoCapture(1);
    cv::Mat bgr_image;

    while (waitKey(20) != 27)  {
        capture >> bgr_image;
        bgr_image.copyTo(orig_image);

        cv::medianBlur(bgr_image, bgr_image, 3);
        // Convert input image to HSV
        cv::Mat hsv_image;
        cv::cvtColor(bgr_image, hsv_image, cv::COLOR_BGR2HSV);

        // Threshold the HSV image, keep only the red pixels
        cv::Mat lower_red_hue_range;
        cv::Mat upper_red_hue_range;
        cv::inRange(hsv_image, cv::Scalar(0, 100, 100), cv::Scalar(10, 255, 255), lower_red_hue_range);
        cv::inRange(hsv_image, cv::Scalar(160, 100, 100), cv::Scalar(179, 255, 255), upper_red_hue_range);

        // Combine the above two images
        cv::Mat red_hue_image;
        cv::addWeighted(lower_red_hue_range, 1.0, upper_red_hue_range, 1.0, 0.0, red_hue_image);

        cv::GaussianBlur(red_hue_image, red_hue_image, cv::Size(9, 9), 2, 2);

        // Use the Hough transform to detect circles in the combined threshold image
        std::vector<cv::Vec3f> circles;
        cv::HoughCircles(red_hue_image, circles, HOUGH_GRADIENT, 1, red_hue_image.rows / 8, 100, 20, 0, 0);

        // Loop over all detected circles and outline them on the original image
        for (size_t current_circle = 0; current_circle < circles.size(); ++current_circle) {
            cv::Point center(std::round(circles[current_circle][0]), std::round(circles[current_circle][1]));
            int radius = std::round(circles[current_circle][2]);

            cv::circle(orig_image, center, radius, cv::Scalar(0, 255, 0), 5);
        }

        // Show images
        cv::namedWindow("Threshold lower image", cv::WINDOW_AUTOSIZE);
        cv::imshow("Threshold lower image", lower_red_hue_range);
        cv::namedWindow("Threshold upper image", cv::WINDOW_AUTOSIZE);
        cv::imshow("Threshold upper image", upper_red_hue_range);
        cv::namedWindow("Combined threshold images", cv::WINDOW_AUTOSIZE);
        cv::imshow("Combined threshold images", red_hue_image);
        cv::namedWindow("Detected red circles on the input image", cv::WINDOW_AUTOSIZE);
        cv::imshow("Detected red circles on the input image", orig_image);
    }
    return 0;
}
edytowany 2x, ostatnio: danielbr3
MA
Może wystarczy kulę przymocować czymś do podłoża ;)
_13th_Dragon
Nie, bo jak napisał: - "Nie mogę dopuścić do sytuacji aby kula spadła z płaszczyzny" - więc mocowanie do podłogi odpada.
D3
Chyba to należało potraktować jako żart :D
DO
  • Rejestracja:ponad 5 lat
  • Ostatnio:5 miesięcy
  • Postów:85
1

Tak na szybko co mi przychodzi do głowy:

  • skoro to jest śledzenie obiektu to możesz zapoznać się z filtrem Kalmana.
  • Jasność zawsze jest problemem w tego typu podejściach. Próbowałbym może dynamicznie wyliczać jasność kolejnych zdjęć i na tej podstawie dososowywać progi.
  • Nie wiem jak to wygląda u Ciebie w praktyce, ale być może można filtrować jakoś śmieci przez ich rozmiar
D3
  • Rejestracja:ponad 6 lat
  • Ostatnio:11 dni
  • Postów:58
0
Kopiuj
int main(int argc, char** argv) {

    // Load input image
    VideoCapture capture = VideoCapture(1);
    cv::Mat bgr_image;
    cv::Mat orig_image;
    cv::Mat hsv_image;
    cv::Mat thresholded;
    std::vector<cv::Vec3f> circles;
    int hueLower = 0, hueUpper = 180, saturationLower = 100, saturationUpper = 255, valueLower = 100, valueUpper = 255;

    string nameThreshold = "Threshold lower image";
    cv::namedWindow(nameThreshold, cv::WINDOW_AUTOSIZE);
    createTrackbar("Hue lower", nameThreshold, &hueLower, 180, NULL);
    createTrackbar("Hue upper", nameThreshold, &hueUpper, 180, NULL);
    createTrackbar("Saturation lower", nameThreshold, &saturationLower, 255, NULL);
    createTrackbar("Saturation upper", nameThreshold, &saturationUpper, 255, NULL);
    createTrackbar("Value lower", nameThreshold, &valueLower, 255, NULL);
    createTrackbar("Value upper", nameThreshold, &valueUpper, 255, NULL);
    cv::namedWindow("Detected red circles on the input image", cv::WINDOW_AUTOSIZE);

    while (waitKey(20) != 27)
    {
        capture >> bgr_image;
        bgr_image.copyTo(orig_image);

        // MedianBlur
        cv::medianBlur(bgr_image, bgr_image, 3);
        // Convert input image to HSV
        cv::cvtColor(bgr_image, hsv_image, cv::COLOR_BGR2HSV);

        // Threshold the HSV image, keep only the red pixels
        cv::inRange(hsv_image, cv::Scalar(hueLower, saturationLower, valueLower),
            cv::Scalar(hueUpper, saturationUpper, valueUpper), thresholded);

        cv::GaussianBlur(thresholded, thresholded, cv::Size(9, 9), 2, 2);

        // Use the Hough transform to detect circles in the combined threshold image
        cv::HoughCircles(thresholded, circles, HOUGH_GRADIENT, 1, thresholded.rows / 8, 100, 20, 0, 0);

        // Loop over all detected circles and outline them on the original image
        for (size_t current_circle = 0; current_circle < circles.size(); ++current_circle) {
            cv::Point center(std::round(circles[current_circle][0]), std::round(circles[current_circle][1]));
            int radius = std::round(circles[current_circle][2]);
            cv::circle(orig_image, center, radius, cv::Scalar(0, 255, 0), 5);
        }

        // Show images
        cv::imshow(nameThreshold, thresholded);
        cv::imshow("Detected red circles on the input image", orig_image);
    }
    return 0;
}

W takiej wersji nawet to w miarę wykrywa, jest dość dokładnie i nie gubi się przy ruchu. Pozostaje jednak wciąż problem opóźnienia. Po poruszeniu kamerą mija jakieś 100ms do czasu aż na ekranie zobaczę nowe położenie. Wszelkie dodatkowe filtry jeszcze to spowolnią. Najważniejsze w sumie jest zoptymalizowanie tego na co nie mam zbytnio pomysłu bo kod i tak wydaje się ubogi.

DO
  • Rejestracja:ponad 5 lat
  • Ostatnio:5 miesięcy
  • Postów:85
0

może resize ramek? Do wykrycia okręgów obstawiam, że nie potrzebujesz dużej rozdzielczości, a to na pewno ładnie zredukuje liczbę operacji. Rozumiem, że puszczasz to póki co na kompie? Trochę dziwne, że to już Ci generuje jakieś opóźnienia.

Zobacz pozostałe 2 komentarze
RE
wiem co może być powodem: HoughCircles jak kiedyś miałem złe ustawienia to zajeżdżał CPU kompletne amen. To taka luźna obserwacja z doświadczenia :)
RE
Chociaż widzę że chyba rozmiar pola masz stawione na 0 w hough
DO
pewno też, chociaż dla mnie i tak wynik 4fps'y na kompie jest mocno zastanwiający. Pare lat temu używałem HoughCircles na Respberry i z tego co pamiętam miałem podobną liczbę fps'ów (jeśli nie nawet trochę większą)
RE
zależy od ustawień, mi zarżnął hough przy złych ustawieniach ryzena 1700. Na moim jetson nano hough(Ale ja robię hough na obrazie po treshold) + model resnet18 mam płynny obraz. Ale to musiałem dobrać odpowiednie pole itd. Opencv i na rpi ma wsparcie dla neon(o ile włączone przy budowaniu) i same operacje na simd powinny dać kopa. Także trzeba moim zdaniem pogrzebać w ustawieniach. Albo sprofilować ten kod.
D3
ustawienia rozdzielczości również niewiele pomogły
_13th_Dragon
  • Rejestracja:prawie 20 lat
  • Ostatnio:10 dni
1

while (waitKey(1) != 27)


Wykonuję programy na zamówienie, pisać na Priv.
Asm/C/C++/Pascal/Delphi/Java/C#/PHP/JS oraz inne języki.
Zobacz pozostałe 3 komentarze
D3
rozumiem, jednak chyba to nie było głównym źródłem opóźnienia, poprawa znikoma :/
_13th_Dragon
Ze jak? Masz max 50 FPS przy samej tej pustej pętli.
D3
chodzi mi o to że po zmianie tej linii na zwykłą pętle i przy zachowaniu reszty kodu jestem w stanie odczytać 4-5 klatek/sekunde, gdybym miał 50FPS to bym się cieszył :D
_13th_Dragon
A mi chodzi o to że jak tu i tam rozrzucasz po 20ms to w końcu ci ekstra godzina wyjdzie. Wywal imshow i sprawdź.
D3
właśnie przed chwilą sprawdzałem różne linijki kodu jak wpływają na szybkość, imshow zabiera mało czasu, natomiast prawie każdy filtr jest mega kosztowny, np. filtr mediana. Możliwe że mógłbym z których zrezygnować, lecz bez znalezienia przyczyny musiałbym wyrzucić chyba wszystkie. Znajdowania okręgów transformatą Hougha nie zabiera o dziwo najwięcej czasu
darkbit
  • Rejestracja:ponad 20 lat
  • Ostatnio:12 dni
  • Lokalizacja:~Koszalin
0

Obstawiam "Ball On Plate"? :)
Robiliśmy to na kompie też (Qt), kamerka PS3 Eye (niska rozdziałka, dużo FPSów), płytka od ST jakaś Discovery (sterowanie serwami do machania "płaszczyzną", komunikacja po serialu z kompem).
Faktycznie jest opóźnienie obrazu i tego raczej się nie przeskoczy.
Obraz na pewno trzeba było przygotować przed wykrywaniem kulki. Chyba: kontrast, konwersja na skalę szarości, usuwanie szumów.
I z tego co pamiętam, użyty został PID, przewidywany był kierunek ruchu kulki na podstawie X ostatnich jej położeń.
Może to coś Ci pomoże ;)

D3
tak, to dokładnie taki projekt. Z PIDem prawdopodobnie sobie poradzę. Chciałbym tylko uniknąć LabView i zrobić to Od razu na STM by nie tracić czasu na przesyłanie danych po komunikacji (choć to mała wartość) i nie używać w ogóle kompa, zrobić urządzenie mobilne
D3
  • Rejestracja:ponad 6 lat
  • Ostatnio:11 dni
  • Postów:58
0

Zmniejszyłem rozdzielczość, lecz poprawa jest nieznaczna, teraz mam jakieś 4-4,5 FPS.

Kopiuj
    capture.set(CAP_PROP_FRAME_WIDTH, 640);
    capture.set(CAP_PROP_FRAME_HEIGHT, 480);
RE
pytanie do hough i dwa ostatnie argumenty. 0, 0. Dlaczego? OT max i min pole.
RE
zobaczyłem twoją odpowiedź widzę że z okręgami nie masz problemu.
RE
ale jestem zdzwiony że wspominasz iż medianablur zabiera ci bardzo dużo czasu. Mniej więcej ile? Swoją drogą na stm32 nie ma opencv to będziesz musiał ręcznie naklepać.
DO
@recovery: znalazłem coś takiego: link, więc coś tam się da wykombinować z opencv na stm32
RE
  • Rejestracja:ponad 18 lat
  • Ostatnio:około 18 godzin
0

rzuciłem też okiem, czasami niektórzy faktycznie mają problem z powolnością blura ale mówią o szybszych libkach.
https://dsp.stackexchange.com/questions/50576/fastest-available-algorithm-to-blur-an-image-low-pass-filter


We are the 4p. Existence, as you know it, is over. We will add your biological and technological distinctiveness to our own. Resistance is futile
DO
  • Rejestracja:ponad 5 lat
  • Ostatnio:5 miesięcy
  • Postów:85
0

piszesz, że filtry dużo czasu zabierają, więc może spróbuj zmniejszyć kernel na 3x3 i porównaj wyniki. A ogólnie to ile fps'ów dostajesz jak wszystko wywalisz?

RE
  • Rejestracja:ponad 18 lat
  • Ostatnio:około 18 godzin
1

Odpowiadam odnośnie opencv na stm32. Z rok temu szukałem o tym informacji i parę spraw:

  1. To są jakies prób przeróbki opencv na stm32 np.https://medium.com/@deryugin.denis/how-to-run-opencv-on-stm32-mcu-b581f42b0766
  2. O oficjalnym wsparciu zapomnij. Mi były potrzebne jakieś proste operacje typu rgb565 do grey. To sobie sam napisałem.
  3. Zapomnij o wydajności. generalni opencv na arm i x86 używa SIMD żeby przyspieszyć operacje. Tego nie masz w ogóle na stm32. Więc zostaje taki czysty kod C co będzie wolne.

Generalnie zapomnij że na stm32 i rdzeniach typu m4/m7 nie odpalisz opencv a nawet jak odpalisz jakieś zhackowane wersje to będą albo wolne albo uboższe.

Jeśli chcesz możesz zamiast tego próbować jak napisałem metod neuronowych dla stm32. Dla małych rdzeni można użyć cube i zaciągnąć model w keras a on go przerobi tak żeby się dało odpalić na stm32. Tak wykryjesz piłkę ale położenie będzie mniej więcej.

Moim zdaniem zainteresuj się rpi4 albo jetson nano(jeśli chcesz używać ML) lub jakimiś pochodnymi płytkami na linuksie.

edit:
zawsze możesz próbować sam napisać wszystkie algorytmy na stm32 i próbować je optymalizować tam ale to dłubanina.


We are the 4p. Existence, as you know it, is over. We will add your biological and technological distinctiveness to our own. Resistance is futile
edytowany 2x, ostatnio: revcorey
MarekR22
Moderator C/C++
  • Rejestracja:ponad 17 lat
  • Ostatnio:minuta
4

Problem leży w tym, że używasz cv::HoughCircles na złym obrazie.
Ta funkcja służy do wynajdowania okręgów nie kół!
Najpierw musisz przetworzyć obraz tak, by dostać obwiednie kształtów na obrazie.
Po odfiltrowaniu po zakresie kolorów dostajesz koło (i śmieci), najlepiej użyć cv::Canny by dostać krawędzie koło czyli okrąg.
W takiej sytuacji cv::HoughCircles będzie bardziej efektywne.

Troszkę zrefaktorowałem twój kod (możesz przywrócić rozmycie gaussa - a nawet powinieneś), jest lepiej ale nadal można to bardzo poprawić (pod względem czytelności).

Do testowania użyłem czerwonego guzika z pilota :).

Kopiuj
#include <opencv2/opencv.hpp>
#include <string>
#include <iostream>

using namespace cv;

class BallTracker {
public:
    BallTracker()
    {
        openCamera();
        setupTrackbars();
    }

    void openCamera()
    {
        capture.setExceptionMode(true);
        capture.open(0);
    }

    void setupTrackbars()
    {
        cv::namedWindow(nameThreshold, cv::WINDOW_AUTOSIZE);
        createTrackbar("Hue lower", nameThreshold, &hueLower, 180);
        createTrackbar("Hue upper", nameThreshold, &hueUpper, 180);
        createTrackbar("Saturation lower", nameThreshold, &saturationLower, 255);
        createTrackbar("Saturation upper", nameThreshold, &saturationUpper, 255);
        createTrackbar("Value lower", nameThreshold, &valueLower, 255);
        createTrackbar("Value upper", nameThreshold, &valueUpper, 255);
        cv::namedWindow(nameCaptureWindow, cv::WINDOW_AUTOSIZE);
    }

    void run()
    {
        while (waitKey(20) != 27) {
            std::vector<cv::Vec3f> circles;
            capture >> orig_image;

            // Convert input image to HSV
            cv::cvtColor(orig_image, hsv_image, cv::COLOR_BGR2HSV);

            // Threshold the HSV image, keep only the red pixels
            cv::inRange(hsv_image, cv::Scalar(hueLower, saturationLower, valueLower),
                cv::Scalar(hueUpper, saturationUpper, valueUpper), thresholded);

            cv::Canny(thresholded, thresholded, 100, 200);

            cv::HoughCircles(thresholded, circles, HOUGH_GRADIENT, 1, thresholded.rows / 8, 100, 20, 5, 200);

            for (size_t current_circle = 0; current_circle < circles.size(); ++current_circle) {
                cv::Point center(std::round(circles[current_circle][0]), std::round(circles[current_circle][1]));
                int radius = std::round(circles[current_circle][2]);
                cv::circle(orig_image, center, radius, detectedCircelColor, 5);
            }

            // Show images
            cv::imshow(nameThreshold, thresholded);
            cv::imshow(nameCaptureWindow, orig_image);
        }
    }

private:
    const String nameThreshold = "Threshold lower image";
    const String nameCaptureWindow = "Detected red circles on the input image";
    const cv::Scalar detectedCircelColor{ 0, 255, 0 };

    VideoCapture capture;
    cv::Mat bgr_image;
    cv::Mat orig_image;
    cv::Mat hsv_image;
    cv::Mat thresholded;

    int hueLower = 0;
    int hueUpper = 8;
    int saturationLower = 150;
    int saturationUpper = 255;
    int valueLower = 100;
    int valueUpper = 255;
};

int main(int argc, char** argv)
{
    try {
        BallTracker tracker;
        tracker.run();
    }
    catch (const std::exception& e) {
        std::cerr << "ERROR! " << e.what() << '\n';
        return 1;
    }

    return 0;
}

BTW, dawaj zawsze kod, który można skopiować bez kombinowania.


Jeśli chcesz pomocy, NIE pisz na priva, ale zadaj dobre pytanie na forum.
D3
dla twojego kodu mam 13-15fps, ale chyba nic tutaj nie zmieniłeś poza usunięciem tego rozmycia. Z czytelnością faktycznie się nie postarałem u siebie, ostatnio chyba za dużo piszę w czystym C a tutaj chciałem tylko na szybko naklepać coś do testu.
D3
  • Rejestracja:ponad 6 lat
  • Ostatnio:11 dni
  • Postów:58
0

Cześć. Wracam po dłuższym czasie nieobecności. Postanowiłem sprawdzić najpierw dokładne czasy z mojego kodu dołączając kolejne fragmenty, a później spojrzę na kod Marka i pokombinuję dalej.

  • sam odczyt z kamery: 28.6 fps
  • kopia obrazu: 27.2 fps
  • medianBlur: 21 fps
  • konwersja HSV: 18.1fps
  • threshold: 17.9 fps
  • gaussianBlur: 14.5fps
  • HoughCircles: 7.7fps
  • pokazanie obrazów: bez zmian

Pewnie faktycznie ciężko będzie to zrobić na STMie ale może zdecyduję się na Raspberry. Ewentualnie zostanę przy LabView ale nie zaszkodzi się trochę pobawić z OpenCV :D

MarekR22
Moderator C/C++
  • Rejestracja:ponad 17 lat
  • Ostatnio:minuta
0
danielbr3 skomentował(a):

dla twojego kodu mam 13-15fps, ale chyba nic tutaj nie zmieniłeś poza usunięciem tego rozmycia. Z czytelnością faktycznie się nie postarałem u siebie, ostatnio chyba za dużo piszę w czystym C a tutaj chciałem tylko na szybko naklepać coś do testu.

Napisałem wyraźnie w tekście co zmieniłem. Przede wszystkim dodałem transformację Canny!

Po drugie pytanie, było o dokładność nie wydajność.
Do wydajności trzeba dobrać prawidłowo parametry. Przykładowo dla cv::HoughCircles należało by zawęzić rozmiar możliwych promieni okręgu. To powinno znacząco przyspieszyć program (poczytaj jak to działa).

Może lepiej będzie jak dostarczysz kilka przykładowych obrazów. Taki jaki ci działa i taki jakie ci nie działa.


Jeśli chcesz pomocy, NIE pisz na priva, ale zadaj dobre pytanie na forum.
D3
sorry, przegapiłem to Canny. Parametry oczywiście trochę dopasuję, lecz nie chciałbym tego też zbyt mocno zmieniać bo chciałbym by program był w miarę odporny na różne sytuacje. Choć w ostateczności i tak będę obserwował kulę z konkretnej odległości na gładkiej, jednokolorowej płycie. Jednak po tak słabej wydajności programu na randomowym obrazie nie wiem czy mogę się czegoś dobrego spodziewać i prawdę mówiąc jestem w mocnym szoku. W LabView bez problemu osiągnąłem 27fps z wszystkimi filtrami, podczas gdy 30fps to maksymalna szybkość mojej kamery.
D3
po openCV spodziewałem się conajmniej takich lub lepszych wyników a okazuje się że już samo odczytanie obrazu i zrobienie kopii jest wolniejsze. Może trzeba coś pogrzebać przy konfiguracji kamery. Pewnie openCV też daje taką możliwość. A może jest jakaś lepsza biblioteka od openCV, choć wydaje mi się że jest ona jednak najlepszą biblioteką do przetwarzania obrazu
D3
chciałbym ogólnie osiągnąć najpierw conajmniej 20fps przy czarnym obrazie, gdzie nie złapie żadnych okręgów. Jeśli to się nie uda to nawet nie ma sensu się dalej tym zajmować bo i tak nie będę w stanie zrealizować zadania
RE
  • Rejestracja:ponad 18 lat
  • Ostatnio:około 18 godzin
1

Pytanie to co robi labview i jak. Już ci wcześniej zwracałem uwagę na parametry w hough które(szczególnie ostatnie dwa) ostatnie słabo wyglądają.

Ja widzę że problemem jest nie to że opencv jest wolne czy coś tylko nie do końca wiesz co robisz masz przykład na rpi


We are the 4p. Existence, as you know it, is over. We will add your biological and technological distinctiveness to our own. Resistance is futile
edytowany 1x, ostatnio: revcorey

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.