sabato 12 luglio 2025

Apriltag 3 e pose estimation

 Con la versione 3 di Apriltag e' possibile avere la pose estimation del tag (ho usato 41h12)

il repository di riferimento e' https://github.com/zhenzhenxiang/apriltag3

(ve ne sono altri ma ho avuto problemi di compilazione all'interno di Debian Testing)

 

Si clona di github il progetto (al momento 3.4.3) e si crea un folder ap3 dove inserire il seguente Makefile e il successivo main.cpp

# Adjust these paths if needed
APRILTAG_DIR = ../
CXX = g++
CXXFLAGS = -Wall -O2 -std=c++11

# Paths for AprilTag
INCLUDES = -I$(APRILTAG_DIR) -I$(APRILTAG_DIR)/common
LIBS = -L$(APRILTAG_DIR) -lapriltag

# Use OpenCV4 pkg-config flags
OPENCV_CFLAGS := $(shell pkg-config --cflags opencv4)
OPENCV_LIBS := $(shell pkg-config --libs opencv4)

# Source file
SRC = main.cpp
OUT = pos_tag

all: $(OUT)

$(OUT): $(SRC)
$(CXX) $(CXXFLAGS) $(SRC) $(INCLUDES) $(LIBS) $(OPENCV_CFLAGS) $(OPENCV_LIBS) -o $(OUT)

clean:
rm -f $(OUT)


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

extern "C" {
#include "apriltag.h"
#include "tagStandard41h12.h"
#include "common/getopt.h"
#include "apriltag_pose.h"
}

int main(int argc, char* argv[]) {
// --- Load image ---
if (argc < 2) {
std::cerr << "Usage: " << argv[0] << " <image_path>" << std::endl;
return 1;
}

std::string imagePath = argv[1];
cv::Mat image = cv::imread(imagePath, cv::IMREAD_GRAYSCALE);
if (image.empty()) {
std::cerr << "Failed to load image\n";
return -1;
}

// --- Setup detector ---
apriltag_family_t *tf = tagStandard41h12_create();
apriltag_detector_t *td = apriltag_detector_create();
apriltag_detector_add_family(td, tf);
td->quad_decimate = 1.0;
td->quad_sigma = 0.0;
td->nthreads = 4;
td->debug = false;
td->refine_edges = true;

// --- Convert OpenCV image to Apriltag image ---
image_u8_t im = {
.width = image.cols,
.height = image.rows,
.stride = image.cols,
.buf = image.data
};

// --- Detect tags ---
zarray_t *detections = apriltag_detector_detect(td, &im);
//std::cout << "Detected " << zarray_size(detections) << " tags.\n";

// --- Camera parameters (replace with calibrated values) ---
double fx = 663.64233754; // focal length x
double fy = 664.86615495; // focal length y
double cx = image.cols / 2.0;
double cy = image.rows / 2.0;
double tagsize = 0.150; // in meters

for (int i = 0; i < zarray_size(detections); i++) {
apriltag_detection_t *det;
zarray_get(detections, i, &det);

std::cout << det->id;

apriltag_detection_info_t info;
info.det = det;
info.tagsize = tagsize;
info.fx = fx;
info.fy = fy;
info.cx = cx;
info.cy = cy;

apriltag_pose_t pose;
estimate_tag_pose(&info, &pose);

// --- Translation ---
double x = pose.t->data[0];
double y = pose.t->data[1];
double z = pose.t->data[2];

std::cout << ";" << x << ";" << y << ";" << z;

// --- Convert rotation matrix to yaw-pitch-roll ---
double r00 = pose.R->data[0], r01 = pose.R->data[1], r02 = pose.R->data[2];
double r10 = pose.R->data[3], r11 = pose.R->data[4], r12 = pose.R->data[5];
double r20 = pose.R->data[6], r21 = pose.R->data[7], r22 = pose.R->data[8];

double yaw = atan2(r10, r00);
double pitch = atan2(-r20, sqrt(r21 * r21 + r22 * r22));
double roll = atan2(r21, r22);

std::cout << ";" << yaw * 180.0 / M_PI
<< ";" << pitch * 180.0 / M_PI
<< ";" << roll * 180.0 / M_PI << "\n";

matd_destroy(pose.R);
matd_destroy(pose.t);
}

apriltag_detections_destroy(detections);
apriltag_detector_destroy(td);
tagStandard41h12_destroy(tf);

return 0;
}


 

 

 

giovedì 10 luglio 2025

Calibrazione PiCam HQ 12 Mpx

 Ho provato a calibrare la PiCam HQ 12.3 Mpx con sensore Sony IMX477

In modalita' foto la risoluzione e' 4056x3040 mentre in modalita' video parte da 640x480 30 fps


Ho usato sia una serie di foto sia un video usando lo script a link sottostante


 https://github.com/yumashino/Camera-Calibration-with-ChArUco-Board/tree/main

 


 

rispetto alla documentazione non e' piu' necessario usare libcam ma per scattare le foto si usa

 

rpicam-still --encoding png -o img_01.png

rpicam-still --raw -o 01.jpg (salva anche in DNG)

per i video

rpicam-vid  -t 10s --width 2028 --height 1080 -o test.mp4 (salva 10 secondi in h264)

 

calibration_time: "Thu 10 Jul 2025 07:32:59 CEST"
image_width: 4032
image_height: 3024
flags: 0
camera_matrix: !!opencv-matrix
   rows: 3
   cols: 3
   dt: d
   data: [ 3377.8915344697502, 0., 2032.4285452943598, 0.,
       3394.7675726553693, 1455.5140917783713, 0., 0., 1. ]
distortion_coefficients: !!opencv-matrix
   rows: 1
   cols: 5
   dt: d
   data: [ 0.24433970441928685, -1.371442972691743,
       0.0033488938962143514, -0.00060884641206734576,
       2.3798752100481146 ]
avg_reprojection_error: 2.668497308654219



mercoledì 9 luglio 2025

Charuco camera calibration

Attenzione : la tavola di calibrazione e' cambiata nelle versioni recenti di Opencv (da 4.6 in poi....in pratica nelle versioni vecchie il quadrato in alto a sinistra e' occupato da un aruco tag mentre in quelle moderne inizia con un quadrato nero....per fare le calibrazioni con le tavole vecchie si deve usare board.setLegacyPattern(true)

 

vecchia versione

nuova versione

Ho trovato che nel pacchetto opencv e precisamente in samples/cpp/tutorial_code/objectDetection e' gia' presente un programma che permette di fare la calibrazione della camera partendo da immagini o inquadrando in modalita' video una tavola di calibrazione

Per la tavola di calibrazione ho usato questo link che genera una charuco board 8x11
 


 

 nel caso si usino immagini gia' pronte con nome tipo img_01.jpg. img_02.jpg

  ./calibrate_camera_charuco -w=11 -h=8 --sl=0.03 --ml=0.022 -d=0 -v=/home/luca/iphone/img_%02d.png

Nel caso del video 

 ./calibrate_camera_charuco -w=11 -h=8 --sl=0.03 --ml=0.022 -d=0 --ci=0

 i risultati sono salvati nel file cam.yml

Calibrazione colore DYI

Volevo provare a farmi una calibrazione colore in casa senza passare dallo spendere un centinaio di euro per le tavole colore (esempio quella di XRite)

Mi sono scaricato il pdf di questa immagine da Wikipedia

 

Per ogni mattonella sono riportati i valori rgb di riferimento

Ho poi fotografato con un IPhone una immagine ed ho messo a confronto i valori rgb di ogni mattonella della foto scattata con i valori rgb della tavola di riferimento

E' stato usato Gimp per selezionare un quadrato all'interno della mattonella colore e ed avere il valore RGB medio della selezione



Come si vede la relazione e' di tipo lineare ma e' ben lontana da y=x

Mi sono scritto questo semplice codice che trasforma i valori dei pixel 

import cv2
import numpy as np

# Read the input image
image = cv2.imread('1.jpg') # Change to your image filename

if image is None:
raise FileNotFoundError("Image not found. Make sure the path is correct.")

# Convert image to float for accurate scaling
image_float = image.astype(np.float32)

# Split channels (OpenCV uses BGR format by default)
B, G, R = cv2.split(image_float)

# Apply scaling factors
R = (R * 1.047)-21.3
G = (G * 1.206)-27.3
B = (B * 1.23)-22.2

# Merge back the channels
result = cv2.merge((B, G, R))

# Clip values to valid range [0, 255] and convert back to uint8
result = np.clip(result, 0, 255).astype(np.uint8)

# Save the output image
cv2.imwrite('output1.jpg', result)



l'immagine corretta risulta essere questa


Come si osserva dal bianco della carta l'immagine corretta ha colori piu' fedeli alla realta'. Non dico che siamo a livello delle tavole di colore da 100 euro e dei software professionali ma e' decisamente un passo avanti










 

 

 

 

Convertire HEIC su Linux

 Usando un Iphone come telefono mi sono trovato a dover usare il formato HEIC per le foto. 

Per la conversione da HEIC in jpg (heic e' non e' lossless quindi e' inutile usare png) si installa

apt install libheif-examples

e si converte tramite

heif-convert IMG.HEIC IMG.JPG

venerdì 20 giugno 2025

NanoProtoBuf Esp32 IDF

Su Esp32 (e piu' in generale sui microcontrollori) la libreria Protobuf non puo' funzionare e quindi si deve usare nanopb (nano proto buffer)  https://github.com/nanopb/nanopb

Il seguente progetto e' stato creato usando il plugin Esp-Idf di VSCode

Per utilizzarla si deve prima installare il compilare protoc e poi scaricare e compilare il pacchetto nanopb da GitHub ...questo servira' come plugin per protoc

Si procede  quindi con il seguente comando che genera i file sensor.pb.c e sensor.pb.h dove sono inserite le classi per la lettura scrittura del file sensor.proto. questi due file sono da inserire nella main del progetto mentre i file pb_common.c pb_common.h pb_decode.c pb_decode.h pc_encode.c e pb_encode.h si inseriscono nel folder nanopb all'interno del folder main  

protoc --plugin=protoc-gen-nanopb=/home/luca/nanopb/generator/protoc-gen-nanopb --nanopb_out=./ --proto_path=/home/luca/nanopb/generator/proto sensor.proto

sensor.proto

syntax = "proto3";

message SensorData {
int32 id = 1;
float temperature = 2;
float humidity = 3;
}

Si modifica quindi il file CMakeLists.txt per puntare i nuovi file

idf_component_register(
SRCS
"main.c"
"sensor.pb.c"
"nanopb/pb_encode.c"
"nanopb/pb_decode.c"
"nanopb/pb_common.c"
INCLUDE_DIRS
"."
"nanopb"
)

e si puo' compilare questo semplice programma di esempio sull'utilizzo di nanopb 

#include <stdio.h>

#include "sensor.pb.h"
#include "pb_encode.h"
#include "pb_decode.h"

#include "driver/uart.h"

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

void app_main(void)
{
const uart_config_t uart_config = {
.baud_rate = 115200,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE
};



uart_driver_install(UART_NUM_0, 1024, 0, 0, NULL, 0);
uart_param_config(UART_NUM_0, &uart_config);

const char *msg = "Hello from UART0 via USB!\n";
uart_write_bytes(UART_NUM_0, msg, strlen(msg));


vTaskDelay(pdMS_TO_TICKS(3000));
printf("USB CDC active\n");

vTaskDelay(pdMS_TO_TICKS(500)); // Delay to let USB CDC connect


uint8_t buffer[128];
pb_ostream_t stream = pb_ostream_from_buffer(buffer, sizeof(buffer));

SensorData da = SensorData_init_zero;
da.id = 42;
da.temperature = 23.5;
da.humidity = 55.2;

if (pb_encode(&stream, SensorData_fields, &da)) {
size_t message_length = stream.bytes_written;
// Send buffer over serial, BLE, etc.
} else {
//Serial.println("Encoding failed!");
}

size_t message_length = stream.bytes_written;

SensorData da_r = SensorData_init_zero;
//int received_length = 0;
pb_istream_t str = pb_istream_from_buffer(buffer, message_length);

while(1){
if (pb_decode(&str, SensorData_fields, &da_r)) {
char str_out[32];
snprintf(str_out, sizeof(str_out), "%.2f\n", da_r.humidity);
uart_write_bytes(UART_NUM_0, str_out, strlen(str_out));
printf("Temp: %.2f, Humidity: %.2f\n",da_r.temperature, da_r.humidity);
} else {
uart_write_bytes(UART_NUM_0, "Decoding error\n", strlen("Decoding error\n"));
printf("Error decoding\n");
}

vTaskDelay(pdMS_TO_TICKS(1000));}

}

 

 

 

Analisi MNF su spettri di riflettanza di plastica

Devo cerca di lavorare su spettri di riflettanza di plastica e la prima domanda e': quale sono le bande significative? Sono partito dal ...