Фоторамка с нейросетью

Коллеги, у Алекса есть проект "Фоторамка с нейросетью" на ESP8266. Купил дисплей на контроллере ILI9488. Не могу приживить, при запуске белый экран. WEB интерфейс работает. Хотелось решить эту проблему.

 

Комментарии

dina

★★✩✩✩✩✩
3 Окт 2021
368
93
50
@rv1cj, Это наверно только кажеться ,как оригинал выглядет ,мы не знаем. Я разные стили пробывал ,были те картинки ,которые строго по центру.
 

SoftFelix

★✩✩✩✩✩✩
16 Фев 2020
56
16
Но такое ощущение что картинка сдвинута немного в право. Как можно решить эту проблемку?
В каталоге библиотеки Adafruit ST7735 есть файл Adafruit_ST77xx.cpp
и в нем по поиску нужно найти строки
x += _xstart;
y += _ystart;

если нужно сдвинуть картинку на два пикселя вправо и один пиксель вниз, то правим в блокноте так (не забываем сохранить)
x += _xstart+2;
y += _ystart+3;
 
  • Лойс +1
Реакции: Syrion

Syrion

✩✩✩✩✩✩✩
13 Дек 2024
15
8
@rv1cj ищите в своей библиотеке схожий код, связанный с координатами. Например, какие-либо методы/функции отрисовки, принимающие на вход координату левого верхнего угла, где они вызываются, и корректируйте значения по примеру SoftFelix. Обычно в библиотеках интуитивно понятно все названо.
 

Alex_48

★✩✩✩✩✩✩
4 Дек 2023
36
14
Оформи код соответствующим тэгом, см. Правила
@rv1cj,
если у вас библиотека TFT_eSPI, для смещения системы координат скорее всего, можно использовать функцию setOrigin(), код в файле TFT_eSPI.cpp библиотеки, примеров использования и описания не нашел :

функция setOrigin():
/***************************************************************************************
** Function name:           setOrigin
** Description:             Set graphics origin to position x,y wrt to top left corner
***************************************************************************************/
//Note: setRotation, setViewport and resetViewport will revert origin to top left
void TFT_eSPI::setOrigin(int32_t x, int32_t y)
{
  _xDatum = x;
  _yDatum = y;
}
Таким образом, если нужно сдвинуть картинку на два пикселя вправо и ТРИ пикселя вниз, то правим в блокноте файл TFT.h, добавив одну строку:
пример использования смещения координат:
void tft_init() {
SPI.setFrequency(6000000ul);
tft.begin();
tft.setRotation(0); //*** разворот изображения (0,1,2,3)
tft.invertDisplay(true); //*** инверсия цвета (для ili9488 на IPS-матрице)
tft.setOrigin(2,3);  // смещение  системы координат
tft.fillScreen(0x0000);
tft.setTextColor(0x07E0);
tft.setTextSize(2); //*** формат в TFT_eSPI

gen.onRender(tft_render);
}
Должно получиться, сам пока проверить не могу :rolleyes:

пока разбирался с библиотекой TFT_eSPI, нашёл ещё одну интересную функцию setSwapBytes():
функция setSwapBynes():
/***************************************************************************************
** Function name:           setSwapBytes
** Description:             Used by 16-bit pushImage() to swap byte order in colours
***************************************************************************************/
void TFT_eSPI::setSwapBytes(bool swap)
{
  _swapBytes = swap;
}
С её помощью при выводе картинки на дисплей можно отказаться от "нестандартной" функции pushRect_SW(), а использовать сразу pushImage() и "чистую" библиотеку.
в таком случае файл TFT.h проекта будет выглядеть примерно так:

файл TFT.h проекта:
#pragma once
#include "SPI.h"
#include <TFT_eSPI.h> //***
#include "gen.h"
/*   Подключение Wemos MINI R1 к TFT iLi9488
*
ESP8266(Wemos MINI) |      iLi9488
-------------------   --------------------
MISO  D6  GPIO 12  ->   SDO  (MISO)
+3,3 V  ->   BL   (LED)
SCK  D5  GPIO 14  ->   SCK
MOSI  D7  GPIO 13  ->   SDI  (MOSI)
D4  GPIO 2   ->   D/C
D3 or +3,3 V ->   RST  (RESET)
SS  D8  GPIO15   ->   CS
GND  ->   GND
+3,3 V  ->   VCC
-------------------------------------------
*/
#define TFT_CS   PIN_D8
#define TFT_DC   PIN_D4
#define TFT_RST  PIN_D3
TFT_eSPI tft = TFT_eSPI();                                 //***
void tft_render(int x, int y, int w, int h, uint8_t* buf) {
    tft.pushImage(x, y, w, h,(uint16_t*)buf); // корректные цвета при использовании setSwapBytes(true)
}

void tft_init() {
    SPI.setFrequency(6000000ul);
    tft.begin();
    tft.setRotation(0);                //*** разворот изображения  (0,1,2,3)
    tft.invertDisplay(true);         //*** инверсия цвета (для ili9488 на IPS-матрице)
    tft.setSwapBytes(true);      //*** изменение чередования цветов в кодировке RGB-565 (RGB->GRB) при некорректном отображении картинки
    tft.fillScreen(0x0000);
    tft.setTextColor(0x07E0);
    tft.setTextSize(2);               //***  формат в TFT_eSPI

    gen.onRender(tft_render);
}
 
Изменено:

rv1cj

★✩✩✩✩✩✩
22 Июн 2023
111
44
на \кране в момент загрузки ошибки? стили отображает?
Похоже?

WhatsApp Image 2025-03-04 at 21.53.50.jpeg
 

Alex_48

★✩✩✩✩✩✩
4 Дек 2023
36
14
@maggot,
Не может стили получить, проверь файл kandinskiy.h, как я писал в посте #134, чтобы список стилей или предустановленный был, или пустая строка:
String styles = "";
String status = "";


Возможно, что-то не так с API и Secret-ключами
 
Изменено:

maggot

✩✩✩✩✩✩✩
9 Авг 2024
4
0
@Alex_48,а что может быть не так с api и secret-ключми?
Все равно пишет wrong config
 

Alex_48

★✩✩✩✩✩✩
4 Дек 2023
36
14
@maggot,
Не указан стиль, отсюда wrong config:
Screenshot_20250304_215949_com.android.chrome.jpg
А список полученных стилей пустой:
IMG_20250304_220131.jpg
Возможно, у Кандинского что-то опять отвалилось, у меня сегодня после обеда больше часа висел статус "Wait Result", картинку так и не дождался:
WhatsApp Image 2025-03-05 at 22.34.03.jpeg
 

rv1cj

★✩✩✩✩✩✩
22 Июн 2023
111
44
Как вариант попробуйте сгенерить новую пару API - KEY. Если заработает, тарую просто удалите. Было нечто похожее.
 

SoftFelix

★✩✩✩✩✩✩
16 Фев 2020
56
16
На почту пришло письмо от fusionbrain.ai, что с 07.04.2025 вступят изменения по API. Так что, возможно, рамка опять перестанет работать.

Привет!
Есть важное обновление, которое касается использования API, и мы хотим заранее проинформировать вас, чтобы переход был максимально плавным.
Обновления в документации
С 7 апреля 2025 года вступят в силу изменения в документации. Мы настоятельно рекомендуем ознакомиться с новыми требованиями, чтобы ваш код продолжал работать без сбоев.
Что нужно сделать?
Обновите ваш код в соответствии с новыми требованиями в документации
Внесите изменения до 7 апреля, чтобы не было неожиданных проблем.
Если возникнут вопросы или нужна будет помощь — пишите, мы всегда рады помочь!

Спасибо, что используете нашу платформу! Мы уверены, что эти изменения сделают ваш опыт с API ещё удобнее.

С наилучшими пожеланиями,
Команда Fusion Brain
 
  • Лойс +1
Реакции: vadim352627

SoftFelix

★✩✩✩✩✩✩
16 Фев 2020
56
16
@vadim352627, я тоже пытался в самом начале перетащить рамку на ESP32, дабы они у меня были, а ESP8266 нет.
Беглая коррекция исходника не принесла успеха - даже при безошибочной компиляции, ESP32 входила в boot-loop. Потом увидел вот эту рекомендацию, стало лучше, но всё равно не заработало.

Антон

8 месяцев назад

Ответить на Антон

Победил в конечном итоге. У меня чтобы заработало на ESP32 нужно было:
  • 4я строка db.h GyverDBFile db(&LittleFS, «/settings.db»);
  • 19 строка db.h LittleFS.begin(true);
  • 57я settings.h ESP.restart();
  • перенос WiFi.mode(WIFI_AP_STA); в самый вверх void setup()
boot-loop фиксился
  • перенос WiFi.mode(WIFI_AP_STA); в самый вверх void setup()
И причина совершенно не понятна.

settings врядли причём, т.к. АГ эту библиотеку позиционирует для ESP8266 и ESP32.
 
  • Лойс +1
Реакции: vadim352627

vadim352627

★✩✩✩✩✩✩
25 Янв 2025
17
17
@SoftFelix, Огромное спасибо вам за помощь!!!! Мне чатгпт вот что сказал после этого: "Лучше сначала задать режим, а потом уже подключаться к сети – это помогает избежать сбоев и перезагрузок."
Пока что единственная проблема которая осталась - это вывод изображения на экран, изображение как то искажается и выдаёт ошибку "jpg error"

upd. ((Пока проблему еще не решил, кто знает - отзовитесь))
 
Изменено:

vadim352627

★✩✩✩✩✩✩
25 Янв 2025
17
17
Привет всем, на данный момент судя по новой документации (Должна вступить в силу 7 апреля) код должен выглядеть вот так:

Kandinsky.h:
#pragma once
#include <Arduino.h>

// config
#define FUSION_HOST "api-key.fusionbrain.ai"
#define FUSION_PORT 443
#define FUSION_PERIOD 6000
#define FUSION_TRIES 5
#define FUS_LOG(x) Serial.println(x)

// #define GHTTP_HEADERS_LOG Serial
#include <GSON.h>
#include <GyverHTTP.h>

#include "StreamB64.h"
#include "tjpgd/tjpgd.h"

#ifdef ESP8266
#include <ESP8266WiFi.h>
#include <WiFiClientSecure.h>
#include <WiFiClientSecureBearSSL.h>
#define FUSION_CLIENT BearSSL::WiFiClientSecure
#else
#include <WiFi.h>
#include <WiFiClientSecure.h>
#define FUSION_CLIENT WiFiClientSecure
#endif

class Kandinsky {
    typedef std::function<void(int x, int y, int w, int h, uint8_t* buf)> RenderCallback;
    typedef std::function<void()> RenderEndCallback;

    enum class State : uint8_t {
        GetModels,
        Generate,
        Status,
        GetStyles,
    };

   public:
    Kandinsky() {}
    Kandinsky(const String& apikey, const String& secret_key) {
        setKey(apikey, secret_key);
    }

    void setKey(const String& apikey, const String& secret_key) {
        if (apikey.length() && secret_key.length()) {
            _api_key = "Key " + apikey;
            _secret_key = "Secret " + secret_key;
        }
    }

    void onRender(RenderCallback cb) {
        _rnd_cb = cb;
    }

    void onRenderEnd(RenderEndCallback cb) {
        _end_cb = cb;
    }

    // 1, 2, 4, 8
    void setScale(uint8_t scale) {
        switch (scale) {
            case 1: _scale = 0; break;
            case 2: _scale = 1; break;
            case 4: _scale = 2; break;
            case 8: _scale = 3; break;
            default: _scale = 0; break;
        }
    }

    bool begin() {
        if (!_api_key.length()) return false;
        return request(State::GetModels, FUSION_HOST, "/key/api/v1/pipelines");
    }

    bool getStyles() {
        if (!_api_key.length()) return false;
        return request(State::GetStyles, "cdn.fusionbrain.ai", "/static/styles/key");
    }

    bool generate(Text query, uint16_t width = 512, uint16_t height = 512, Text style = "DEFAULT", Text negative = "") {
        status = "wrong config";
        if (!_api_key.length()) return false;
        if (!style.length()) return false;
        if (!query.length()) return false;
        if (!_id.length()) return false;

        gson::string json;
        json.beginObj();
        json.addString(F("type"), F("GENERATE"));
        json.addString(F("style"), style);
        json.addString(F("negativePromptDecoder"), negative);
        json.addInt(F("width"), width);
        json.addInt(F("height"), height);
        json.addInt(F("numImages"), 1);
        json.beginObj(F("generateParams"));
        json.addString(F("query"), query);
        json.endObj();
        json.endObj(true);

        FUS_LOG("JSON being sent:");
        FUS_LOG(json);

        ghttp::Client::FormData data;
        data.add("pipeline_id", "", "", Value(_id));
        data.add("params", "blob", "application/json", json);

        uint8_t tries = FUSION_TRIES;
        while (tries--) {
            if (request(State::Generate, FUSION_HOST, "/key/api/v1/pipeline/run", "POST", &data)) {
                FUS_LOG("Gen request sent");
                status = "wait result";
                return true;
            } else {
                FUS_LOG("Gen request error");
                delay(2000);
            }
        }
        status = "gen request error";
        return false;
    }

    bool getImage() {
        if (!_api_key.length()) return false;
        if (!_uuid.length()) return false;
        FUS_LOG("Check status...");
        String url("/key/api/v1/pipeline/status/");
        url += _uuid;
        return request(State::Status, FUSION_HOST, url);
    }

    void tick() {
        if (_uuid.length() && millis() - _tmr >= FUSION_PERIOD) {
            _tmr = millis();
            getImage();
        }
    }

    String modelID() { return _id; }
    String styles = "DEFAULT;ANIME;UHD;KANDINSKY";
    String status = "idle";

   private:
    String _api_key;
    String _secret_key;
    String _uuid;
    uint8_t _scale = 0;
    uint32_t _tmr = 0;
    String _id;
    RenderCallback _rnd_cb = nullptr;
    RenderEndCallback _end_cb = nullptr;
    StreamB64* _stream = nullptr;

    // static
    static Kandinsky* self;

    static size_t jd_input_cb(JDEC* jdec, uint8_t* buf, size_t len) {
        if (self) {
            self->_stream->readBytes(buf, len);
        }
        return len;
    }

    static int jd_output_cb(JDEC* jdec, void* bitmap, JRECT* rect) {
        if (self && self->_rnd_cb) {
            self->_rnd_cb(rect->left, rect->top, rect->right - rect->left + 1, rect->bottom - rect->top + 1, (uint8_t*)bitmap);
        }
        return 1;
    }

    // system
    bool request(State state, Text host, Text url, Text method = "GET", ghttp::Client::FormData* data = nullptr) {
        FUSION_CLIENT client;
#ifdef ESP8266
        client.setBufferSizes(512, 512);
#endif
        client.setInsecure();
        ghttp::Client http(client, host.str(), FUSION_PORT);

        ghttp::Client::Headers headers;
        headers.add("X-Key", _api_key);
        headers.add("X-Secret", _secret_key);

        bool ok = data ? http.request(url, method, headers, *data)
                       : http.request(url, method, headers);
        if (!ok) {
            FUS_LOG("Request error");
            return false;
        }

        ghttp::Client::Response resp = http.getResponse();
        FUS_LOG("Response code: " + String(resp.code()));
        if (resp && resp.code() >= 200 && resp.code() < 300) {
            if (state == State::Status) {
                bool ok = parseStatus(resp.body());
                http.flush();
                return ok;
            } else {
                gtl::stack_uniq<uint8_t> str;
                resp.body().writeTo(str);
                gson::Parser json;
                if (!json.parse(str.buf(), str.length())) {
                    FUS_LOG("Parse error");
                    return false;
                }
                return parse(state, json);
            }
        } else {
            http.flush();
            FUS_LOG("Error" + String(resp.code()));
            FUS_LOG("Response error");
        }
        return false;
    }

    bool parseStatus(Stream& stream) {
        bool found = false;
        bool insideResult = false;
        while (stream.available()) {
            stream.readStringUntil('"');
            String key = stream.readStringUntil('"');
            if (key == "result") {
                insideResult = true;
                continue;
            }
            if (insideResult && key == "files") {
                found = true;
                break;
            }
            stream.readStringUntil('"');
            String val = stream.readStringUntil('"');
            if (!key.length() || !val.length()) break;

            if (key == "status") {
                switch (Text(val).hash()) {
                    case SH("INITIAL"):
                    case SH("PROCESSING"):
                        return true;
                    case SH("DONE"):
                        _uuid = "";
                        break;
                    case SH("FAIL"):
                        _uuid = "";
                        status = "gen fail";
                        return false;
                }
            }
        }
        if (found) {
            stream.readStringUntil('"');
            uint8_t* workspace = new uint8_t[TJPGD_WORKSPACE_SIZE];
            if (!workspace) {
                FUS_LOG("allocate error");
                return false;
            }

            JDEC jdec;
            jdec.swap = 0;
            JRESULT jresult = JDR_OK;
            StreamB64 sb64(stream);
            _stream = &sb64;
            self = this;

            jresult = jd_prepare(&jdec, jd_input_cb, workspace, TJPGD_WORKSPACE_SIZE, 0);
            if (jresult == JDR_OK) {
                jresult = jd_decomp(&jdec, jd_output_cb, _scale);
                if (jresult == JDR_OK && _end_cb) _end_cb();
            } else {
                FUS_LOG("jdec error");
            }

            self = nullptr;
            delete[] workspace;
            status = jresult == JDR_OK ? "gen done" : "jpg error";
            return jresult == JDR_OK;
        }
        return true;
    }

    bool parse(State state, gson::Parser& json) {
        switch (state) {
            case State::GetStyles:
                styles = "";
                for (int i = 0; i < (int)json.rootLength(); i++) {
                    if (i) styles += ';';
                    json[i]["name"].addString(styles);
                }
                return styles.length();

            case State::GetModels:
                json[0]["id"].toString(_id);
                if (_id.length()) return true;
                break;

            case State::Generate:
                _tmr = millis();
                json["uuid"].toString(_uuid);
                if (_uuid.length()) return true;
                break;

            default:
                break;
        }
        return false;
    }
};

Kandinsky* Kandinsky::self [B]attribute[/B]((weak)) = nullptr;
[UPD.] Обновил код на рабочий
Последняя строка Kandinsky* Kandinsky::self __attribute__((weak)) = nullptr; , т.к. сайт зачем то меняет нижние подчеркивания на B.
 
Изменено:
  • Лойс +1
Реакции: dvdiamond и SoftFelix

BRGN

✩✩✩✩✩✩✩
5 Апр 2025
2
6
@vadim352627, У вас в begin должно стоять key/api/v1/pipelines, в 123 строке "pipeline_id" , возможно где то еще пропустили часть из документации, но самое важное, что в json который при инициализации получаешь теперь в id возвращает текстовые данные, а не число как было до этого. Я пытался разобраться, ничего не вышло, если у вас получится, буду рад если поделитесь кодом)
 
  • Лойс +1
Реакции: vadim352627

dvdiamond

★✩✩✩✩✩✩
15 Авг 2024
9
10
Да, сдохло.
C++:
Init AI model...
Error!
Но стили подтягиваются.

цитата:
Важно отметить, что получение картинки осуществляется только один раз, повторно запросить нельзя.
Хм. Было так? Или теперь только одна картинка на запрос (если через 10 минут повторить такой же запрос, то картинку не даст)?
 
Изменено:
  • Лойс +1
Реакции: vadim352627

koss158

✩✩✩✩✩✩✩
9 Апр 2025
2
3
Я в общем разобрался.
Спасибо, рамка заработала, только в последней 347 строчке заругалось на /B перед и до attribute, заменил на строчку с оригинального проекта Алекса с гитхаба - всё собралось.
 
  • Лойс +1
Реакции: SoftFelix и dvdiamond