10浏览
查看: 10|回复: 2

[项目] 【Arduino 动手做】使用K10构建人工智能驱动的宝丽来相机

[复制链接]
将您的 Unihiker K10 变****工智能驱动的宝丽来相机,可以实时拍摄照片并应用令人惊叹的人工智能生成效果!该项目结合了 Google 的 Gemini AI 和 ImageRouter 的强大功能,创造了一种神奇的摄影体验,让每张照片都成为一件艺术品。

该设备具有直观的界面,带有物理按钮、RGB 灯光效果和摇动捕捉机制,使摄影感觉自然且引人入胜。当您拍摄照片时,它会通过人工智能自动处理,以创造独特的艺术诠释。


您将构建的内容
人工智能相机:拍照并应用人工智能生成的艺术效果
交互界面:物理按钮和抖动检测,实现自然交互
实时处理:连接WiFi的后端处理AI处理
视觉反馈:RGB 灯光和屏幕动画指导用户体验
3D 打印外壳:定制外壳,感觉就像真正的宝丽来相机

硬件要求
Unihiker K10 - 主开发板(在这里购买)
3D 打印机 - 用于打印定制外壳
SD 卡 - 最小 512MB(FAT32 格式)
计算机 - 用于 Arduino IDE 和服务器设置
USB 电缆 - 用于编程和电源
关于 Unihiker K10
Unihiker K10 是一款集成了以下功能的人工智能学习设备:
2.8英寸彩色触摸屏
内置摄像头
WiFi 和蓝牙连接
RGB LED 灯
加速度计(用于抖动检测)
扬声器和麦克风
温度、湿度和光传感器
用于附加传感器的边缘连接器

软件要求
Arduino IDE - 用于固件开发
带有 uv 包管理器的 Python - 用于 AI 后端服务器
Google Gemini API 密钥 - 用于 AI 图像处理
ImageRouter API 密钥 - 用于其他图像效果
开始之前
您可以在此存储库中找到项目文件和 3D 打印:https://github.com/pham-tuan-binh/memento

该存储库还包含一系列不同项目的代码和说明。

第 1 步:硬件组装
该项目包括三个主要的 3D 打印组件:

1. 车身 - Unihiker K10 的主外壳

2. 背板 - 覆盖背面(选择带螺丝或不带螺丝)

3. Button - 交互式按钮组件

打印设置:

材质:推荐 PLA 或 PETG
填充物:所有零件 15%
支持:为悬垂启用
层高:0.2mm,质量好
方向:打印体,大平面朝下
组装步骤:

1. 先将按钮插入侧面的孔中

2. 将 Unihiker K10 放入机身,将 Type-C 端口与其孔对齐

3. 将背板安装到设备背面

4.插入SD卡(必须在开机前完成)

第 2 步:软件设置
Arduino IDE 配置

1. 安装 Arduino IDE(如果尚未安装)

2. 设置 Unihiker K10 板支持:

遵循 Unihiker 官方文档
注意:如果遇到文档错误,请检查更新的板包
3. 打开宝丽来项目:

打开 Arduino IDE
转到文件→ 打开
导航到项目/宝丽来/
选择 polaroid.ino
WiFi 配置

1. 在您的项目中打开 wifi_helper.ino

2. 更新 WiFi 凭据:

const char *WIFI_SSID = "YOUR_WIFI_NETWORK_NAME";
const char *WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";
3.保存文件

服务器 IP 配置

1. 打开 gen_ai_helper.ino

2. 使用您计算机的 IP 地址更新服务器 URL:

const char *SERVER_URL = "http://YOUR_COMPUTER_IP:8000/upload_adv";
要查找您计算机的 IP:

Windows: 在命令提示符下运行 ipconfig
Mac/Linux: 在终端中运行 ifconfig 或 ip addr
查找您的本地网络 IP(通常以“192.168”或“10”开头)。
第 3 步:后端服务器设置
安装uv包管理器

# Windows:
powershell -c "irm https://astral.sh/uv/install.ps1 | iex

# macOS/Linux
curl -LsSf https://astral.sh/uv/install.sh | sh
设置 API 密钥

1. 导航到服务器目录:

cd project/polaroid/server
2. 复制环境模板:

cp .example.env .env
3. 使用您的 API 密钥编辑 .env 文件:

GEMINI_API_KEY=your_actual_gemini_api_key_here
IMAGEROUTER_API_KEY=your_actual_imagerouter_api_key_here
获取 API 密钥

谷歌双子座 API:

1. 前往 Google AI Studio

2. 使用您的 Google 帐户登录

3. 单击“获取 API 密钥”或导航至 API 密钥部分

4. 创建新的 API 密钥

5. 将其复制并粘贴到您的“.env”文件中

ImageRouter API:

1. 转到 ImageRouter

2. 注册一个帐户

3. 导航到您的 API 密钥部分

4. 生成新的 API 密钥

5. 将其复制并粘贴到您的“.env”文件中

启动服务器

1. 运行服务器:

uv run main.py
2. 通过打开浏览器来验证它是否正常工作:http://localhost:8000/docs

这显示了 FastAPI 文档界面,您可以在其中测试端点。

第 4 步:SD 卡设置
准备SD卡

1. 将 SD 卡格式化为 FAT32

2. 复制存储文件:

将“storage/”目录的全部内容复制到 SD 卡的根目录
确保保留文件夹结构
3. 开机前将 SD 卡插入 Unihiker K10

宝丽来项目所需文件:

存储/宝丽来/ - UI 图像(begin.jpg、loading.jpg、shake.jpg)
存储/shutter.wav - 相机快门声音
存储/loading.wav - 处理声音
第 5 步:上传固件
编译和上传

1. 通过 USB 将 Unihiker K10 连接到计算机

2. 选择正确的板:Unihiker K10

3.选择正确的端口

4. 上传代码

5. 等待完成 - 设备将自动重启

第 6 步:测试您的 AI 宝丽来相机
开机和测试

1. 打开设备电源 - 它应该显示“开始”屏幕

2. 验证 WiFi 连接 - 设备应连接到您的网络

3. 测试相机 - 按按钮 A 启动相机模式

使用相机

1. 开始:按按钮A进入相机模式

2. 拍摄:再次按按钮 A 拍照

3. 处理:在 AI 处理图像时观看加载动画

4. 摇动显示:摇动设备即可查看 AI 生成的结果

5. 比较:按按钮 B 在原始图像和 AI 处理后的图像之间切换

6. 新照片:按按钮 A 拍摄另一张照片

视觉反馈:

RGB 灯:加载时紫/蓝波,摇晃时白闪
屏幕状态:不同的图像引导您完成每个步骤
音效:拍摄时有快门声,处理时加载声音
结论
祝贺!您已经成功打造了一款人工智能驱动的宝丽来相机,将即时摄影的怀旧之情与尖端的人工智能技术相结合。该项目展示了如何使用 Unihiker K10 等现代硬件平台来创造引人入胜的互动体验,从而弥合传统摄影和人工智能驱动的创造力之间的差距。

该项目的模块化设计使得尝试不同的人工智能模型、添加新功能或使其适应其他创意应用程序变得容易。无论您是对计算机视觉、物联网开发感兴趣,还是只是想创造一些独特的东西,这个项目都为进一步探索提供了坚实的基础。

【Arduino 动手做】使用K10构建人工智能驱动的宝丽来相机图7

【Arduino 动手做】使用K10构建人工智能驱动的宝丽来相机图1

【Arduino 动手做】使用K10构建人工智能驱动的宝丽来相机图2

【Arduino 动手做】使用K10构建人工智能驱动的宝丽来相机图3

【Arduino 动手做】使用K10构建人工智能驱动的宝丽来相机图4

【Arduino 动手做】使用K10构建人工智能驱动的宝丽来相机图5

【Arduino 动手做】使用K10构建人工智能驱动的宝丽来相机图6

驴友花雕  中级技神
 楼主|

发表于 昨天 16:51

【Arduino 动手做】使用K10构建人工智能驱动的宝丽来相机

项目代码

  1. #include "unihiker_k10.h"
  2. #include <HTTPClient.h>
  3. #include <WiFi.h>
  4. #include <WebSocketsClient.h>
  5. #include <ArduinoJson.h>
  6. #include <esp_camera.h>
  7. // Server configuration
  8. const char* SERVER_HOST = "10.8.162.58";
  9. const int SERVER_PORT = 7860;
  10. String USER_ID = "550e8400-e29b-41d4-a716-446655440000";
  11. // Wifi Configuration
  12. const char* ssid = "PUT YOUR WIFI SSID HERE";
  13. const char* password = "PUT YOUR WIFI PASSWORD HERE";
  14. // Create UNIHIKER K10 instance
  15. UNIHIKER_K10 board;
  16. // WebSocket and HTTP clients
  17. WebSocketsClient webSocket;
  18. HTTPClient httpClient;
  19. // Connection state
  20. bool serverConnected = false;
  21. bool waitingForFrameRequest = false;
  22. // Task handles
  23. TaskHandle_t websocketTaskHandle = NULL;
  24. TaskHandle_t streamingTaskHandle = NULL;
  25. TaskHandle_t cameraTaskHandle = NULL;
  26. // LVGL
  27. extern SemaphoreHandle_t xLvglMutex;
  28. // Display object
  29. lv_obj_t *diffusedImageObject = NULL;
  30. // Camera queue and global frame storage
  31. QueueHandle_t xQueueCamera = NULL;
  32. camera_fb_t *globalFrame = NULL;
  33. SemaphoreHandle_t frameMutex = NULL;
  34. bool frameReady = false;
  35. // WebSocket event handler
  36. void webSocketEvent(WStype_t type, uint8_t * payload, size_t length) {
  37.     switch(type) {
  38.         case WStype_DISCONNECTED:
  39.             Serial.println("WebSocket Disconnected - attempting reconnect...");
  40.             serverConnected = false;
  41.             break;
  42.             
  43.         case WStype_CONNECTED:
  44.             Serial.printf("WebSocket Connected to: %s\n", payload);
  45.             serverConnected = true;
  46.             break;
  47.             
  48.         case WStype_TEXT: {
  49.             Serial.printf("Received: %s\n", payload);
  50.             DynamicJsonDocument doc(1024);
  51.             deserializeJson(doc, payload);
  52.             
  53.             String status = doc["status"];
  54.             if (status == "send_frame") {
  55.                 waitingForFrameRequest = true;
  56.             }
  57.             break;
  58.         }
  59.         
  60.         case WStype_ERROR:
  61.             Serial.printf("WebSocket Error: %s\n", payload);
  62.             break;
  63.             
  64.         case WStype_FRAGMENT_TEXT_START:
  65.         case WStype_FRAGMENT_BIN_START:
  66.         case WStype_FRAGMENT:
  67.         case WStype_FRAGMENT_FIN:
  68.             Serial.println("WebSocket Fragment received");
  69.             break;
  70.             
  71.         default:
  72.             Serial.printf("WebSocket event type: %d\n", type);
  73.             break;
  74.     }
  75. }
  76. // Continuous camera task - always consuming frames
  77. void cameraTask(void* parameter) {
  78.     camera_fb_t *frame = NULL;
  79.    
  80.     while (true) {
  81.         // Aggressively consume ALL frames from queue to prevent overflow
  82.         while (xQueueReceive(xQueueCamera, &frame, pdMS_TO_TICKS(1))) {
  83.             
  84.             // Take mutex and update global frame
  85.             if (xSemaphoreTake(frameMutex, pdMS_TO_TICKS(1)) == pdTRUE) {
  86.                 // Release previous global frame if exists
  87.                 if (globalFrame != NULL) {
  88.                     esp_camera_fb_return(globalFrame);
  89.                 }
  90.                
  91.                 // Store new frame globally
  92.                 globalFrame = frame;
  93.                 frameReady = true;
  94.                
  95.                 xSemaphoreGive(frameMutex);
  96.                
  97.                 // Only print frame info occasionally
  98.                 static uint32_t frameCount = 0;
  99.                 if (frameCount % 100 == 0) {
  100.                     Serial.printf("Frame #%d: %dx%d, %d bytes\n", frameCount, frame->width, frame->height, frame->len);
  101.                 }
  102.                 frameCount++;
  103.             } else {
  104.                 // If can't get mutex, just return the frame to prevent memory leak
  105.                 esp_camera_fb_return(frame);
  106.             }
  107.         }
  108.         
  109.         vTaskDelay(pdMS_TO_TICKS(10)); // Fast consumption rate
  110.     }
  111. }
  112. // WebSocket task
  113. void websocketTask(void* parameter) {
  114.     unsigned long lastSendTime = 0;
  115.     unsigned long lastDebugTime = 0;
  116.     const unsigned long sendInterval = 250; // Send frame every 1000ms (1 second)
  117.     const unsigned long debugInterval = 5000; // Debug print every 5 seconds
  118.    
  119.     while (true) {
  120.         if (WiFi.status() == WL_CONNECTED) {
  121.             webSocket.loop();
  122.             
  123.             // Always try to send frames when connected and frame is ready
  124.             if (serverConnected && frameReady && waitingForFrameRequest) {
  125.                 Serial.println("Sending frame to server...");
  126.                 sendCameraFrame();
  127.                 waitingForFrameRequest = false;
  128.                 lastSendTime = millis();
  129.             } else if (!serverConnected && (millis() - lastDebugTime > debugInterval)) {
  130.                 Serial.println("DEBUG: Server not connected");
  131.                 lastDebugTime = millis();
  132.             }
  133.         } else {
  134.             Serial.println("WiFi disconnected, attempting reconnect...");
  135.             connectToWiFi();
  136.         }
  137.         
  138.         vTaskDelay(pdMS_TO_TICKS(50)); // Check more frequently
  139.     }
  140. }
  141. // Send camera frame to server
  142. void sendCameraFrame() {
  143.     if (xSemaphoreTake(frameMutex, pdMS_TO_TICKS(10)) == pdTRUE) {
  144.         if (globalFrame != NULL && frameReady) {
  145.             // Send next_frame status
  146.             DynamicJsonDocument statusDoc(256);
  147.             statusDoc["status"] = "next_frame";
  148.             String statusJson;
  149.             serializeJson(statusDoc, statusJson);
  150.             webSocket.sendTXT(statusJson);
  151.             
  152.             // Send parameters for diffusion
  153.             DynamicJsonDocument paramsDoc(512);
  154.             paramsDoc["strength"] = 0.8;
  155.             paramsDoc["guidance_scale"] = 7.5;
  156.             paramsDoc["prompt"] = "Portrait of The Joker halloween costume, face painting, with , glare pose, detailed, intricate, full of colour, cinematic lighting, trending on artstation, 8k, hyperrealistic, focused, extreme details, unreal engine 5 cinematic, masterpiece";
  157.             String paramsJson;
  158.             serializeJson(paramsDoc, paramsJson);
  159.             webSocket.sendTXT(paramsJson);
  160.             
  161.             // Convert RGB565 to JPEG if needed
  162.             uint8_t *jpeg_buf = NULL;
  163.             size_t jpeg_len = 0;
  164.             bool conversion_success = false;
  165.             
  166.             if (globalFrame->format == PIXFORMAT_RGB565) {
  167.                 // Convert RGB565 to JPEG
  168.                 conversion_success = fmt2jpg(globalFrame->buf, globalFrame->len, globalFrame->width, globalFrame->height,
  169.                                            PIXFORMAT_RGB565, 80, &jpeg_buf, &jpeg_len);
  170.                 if (conversion_success) {
  171.                     webSocket.sendBIN(jpeg_buf, jpeg_len);
  172.                     free(jpeg_buf);  // Free the allocated JPEG buffer
  173.                 }
  174.             } else {
  175.                 // Send raw data if already JPEG
  176.                 webSocket.sendBIN(globalFrame->buf, globalFrame->len);
  177.                 conversion_success = true;
  178.             }
  179.             
  180.             if (conversion_success) {
  181.                 Serial.printf("Frame sent successfully (%d bytes)\n", jpeg_len > 0 ? jpeg_len : globalFrame->len);
  182.             } else {
  183.                 Serial.println("ERROR: Failed to convert frame to JPEG");
  184.             }
  185.         }
  186.         
  187.         xSemaphoreGive(frameMutex);
  188.     }
  189. }
  190. // Streaming task to receive diffused images
  191. void streamingTask(void* parameter) {
  192.     while (true) {
  193.         if (WiFi.status() == WL_CONNECTED && serverConnected) {
  194.             receiveDiffusedImage();
  195.         }
  196.         
  197.         vTaskDelay(pdMS_TO_TICKS(200));
  198.     }
  199. }
  200. // Receive diffused image from server
  201. void receiveDiffusedImage() {
  202.     String streamUrl = "http://" + String(SERVER_HOST) + ":" + String(SERVER_PORT) + "/api/stream/" + USER_ID;
  203.    
  204.     httpClient.begin(streamUrl);
  205.     httpClient.setTimeout(2000);
  206.    
  207.     int httpCode = httpClient.GET();
  208.    
  209.     if (httpCode == HTTP_CODE_OK) {
  210.         WiFiClient* stream = httpClient.getStreamPtr();
  211.         String boundary = "--frame";
  212.         String line;
  213.         
  214.         while (httpClient.connected()) {
  215.             if (stream->available()) {
  216.                 line = stream->readStringUntil('\n');
  217.                
  218.                 if (line.indexOf(boundary) >= 0) {
  219.                     // Skip headers
  220.                     while (stream->available()) {
  221.                         line = stream->readStringUntil('\n');
  222.                         if (line.length() <= 2) break;
  223.                     }
  224.                     
  225.                     // Read diffused image data
  226.                     if (stream->available()) {
  227.                         processDiffusedImage(stream);
  228.                     }
  229.                 }
  230.             }
  231.             vTaskDelay(pdMS_TO_TICKS(1));
  232.         }
  233.     }
  234.    
  235.     httpClient.end();
  236. }
  237. // Process diffused image
  238. void processDiffusedImage(WiFiClient* stream) {
  239.     static uint8_t imageBuffer[50000];
  240.     size_t bytesRead = 0;
  241.    
  242.     while (stream->available() && bytesRead < sizeof(imageBuffer)) {
  243.         int byte = stream->read();
  244.         if (byte == -1) break;
  245.         imageBuffer[bytesRead++] = byte;
  246.     }
  247.    
  248.     if (bytesRead > 0) {
  249.         displayDiffusedImage(imageBuffer, bytesRead);
  250.     }
  251. }
  252. // Display diffused image on screen
  253. void displayDiffusedImage(uint8_t* imageData, size_t dataSize) {
  254.     xSemaphoreTake(xLvglMutex, portMAX_DELAY);
  255.    
  256.     static lv_img_dsc_t diffused_img;
  257.     diffused_img.header.cf = LV_IMG_CF_TRUE_COLOR;
  258.     diffused_img.header.always_zero = 0;
  259.     diffused_img.header.w = 240;
  260.     diffused_img.header.h = 320;
  261.     diffused_img.data_size = dataSize;
  262.     diffused_img.data = imageData;
  263.    
  264.     lv_img_set_src(diffusedImageObject, &diffused_img);
  265.    
  266.     xSemaphoreGive(xLvglMutex);
  267. }
  268. // WiFi connection function
  269. void connectToWiFi() {
  270.     WiFi.begin(ssid, password);
  271.     Serial.print("Connecting to WiFi");
  272.    
  273.     while (WiFi.status() != WL_CONNECTED) {
  274.         delay(500);
  275.         Serial.print(".");
  276.     }
  277.    
  278.     Serial.println();
  279.     Serial.print("Connected! IP: ");
  280.     Serial.println(WiFi.localIP());
  281. }
  282. // Camera initialization function
  283. void initCamera() {
  284.     // Create frame mutex
  285.     frameMutex = xSemaphoreCreateMutex();
  286.    
  287.     // Initialize camera using UNIHIKER K10 system with RGB565 format
  288.     if (!xQueueCamera) {
  289.         xQueueCamera = xQueueCreate(5, sizeof(camera_fb_t *)); // Larger queue to prevent overflow
  290.         register_camera(PIXFORMAT_RGB565, FRAMESIZE_QVGA, 2, xQueueCamera);
  291.         Serial.println("Camera initialized successfully");
  292.     }
  293. }
  294. void setup() {
  295.     Serial.begin(115200);
  296.     // Initialize board and screen
  297.     board.begin();
  298.     board.initScreen();
  299.    
  300.     // Black background
  301.     lv_obj_set_style_bg_color(lv_scr_act(), lv_color_black(), LV_PART_MAIN);
  302.     // Create image display object
  303.     diffusedImageObject = lv_img_create(lv_scr_act());
  304.     lv_obj_set_pos(diffusedImageObject, 0, 0);
  305.     lv_obj_set_size(diffusedImageObject, 240, 320);
  306.    
  307.     // Initialize camera
  308.     initCamera();
  309.     // Connect to WiFi
  310.     connectToWiFi();
  311.    
  312.     // Wait a bit for WiFi to stabilize
  313.     delay(2000);
  314.    
  315.     // Test server connectivity first
  316.     Serial.printf("Testing server connectivity to %s:%d\n", SERVER_HOST, SERVER_PORT);
  317.     HTTPClient testClient;
  318.     testClient.begin("http://" + String(SERVER_HOST) + ":" + String(SERVER_PORT) + "/api/queue");
  319.     int httpCode = testClient.GET();
  320.     if (httpCode > 0) {
  321.         String response = testClient.getString();
  322.         Serial.printf("Server test response: %d - %s\n", httpCode, response.c_str());
  323.     } else {
  324.         Serial.printf("Server test failed: %d\n", httpCode);
  325.     }
  326.     testClient.end();
  327.    
  328.     Serial.printf("Connecting to WebSocket: ws://%s:%d/api/ws/%s\n", SERVER_HOST, SERVER_PORT, USER_ID.c_str());
  329.    
  330.     // Initialize WebSocket connection
  331.     webSocket.begin(SERVER_HOST, SERVER_PORT, "/api/ws/" + USER_ID);
  332.     webSocket.onEvent(webSocketEvent);
  333.     webSocket.setReconnectInterval(5000);
  334.     webSocket.enableHeartbeat(15000, 3000, 2);  // Enable heartbeat to keep connection alive
  335.    
  336.     // Start tasks
  337.     xTaskCreatePinnedToCore(cameraTask, "Camera", 6144, NULL, 4, &cameraTaskHandle, 1);      // Even higher priority camera task
  338.     xTaskCreatePinnedToCore(websocketTask, "WebSocket", 8192, NULL, 2, &websocketTaskHandle, 0);
  339.     xTaskCreatePinnedToCore(streamingTask, "Streaming", 10240, NULL, 1, &streamingTaskHandle, 1); // Lower priority for streaming
  340.    
  341.     Serial.println("Setup complete!");
  342. }
  343. void loop() {
  344.     // Handle LVGL
  345.     xSemaphoreTake(xLvglMutex, portMAX_DELAY);
  346.     lv_task_handler();
  347.     xSemaphoreGive(xLvglMutex);
  348.    
  349.     vTaskDelay(pdMS_TO_TICKS(5));
  350. }
复制代码


回复

使用道具 举报

驴友花雕  中级技神
 楼主|

发表于 昨天 16:59

【Arduino 动手做】使用K10构建人工智能驱动的宝丽来相机

【Arduino 动手做】使用 Unihiker K10 构建人工智能驱动的宝丽来相机
项目链接:https://www.hackster.io/phamtuan ... unihiker-k10-1f1585
项目作者:Pham Binh

项目视频 :https://www.youtube.com/watch?v=p7J4b8OQXAY
项目代码:https://github.com/pham-tuan-binh/memento
3D 文件:https://github.com/pham-tuan-binh/memento/tree/main/models

Unihiker K10 文档:https://www.unihiker.com/wiki/K10/
Google Gemini API 文档:https://ai.google.dev/
ImageRouter API 文档:https://imagerouter.io/
FastAPI 文档:https://fastapi.tiangolo.com/
项目仓库:https://github.com/pham-tuan-binh/memento
快乐的建造和快乐的快照!
https://github.com/pham-tuan-binh/memento

【Arduino 动手做】使用K10构建人工智能驱动的宝丽来相机图1

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

为本项目制作心愿单
购买心愿单
心愿单 编辑
[[wsData.name]]

硬件清单

  • [[d.name]]
btnicon
我也要做!
点击进入购买页面
上海智位机器人股份有限公司 沪ICP备09038501号-4 备案 沪公网安备31011502402448

© 2013-2025 Comsenz Inc. Powered by Discuz! X3.4 Licensed

mail