Skocz do zawartości

Pomocna odpowiedź

Ciąg dalszy poprawiania algorytmu...

#include <Arduino.h>
#include "esp_camera.h"

#define PWDN_GPIO_NUM  (-1)
#define RESET_GPIO_NUM (-1)
#define XCLK_GPIO_NUM  10
#define SIOD_GPIO_NUM  40
#define SIOC_GPIO_NUM  39

#define Y9_GPIO_NUM    48
#define Y8_GPIO_NUM    11
#define Y7_GPIO_NUM    12
#define Y6_GPIO_NUM    14
#define Y5_GPIO_NUM    16
#define Y4_GPIO_NUM    18
#define Y3_GPIO_NUM    17
#define Y2_GPIO_NUM    15
#define VSYNC_GPIO_NUM 38
#define HREF_GPIO_NUM  47
#define PCLK_GPIO_NUM  13

bool camera_ready = false; // Pozwala sprawdzić czy kamera jest gotowa

#define N_MAX_VALUE 5 // Liczba wiaderek (maksymalna liczba do wylosowania)
uint32_t sampleCounts[N_MAX_VALUE]; // Liczba losowań konkretnej liczby
uint64_t samplesCount = 0;          // Liczba łącznych losowań

#define FRAME_SIZE_CAM FRAMESIZE_96X96
#define FRAME_SIZE (96 * 96)
uint8_t baseFrames[FRAME_SIZE * 3];

uint8_t is_frame_data_noisy(const uint32_t index)
{
    // Obliczenie indeksów dla ramek
    const uint32_t realIndex0 = FRAME_SIZE * 0 + index;
    const uint32_t realIndex1 = FRAME_SIZE * 1 + index;
    const uint32_t realIndex2 = FRAME_SIZE * 2 + index;

    // Analiza danych ramek - czy na danym polu wstępuje szum?
    return baseFrames[realIndex0] ^ baseFrames[realIndex1] ^ baseFrames[realIndex2];
}

void setup()
{
    delay(2000);
    Serial.begin(115200);
    while (!Serial) // Poczekaj na uruchomienie portu szeregowego
    {
        Serial.print(".");
    }

    Serial.println("Serial port ready!");

    // Konfiguracja kamery dla Xiao ESP32-S3 Sense
    camera_config_t config;

    // IO
    config.ledc_channel = LEDC_CHANNEL_0;
    config.ledc_timer   = LEDC_TIMER_0;
    config.pin_d0       = Y2_GPIO_NUM;
    config.pin_d1       = Y3_GPIO_NUM;
    config.pin_d2       = Y4_GPIO_NUM;
    config.pin_d3       = Y5_GPIO_NUM;
    config.pin_d4       = Y6_GPIO_NUM;
    config.pin_d5       = Y7_GPIO_NUM;
    config.pin_d6       = Y8_GPIO_NUM;
    config.pin_d7       = Y9_GPIO_NUM;
    config.pin_xclk     = XCLK_GPIO_NUM;
    config.pin_pclk     = PCLK_GPIO_NUM;
    config.pin_vsync    = VSYNC_GPIO_NUM;
    config.pin_href     = HREF_GPIO_NUM;
    config.pin_sccb_sda = SIOD_GPIO_NUM;
    config.pin_sccb_scl = SIOC_GPIO_NUM;
    config.pin_pwdn     = PWDN_GPIO_NUM;
    config.pin_reset    = RESET_GPIO_NUM;

    config.xclk_freq_hz = 20000000;            // Częstotliwość komunikacji
    config.frame_size   = FRAME_SIZE_CAM;     // Pakiet danych o małym rozmiarze (szybkie liczenie)
    config.pixel_format = PIXFORMAT_GRAYSCALE; // Dla łatwiejszej obróbki
    config.grab_mode    = CAMERA_GRAB_LATEST;  // Dla lepszej losowości
    config.fb_location  = CAMERA_FB_IN_PSRAM;  // Można zmienić też na CAMERA_FB_IN_PSRAM, ale jest wolniejszy :(
    config.fb_count     = 2;

    // Inicjalizacja kamery
    const esp_err_t err = esp_camera_init(&config);
    if (err != ESP_OK)
    {
        Serial.printf("Camera init failed with error 0x%x", err);
        return;
    }

    // Pobieramy trzy obrazki z kamery co pół sekundy
    for (int n = 0; n < 3; n++)
    {
        camera_fb_t* image = esp_camera_fb_get();
        memcpy(baseFrames + n * FRAME_SIZE, image, FRAME_SIZE);
        esp_camera_fb_return(image);
        delay(500);
    }

    // Jesteśmy gotowi do działania
    Serial.println("Camera ready!");
    camera_ready = true;
}


// Generuje losową liczbę używając kamery
uint32_t generate_random_number()
{
    // Pobierz bufory z kamery jeżeli nie istnieją
    camera_fb_t* last_buffer = esp_camera_fb_get();

    // Zmienna zawierająca rezultat losowania
    uint32_t result       = 0;
    uint8_t  bit_count = 0;

    // Oblicz losową liczbę uwzględniając cały bufor z oveflowem
    for (uint32_t index = 0; index <= last_buffer->len; index++)
    {
        // Pomiń ramki bez szumu
        if (!is_frame_data_noisy(index)) continue;

        // Pobierz dane z bufora i przypisz do rezultatu
        const uint8_t value = last_buffer->buf[index];
        result = (result << 1) | (value & 0x01);
        bit_count++;

        // Jeżeli mamy całą liczbę zakończ działanie
        if (bit_count == sizeof(uint32_t) * 8) break;
    }

    esp_camera_fb_return(last_buffer); // Zwróć przed-ostatni bufor

    return result;
}


void loop()
{
    // Poczekaj aż kamera będzie gotowa
    if (camera_ready)
    {
        // Wylosuj liczbę i dodaj do odpowiedniego wiaderka
        const uint32_t rnd = generate_random_number();
        sampleCounts[rnd % N_MAX_VALUE]++;
        samplesCount++;

        // Policz rozkład liczb w wiaderkach
        for (int n = 0; n < N_MAX_VALUE; n++)
        {
            if (n > 0) Serial.print(" | ");
            Serial.printf("%.2f", sampleCounts[n] / (float) samplesCount * 100);
            Serial.print("%");
        }

        // Zakończ linię wyświetlając liczbę próbek
        Serial.printf(" | Total: %llu\r\n", samplesCount);
    }
}

Wstępna analiza charakterystyki sensora / generatora

By lepiej filtrować dane losowe z sensora postanowiłem, że podczas uruchamiania programu warto by pobrać trzy zdjęcia w krótkim odstępie czasowym. Pozwala to na łatwą lokalizację danych, które nie są stałe. Opóźnienie jest konieczne, by dać kamerze czas na zmianę buforów kilka-kilkanaście razy, co zapobiega przekłamaniom przy wykrywaniu identycznych wartości.

    // Pobieramy trzy obrazki z kamery co pół sekundy
    for (int n = 0; n < 3; n++)
    {
        camera_fb_t* image = esp_camera_fb_get();
        memcpy(baseFrames + n * FRAME_SIZE, image, FRAME_SIZE);
        esp_camera_fb_return(image);
        delay(500);
    }

Tym samym zmienił się też algorytm wykrywania, który teraz korzysta z funkcji weryfikującej czy dany bajt jest bajtem losowym czy stałym. Funkcja to najprostszy XOR pomiędzy trzema ramkami pobranymi na starcie programu. W celu optymalizacji można policzyć wartości i zwolnić pamięć przypisaną do pobranych ramek, ale to już kwestia implementacyjna.

uint8_t is_frame_data_noisy(const uint32_t index)
{
    // Obliczenie indeksów dla ramek
    const uint32_t realIndex0 = FRAME_SIZE * 0 + index;
    const uint32_t realIndex1 = FRAME_SIZE * 1 + index;
    const uint32_t realIndex2 = FRAME_SIZE * 2 + index;

    // Analiza danych ramek - czy na danym polu wstępuje szum?
    return baseFrames[realIndex0] ^ baseFrames[realIndex1] ^ baseFrames[realIndex2];
}

Nowy algorym generujący

Nowy algorym, podobnie jak poprzedni bazuje na robieniu "zdjęć" kamerą, aczkolwiek dzięki drobnym zmianom używa ramki 96x96px zamiast 240x240, co pozwoliło znacząco zwiększyć wydajność.

// Generuje losową liczbę używając kamery
uint32_t generate_random_number()
{
    // Pobierz bufory z kamery jeżeli nie istnieją
    camera_fb_t* last_buffer = esp_camera_fb_get();

    // Zmienna zawierająca rezultat losowania
    uint32_t result       = 0;
    uint8_t  bit_count = 0;

    // Oblicz losową liczbę uwzględniając cały bufor z oveflowem
    for (uint32_t index = 0; index <= last_buffer->len; index++)
    {
        // Pomiń ramki bez szumu
        if (!is_frame_data_noisy(index)) continue;

        // Pobierz dane z bufora i przypisz do rezultatu
        const uint8_t value = last_buffer->buf[index];
        result = (result << 1) | (value & 0x01);
        bit_count++;

        // Jeżeli mamy całą liczbę zakończ działanie
        if (bit_count == sizeof(uint32_t) * 8) break;
    }

    esp_camera_fb_return(last_buffer); // Zwróć przed-ostatni bufor

    return result;
}

Tym razem algorytm pobiera zdjęcie z ramki i jeżeli dany piksel ramki jest pikselem losowym (patrz porównanie trzech ramek pobranych na starcie programu) to dopisuje ostatni bit koloru danego piksela do wartości losowej (z przesunięciem w lewo). W ten sposób uzyskujemy bardzo duży szum, gdyż LSB (ostatni bit) jest najbardziej podatny na zakłócenia wynikające z tego, że nasz świat i nasze czujniki nie są idealne, a często wystarczy jeden zabłąkany elektron, by zmienić jego wartość.

Gdy uzyskamy pełną wartość (32 bity dla liczby uint32_t) to zamykamy procedurę generacji (early return) i zwracamy bufor do kamery (nikt nie lubi zawieszających się programów), a wygenerowaną liczbę do dalszej obróbki.

Co ciekawe algorym, mimo konstrukcji warunkowej jest nieco szybszy niż poprzednia edycja, a zarazem uzyskuje podobne rezultaty już przy 1-2 tysiącach losowań zamiast 10 tys.

Teraz można uznać to za generator liczb dostatecznie losowych, by im zaufać (nadal nie na tyle, by generować tym kody do głowic atomowych, ale do jakichś bardziej złożonych gier jak najbardziej się nada).

  • Lubię! 2

Bądź aktywny - zaloguj się lub utwórz konto!

Tylko zarejestrowani użytkownicy mogą komentować zawartość tej strony

Utwórz konto w ~20 sekund!

Zarejestruj nowe konto, to proste!

Zarejestruj się »

Zaloguj się

Posiadasz własne konto? Użyj go!

Zaloguj się »
×
×
  • Utwórz nowe...