Search code examples
postesp32clarifai

Posting a image taken by ESP32 CAM to Clarifai not working


I have the following ESP32CAM sketch that should take a picture and post it to Clarify:

#include "Arduino.h"
#include "esp_camera.h"
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <base64.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>

// Select camera model
//#define CAMERA_MODEL_WROVER_KIT // Has PSRAM
//#define CAMERA_MODEL_ESP_EYE // Has PSRAM
//#define CAMERA_MODEL_M5STACK_PSRAM // Has PSRAM
//#define CAMERA_MODEL_M5STACK_WIDE  // Has PSRAM
#define CAMERA_MODEL_AI_THINKER // Has PSRAM
//#define CAMERA_MODEL_TTGO_T_JOURNAL // No PSRAM

//CAMERA_MODEL_AI_THINKER
#define PWDN_GPIO_NUM     32
#define RESET_GPIO_NUM    -1
#define XCLK_GPIO_NUM      0
#define SIOD_GPIO_NUM     26
#define SIOC_GPIO_NUM     27
#define Y9_GPIO_NUM       35
#define Y8_GPIO_NUM       34
#define Y7_GPIO_NUM       39
#define Y6_GPIO_NUM       36
#define Y5_GPIO_NUM       21
#define Y4_GPIO_NUM       19
#define Y3_GPIO_NUM       18
#define Y2_GPIO_NUM        5
#define VSYNC_GPIO_NUM    25
#define HREF_GPIO_NUM     23
#define PCLK_GPIO_NUM     22

const char* ssid = "mySSID";
const char* password = "myPass";

void setup() {
  Serial.begin(115200);
  Serial.setDebugOutput(true);
  Serial.println();

  camera_config_t config;
  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_sscb_sda = SIOD_GPIO_NUM;
  config.pin_sscb_scl = SIOC_GPIO_NUM;
  config.pin_pwdn = PWDN_GPIO_NUM;
  config.pin_reset = RESET_GPIO_NUM;
  config.xclk_freq_hz = 20000000;
  config.pixel_format = PIXFORMAT_JPEG;
  
  // if PSRAM IC present, init with UXGA resolution and higher JPEG quality
  //                      for larger pre-allocated frame buffer.
  if(psramFound()){
    config.frame_size = FRAMESIZE_QVGA;
    config.jpeg_quality = 10;
    config.fb_count = 2;
  } else {
    config.frame_size = FRAMESIZE_QVGA;
    config.jpeg_quality = 12;
    config.fb_count = 1;
  }

#if defined(CAMERA_MODEL_ESP_EYE)
  pinMode(13, INPUT_PULLUP);
  pinMode(14, INPUT_PULLUP);
#endif

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


  #if defined(CAMERA_MODEL_M5STACK_WIDE)
    s->set_vflip(s, 1);
    s->set_hmirror(s, 1);
  #endif

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected");
  
  classifyImage();

  Serial.println("\nSleep....");
  esp_deep_sleep_start();
 
}

void loop(){
  
}

void classifyImage() {
  String response;
  
  // Capture picture
  camera_fb_t * fb = NULL;
  fb = esp_camera_fb_get();
   
  if(!fb) {
    Serial.println("Camera capture failed");
    return;
  } else {
    Serial.println("Camera capture OK");
  }

  size_t size = fb->len;
  String buffer = base64::encode((uint8_t *) fb->buf, fb->len);
  
  String imgPayload = "{\"inputs\": [{ \"data\": {\"image\": {\"base64\": \"" + buffer + "\"}}}]}";

  buffer = "";
  // Uncomment this if you want to show the payload
  Serial.println(imgPayload);

  esp_camera_fb_return(fb);
  
  // Generic model
  String model_id = "General";

  HTTPClient http;
  http.begin("https://api.clarifai.com/v2/models/" + model_id + "/outputs");
  http.addHeader("Content-Type", "application/json");     
  http.addHeader("Authorization", "c7f894790533332388e23d4d21278321"); 
  int httpResponseCode = http.POST(imgPayload);

  if(httpResponseCode>0){
    Serial.print(httpResponseCode);
    Serial.print(" Returned String: ");
    Serial.println(http.getString());
  } else {      
    Serial.print("POST Error: ");
    Serial.print(httpResponseCode);
  }                      
 
  // Parse the json response: Arduino assistant
  const int jsonSize = JSON_ARRAY_SIZE(1) + JSON_ARRAY_SIZE(20) + 3*JSON_OBJECT_SIZE(1) + 6*JSON_OBJECT_SIZE(2) + JSON_OBJECT_SIZE(3) + 20*JSON_OBJECT_SIZE(4) + 2*JSON_OBJECT_SIZE(6);
  
  StaticJsonDocument<jsonSize> doc;
  // Deserialize the JSON document
  DeserializationError error = deserializeJson(doc, response); 
  // Test if parsing succeeds.
  if (error) {
    Serial.print(F("deserializeJson() failed: "));
    Serial.println(error.f_str());
    return;
  }  

  Serial.println(jsonSize);
  Serial.println(response);

  for (int i=0; i < 10; i++) {
//    const name = doc["outputs"][0]["data"]["concepts"][i]["name"];
//    const float p = doc["outputs"][0]["data"]["concepts"][i]["value"];

    const char* name = doc["outputs"][0]["data"]["concepts"][i]["name"];
    const char* p = doc["outputs"][0]["data"]["concepts"][i]["value"];    
    
    Serial.println("=====================");
    Serial.print("Name:");
    Serial.println(name[i]);
    Serial.print("Prob:");
    Serial.println(p);
    Serial.println();
  }
}

It posts the image to Clarifai bit what I get in return is:

-400 Returned String: {"status":{"code":11102,"description":"Invalid request","details":"Empty or malformed authorization header. Please provide an API key or session token.","req_id":"39d7b4f1b7ad489fb3a9a878000f6e88"},"outputs":[]} -deserializeJson() failed: EmptyInput

What I need is to confirm if the HTTP POST request is formatted properly.


Solution

  • This problem is not the formatting of your POST request, it's the fact that your authorization header is incorrect (as the error "Empty or malformed authorization header" indicates).

    As the Clarafai documentation indicates, the Authorization header should be:

    Authorization: Key YOUR_API_KEY
    

    your code is sending

    Authorization: YOUR_API_KEY
    

    change the line that sets the Authorization header to have the "Key " before the API key.

    Given that the ESP32 is a fussy environment where a lot can go wrong with an HTTP request, a good way to debug these problems is to use the curl utility to attempt the same operation in a more full-featured environment. In this case on a Mac or Linux machine you could run

    curl -X POST -F filename -H 'Authorization: YOUR_API_KEY' -H 'Content-type: application/json' https://api.clarifai.com/v2/models/MODEL_ID/outputs
    

    where the photo you're testing with is stored in filename. The you can be sure the POST request is correct and work out what other things might be wrong.

    Also it appears that you may have posted your API key to the Internet. If that's the case, I'd recommend invalidating the one in the code you posted and generating a new one.