|
|
<!--
|
|
|
CO_OP_TRANSLATOR_METADATA:
|
|
|
{
|
|
|
"original_hash": "160be8c0f558687f6686dca64f10f739",
|
|
|
"translation_date": "2025-08-26T21:55:17+00:00",
|
|
|
"source_file": "4-manufacturing/lessons/2-check-fruit-from-device/wio-terminal-camera.md",
|
|
|
"language_code": "mo"
|
|
|
}
|
|
|
-->
|
|
|
# 捕捉影像 - Wio Terminal
|
|
|
|
|
|
在這部分課程中,你將為 Wio Terminal 添加一個相機,並從中捕捉影像。
|
|
|
|
|
|
## 硬體
|
|
|
|
|
|
Wio Terminal 需要一個相機。
|
|
|
|
|
|
你將使用的相機是 [ArduCam Mini 2MP Plus](https://www.arducam.com/product/arducam-2mp-spi-camera-b0067-arduino/)。這是一個基於 OV2640 影像感測器的 2 百萬像素相機。它通過 SPI 介面進行影像捕捉,並使用 I2C 配置感測器。
|
|
|
|
|
|
## 連接相機
|
|
|
|
|
|
ArduCam 沒有 Grove 插槽,而是通過 Wio Terminal 上的 GPIO 引腳連接到 SPI 和 I2C 匯流排。
|
|
|
|
|
|
### 任務 - 連接相機
|
|
|
|
|
|
連接相機。
|
|
|
|
|
|

|
|
|
|
|
|
1. ArduCam 底部的引腳需要連接到 Wio Terminal 的 GPIO 引腳。為了更容易找到正確的引腳,將隨 Wio Terminal 附帶的 GPIO 引腳貼紙貼在引腳周圍:
|
|
|
|
|
|

|
|
|
|
|
|
1. 使用跳線,進行以下連接:
|
|
|
|
|
|
| ArduCAM 引腳 | Wio Terminal 引腳 | 描述 |
|
|
|
| ------------ | ----------------- | -------------------------------------- |
|
|
|
| CS | 24 (SPI_CS) | SPI 芯片選擇 |
|
|
|
| MOSI | 19 (SPI_MOSI) | SPI 控制器輸出,外設輸入 |
|
|
|
| MISO | 21 (SPI_MISO) | SPI 控制器輸入,外設輸出 |
|
|
|
| SCK | 23 (SPI_SCLK) | SPI 串行時鐘 |
|
|
|
| GND | 6 (GND) | 接地 - 0V |
|
|
|
| VCC | 4 (5V) | 5V 電源供應 |
|
|
|
| SDA | 3 (I2C1_SDA) | I2C 串行數據 |
|
|
|
| SCL | 5 (I2C1_SCL) | I2C 串行時鐘 |
|
|
|
|
|
|

|
|
|
|
|
|
GND 和 VCC 連接為 ArduCam 提供 5V 電源。它以 5V 運行,不同於以 3V 運行的 Grove 感測器。這個電源直接來自為設備供電的 USB-C 連接。
|
|
|
|
|
|
> 💁 對於 SPI 連接,ArduCam 上的引腳標籤和 Wio Terminal 中代碼使用的引腳名稱仍然使用舊的命名約定。本課程中的說明將使用新的命名約定,除非代碼中使用了引腳名稱。
|
|
|
|
|
|
1. 現在可以將 Wio Terminal 連接到你的電腦。
|
|
|
|
|
|
## 編程設備以連接相機
|
|
|
|
|
|
現在可以為 Wio Terminal 編程以使用連接的 ArduCAM 相機。
|
|
|
|
|
|
### 任務 - 編程設備以連接相機
|
|
|
|
|
|
1. 使用 PlatformIO 創建一個全新的 Wio Terminal 專案。將此專案命名為 `fruit-quality-detector`。在 `setup` 函數中添加代碼以配置串行埠。
|
|
|
|
|
|
1. 添加代碼以連接 WiFi,將你的 WiFi 憑據放在名為 `config.h` 的檔案中。不要忘記將所需的庫添加到 `platformio.ini` 檔案中。
|
|
|
|
|
|
1. ArduCam 庫無法作為 Arduino 庫從 `platformio.ini` 檔案中安裝。相反,需要從其 GitHub 頁面安裝源代碼。你可以通過以下方式獲取:
|
|
|
|
|
|
* 從 [https://github.com/ArduCAM/Arduino.git](https://github.com/ArduCAM/Arduino.git) 克隆倉庫
|
|
|
* 前往 GitHub 上的倉庫 [github.com/ArduCAM/Arduino](https://github.com/ArduCAM/Arduino) 並從 **Code** 按鈕下載代碼為 zip 檔案
|
|
|
|
|
|
1. 你只需要此代碼中的 `ArduCAM` 資料夾。將整個資料夾複製到專案中的 `lib` 資料夾中。
|
|
|
|
|
|
> ⚠️ 必須複製整個資料夾,因此代碼位於 `lib/ArduCam` 中。不要僅將 `ArduCam` 資料夾的內容複製到 `lib` 資料夾中,而是將整個資料夾複製過去。
|
|
|
|
|
|
1. ArduCam 庫代碼適用於多種類型的相機。你想要使用的相機類型是通過編譯器標誌配置的——這使得構建的庫盡可能小,通過移除你未使用的相機的代碼來實現。要將庫配置為 OV2640 相機,請在 `platformio.ini` 檔案末尾添加以下內容:
|
|
|
|
|
|
```ini
|
|
|
build_flags =
|
|
|
-DARDUCAM_SHIELD_V2
|
|
|
-DOV2640_CAM
|
|
|
```
|
|
|
|
|
|
這設置了兩個編譯器標誌:
|
|
|
|
|
|
* `ARDUCAM_SHIELD_V2` 用於告訴庫相機位於 Arduino 板上,稱為 shield。
|
|
|
* `OV2640_CAM` 用於告訴庫僅包含 OV2640 相機的代碼。
|
|
|
|
|
|
1. 在 `src` 資料夾中添加一個名為 `camera.h` 的標頭檔案。這將包含與相機通信的代碼。在此檔案中添加以下代碼:
|
|
|
|
|
|
```cpp
|
|
|
#pragma once
|
|
|
|
|
|
#include <ArduCAM.h>
|
|
|
#include <Wire.h>
|
|
|
|
|
|
class Camera
|
|
|
{
|
|
|
public:
|
|
|
Camera(int format, int image_size) : _arducam(OV2640, PIN_SPI_SS)
|
|
|
{
|
|
|
_format = format;
|
|
|
_image_size = image_size;
|
|
|
}
|
|
|
|
|
|
bool init()
|
|
|
{
|
|
|
// Reset the CPLD
|
|
|
_arducam.write_reg(0x07, 0x80);
|
|
|
delay(100);
|
|
|
|
|
|
_arducam.write_reg(0x07, 0x00);
|
|
|
delay(100);
|
|
|
|
|
|
// Check if the ArduCAM SPI bus is OK
|
|
|
_arducam.write_reg(ARDUCHIP_TEST1, 0x55);
|
|
|
if (_arducam.read_reg(ARDUCHIP_TEST1) != 0x55)
|
|
|
{
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// Change MCU mode
|
|
|
_arducam.set_mode(MCU2LCD_MODE);
|
|
|
|
|
|
uint8_t vid, pid;
|
|
|
|
|
|
// Check if the camera module type is OV2640
|
|
|
_arducam.wrSensorReg8_8(0xff, 0x01);
|
|
|
_arducam.rdSensorReg8_8(OV2640_CHIPID_HIGH, &vid);
|
|
|
_arducam.rdSensorReg8_8(OV2640_CHIPID_LOW, &pid);
|
|
|
if ((vid != 0x26) && ((pid != 0x41) || (pid != 0x42)))
|
|
|
{
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
_arducam.set_format(_format);
|
|
|
_arducam.InitCAM();
|
|
|
_arducam.OV2640_set_JPEG_size(_image_size);
|
|
|
_arducam.OV2640_set_Light_Mode(Auto);
|
|
|
_arducam.OV2640_set_Special_effects(Normal);
|
|
|
delay(1000);
|
|
|
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
void startCapture()
|
|
|
{
|
|
|
_arducam.flush_fifo();
|
|
|
_arducam.clear_fifo_flag();
|
|
|
_arducam.start_capture();
|
|
|
}
|
|
|
|
|
|
bool captureReady()
|
|
|
{
|
|
|
return _arducam.get_bit(ARDUCHIP_TRIG, CAP_DONE_MASK);
|
|
|
}
|
|
|
|
|
|
bool readImageToBuffer(byte **buffer, uint32_t &buffer_length)
|
|
|
{
|
|
|
if (!captureReady()) return false;
|
|
|
|
|
|
// Get the image file length
|
|
|
uint32_t length = _arducam.read_fifo_length();
|
|
|
buffer_length = length;
|
|
|
|
|
|
if (length >= MAX_FIFO_SIZE)
|
|
|
{
|
|
|
return false;
|
|
|
}
|
|
|
if (length == 0)
|
|
|
{
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// create the buffer
|
|
|
byte *buf = new byte[length];
|
|
|
|
|
|
uint8_t temp = 0, temp_last = 0;
|
|
|
int i = 0;
|
|
|
uint32_t buffer_pos = 0;
|
|
|
bool is_header = false;
|
|
|
|
|
|
_arducam.CS_LOW();
|
|
|
_arducam.set_fifo_burst();
|
|
|
|
|
|
while (length--)
|
|
|
{
|
|
|
temp_last = temp;
|
|
|
temp = SPI.transfer(0x00);
|
|
|
//Read JPEG data from FIFO
|
|
|
if ((temp == 0xD9) && (temp_last == 0xFF)) //If find the end ,break while,
|
|
|
{
|
|
|
buf[buffer_pos] = temp;
|
|
|
|
|
|
buffer_pos++;
|
|
|
i++;
|
|
|
|
|
|
_arducam.CS_HIGH();
|
|
|
}
|
|
|
if (is_header == true)
|
|
|
{
|
|
|
//Write image data to buffer if not full
|
|
|
if (i < 256)
|
|
|
{
|
|
|
buf[buffer_pos] = temp;
|
|
|
buffer_pos++;
|
|
|
i++;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
_arducam.CS_HIGH();
|
|
|
|
|
|
i = 0;
|
|
|
buf[buffer_pos] = temp;
|
|
|
|
|
|
buffer_pos++;
|
|
|
i++;
|
|
|
|
|
|
_arducam.CS_LOW();
|
|
|
_arducam.set_fifo_burst();
|
|
|
}
|
|
|
}
|
|
|
else if ((temp == 0xD8) & (temp_last == 0xFF))
|
|
|
{
|
|
|
is_header = true;
|
|
|
|
|
|
buf[buffer_pos] = temp_last;
|
|
|
buffer_pos++;
|
|
|
i++;
|
|
|
|
|
|
buf[buffer_pos] = temp;
|
|
|
buffer_pos++;
|
|
|
i++;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
_arducam.clear_fifo_flag();
|
|
|
|
|
|
_arducam.set_format(_format);
|
|
|
_arducam.InitCAM();
|
|
|
_arducam.OV2640_set_JPEG_size(_image_size);
|
|
|
|
|
|
// return the buffer
|
|
|
*buffer = buf;
|
|
|
}
|
|
|
|
|
|
private:
|
|
|
ArduCAM _arducam;
|
|
|
int _format;
|
|
|
int _image_size;
|
|
|
};
|
|
|
```
|
|
|
|
|
|
這是使用 ArduCam 庫配置相機的低層代碼,並在需要時通過 SPI 匯流排提取影像。這段代碼非常特定於 ArduCam,因此你目前不需要擔心它的工作原理。
|
|
|
|
|
|
1. 在 `main.cpp` 中,在其他 `include` 語句下方添加以下代碼以包含此新檔案並創建相機類的實例:
|
|
|
|
|
|
```cpp
|
|
|
#include "camera.h"
|
|
|
|
|
|
Camera camera = Camera(JPEG, OV2640_640x480);
|
|
|
```
|
|
|
|
|
|
這創建了一個 `Camera`,將影像保存為 640x480 分辨率的 JPEG。雖然支持更高的分辨率(最高 3280x2464),但影像分類器處理的影像尺寸要小得多(227x227),因此無需捕捉和傳輸更大的影像。
|
|
|
|
|
|
1. 在此下方添加以下代碼以定義一個設置相機的函數:
|
|
|
|
|
|
```cpp
|
|
|
void setupCamera()
|
|
|
{
|
|
|
pinMode(PIN_SPI_SS, OUTPUT);
|
|
|
digitalWrite(PIN_SPI_SS, HIGH);
|
|
|
|
|
|
Wire.begin();
|
|
|
SPI.begin();
|
|
|
|
|
|
if (!camera.init())
|
|
|
{
|
|
|
Serial.println("Error setting up the camera!");
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
此 `setupCamera` 函數首先將 SPI 芯片選擇引腳(`PIN_SPI_SS`)配置為高電平,使 Wio Terminal 成為 SPI 控制器。然後啟動 I2C 和 SPI 匯流排。最後,它初始化相機類,配置相機感測器設置並確保所有連接正確。
|
|
|
|
|
|
1. 在 `setup` 函數的末尾調用此函數:
|
|
|
|
|
|
```cpp
|
|
|
setupCamera();
|
|
|
```
|
|
|
|
|
|
1. 構建並上傳此代碼,並檢查串行監視器的輸出。如果你看到 `Error setting up the camera!`,請檢查接線,確保所有電纜正確連接 ArduCam 的引腳和 Wio Terminal 的 GPIO 引腳,並且所有跳線都正確插入。
|
|
|
|
|
|
## 捕捉影像
|
|
|
|
|
|
現在可以為 Wio Terminal 編程,使其在按下按鈕時捕捉影像。
|
|
|
|
|
|
### 任務 - 捕捉影像
|
|
|
|
|
|
1. 微控制器會不斷運行你的代碼,因此如果不響應感測器,觸發拍照並不容易。Wio Terminal 有按鈕,因此可以設置相機由其中一個按鈕觸發。在 `setup` 函數的末尾添加以下代碼,以配置 C 按鈕(頂部的三個按鈕之一,最靠近電源開關的那個)。
|
|
|
|
|
|

|
|
|
|
|
|
```cpp
|
|
|
pinMode(WIO_KEY_C, INPUT_PULLUP);
|
|
|
```
|
|
|
|
|
|
`INPUT_PULLUP` 模式本質上反轉了一個輸入。例如,通常按鈕在未按下時會發送低信號,按下時發送高信號。設置為 `INPUT_PULLUP` 時,未按下時發送高信號,按下時發送低信號。
|
|
|
|
|
|
1. 在 `loop` 函數之前添加一個空函數以響應按鈕按下:
|
|
|
|
|
|
```cpp
|
|
|
void buttonPressed()
|
|
|
{
|
|
|
|
|
|
}
|
|
|
```
|
|
|
|
|
|
1. 在按鈕被按下時,在 `loop` 方法中調用此函數:
|
|
|
|
|
|
```cpp
|
|
|
void loop()
|
|
|
{
|
|
|
if (digitalRead(WIO_KEY_C) == LOW)
|
|
|
{
|
|
|
buttonPressed();
|
|
|
delay(2000);
|
|
|
}
|
|
|
|
|
|
delay(200);
|
|
|
}
|
|
|
```
|
|
|
|
|
|
此代碼檢查按鈕是否被按下。如果按下,則調用 `buttonPressed` 函數,並且循環延遲 2 秒。這是為了給按鈕釋放留出時間,以免長按被註冊為兩次按下。
|
|
|
|
|
|
> 💁 Wio Terminal 上的按鈕設置為 `INPUT_PULLUP`,因此未按下時發送高信號,按下時發送低信號。
|
|
|
|
|
|
1. 在 `buttonPressed` 函數中添加以下代碼:
|
|
|
|
|
|
```cpp
|
|
|
camera.startCapture();
|
|
|
|
|
|
while (!camera.captureReady())
|
|
|
delay(100);
|
|
|
|
|
|
Serial.println("Image captured");
|
|
|
|
|
|
byte *buffer;
|
|
|
uint32_t length;
|
|
|
|
|
|
if (camera.readImageToBuffer(&buffer, length))
|
|
|
{
|
|
|
Serial.print("Image read to buffer with length ");
|
|
|
Serial.println(length);
|
|
|
|
|
|
delete(buffer);
|
|
|
}
|
|
|
```
|
|
|
|
|
|
此代碼通過調用 `startCapture` 開始相機捕捉。相機硬體不會在你請求時返回數據,而是發送一個指令開始捕捉,相機將在背景中捕捉影像,將其轉換為 JPEG,並將其存儲在相機本地緩衝區中。然後,`captureReady` 調用檢查影像捕捉是否完成。
|
|
|
|
|
|
一旦捕捉完成,影像數據將通過 `readImageToBuffer` 調用從相機的緩衝區複製到本地緩衝區(字節數組)。然後將緩衝區的長度發送到串行監視器。
|
|
|
|
|
|
1. 構建並上傳此代碼,並檢查串行監視器的輸出。每次按下 C 按鈕時,將捕捉一張影像,並在串行監視器上看到影像大小。
|
|
|
|
|
|
```output
|
|
|
Connecting to WiFi..
|
|
|
Connected!
|
|
|
Image captured
|
|
|
Image read to buffer with length 9224
|
|
|
Image captured
|
|
|
Image read to buffer with length 11272
|
|
|
```
|
|
|
|
|
|
不同的影像會有不同的大小。它們被壓縮為 JPEG,JPEG 文件的大小取決於影像內容。
|
|
|
|
|
|
> 💁 你可以在 [code-camera/wio-terminal](../../../../../4-manufacturing/lessons/2-check-fruit-from-device/code-camera/wio-terminal) 資料夾中找到此代碼。
|
|
|
|
|
|
😀 你已成功使用 Wio Terminal 捕捉影像。
|
|
|
|
|
|
## 可選 - 使用 SD 卡驗證相機影像
|
|
|
|
|
|
查看相機捕捉的影像最簡單的方法是將它們寫入 Wio Terminal 中的 SD 卡,然後在電腦上查看。如果你有一張備用的 microSD 卡和電腦上的 microSD 卡插槽或適配器,可以完成此步驟。
|
|
|
|
|
|
Wio Terminal 僅支持最大 16GB 的 microSD 卡。如果你有更大的 SD 卡,則無法使用。
|
|
|
|
|
|
### 任務 - 使用 SD 卡驗證相機影像
|
|
|
|
|
|
1. 使用電腦上的相關應用程式(macOS 的磁碟工具、Windows 的檔案總管或 Linux 的命令行工具)將 microSD 卡格式化為 FAT32 或 exFAT。
|
|
|
|
|
|
1. 將 microSD 卡插入電源開關下方的插槽。確保完全插入直到卡扣住並保持到位,你可能需要使用指甲或細小工具推入。
|
|
|
|
|
|
1. 在 `main.cpp` 檔案頂部添加以下 include 語句:
|
|
|
|
|
|
```cpp
|
|
|
#include "SD/Seeed_SD.h"
|
|
|
#include <Seeed_FS.h>
|
|
|
```
|
|
|
|
|
|
1. 在 `setup` 函數之前添加以下函數:
|
|
|
|
|
|
```cpp
|
|
|
void setupSDCard()
|
|
|
{
|
|
|
while (!SD.begin(SDCARD_SS_PIN, SDCARD_SPI))
|
|
|
{
|
|
|
Serial.println("SD Card Error");
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
這使用 SPI 匯流排配置 SD 卡。
|
|
|
|
|
|
1. 在 `setup` 函數中調用此函數:
|
|
|
|
|
|
```cpp
|
|
|
setupSDCard();
|
|
|
```
|
|
|
|
|
|
1. 在 `buttonPressed` 函數上方添加以下代碼:
|
|
|
|
|
|
```cpp
|
|
|
int fileNum = 1;
|
|
|
|
|
|
void saveToSDCard(byte *buffer, uint32_t length)
|
|
|
{
|
|
|
char buff[16];
|
|
|
sprintf(buff, "%d.jpg", fileNum);
|
|
|
fileNum++;
|
|
|
|
|
|
File outFile = SD.open(buff, FILE_WRITE );
|
|
|
outFile.write(buffer, length);
|
|
|
outFile.close();
|
|
|
|
|
|
Serial.print("Image written to file ");
|
|
|
Serial.println(buff);
|
|
|
}
|
|
|
```
|
|
|
|
|
|
這定義了一個用於文件計數的全域變數。這用於影像文件名,因此可以捕捉多個影像並使用遞增的文件名 - `1.jpg`、`2.jpg` 等。
|
|
|
|
|
|
然後定義了 `saveToSDCard` 函數,該函數接收一個字節數據緩衝區和緩衝區的長度。使用文件計數創建一個文件名,並遞增文件計數以準備下一個文件。然後將緩衝區中的二進位數據寫入文件。
|
|
|
|
|
|
1. 在 `buttonPressed` 函數中調用 `saveToSDCard` 函數。此調用應該在刪除緩衝區 **之前**:
|
|
|
|
|
|
```cpp
|
|
|
Serial.print("Image read to buffer with length ");
|
|
|
Serial.println(length);
|
|
|
|
|
|
saveToSDCard(buffer, length);
|
|
|
|
|
|
delete(buffer);
|
|
|
```
|
|
|
|
|
|
1. 構建並上傳此代碼,並檢查串行監視器的輸出。每次按下 C 按鈕時,將捕捉一張影像並保存到 SD 卡。
|
|
|
|
|
|
```output
|
|
|
Connecting to WiFi..
|
|
|
Connected!
|
|
|
Image captured
|
|
|
Image read to buffer with length 16392
|
|
|
Image written to file 1.jpg
|
|
|
Image captured
|
|
|
Image read to buffer with length 14344
|
|
|
Image written to file 2.jpg
|
|
|
```
|
|
|
|
|
|
1. 關閉 microSD 卡電源,稍微推入並釋放以彈出,然後取出。你可能需要使用細小工具完成此操作。將 microSD 卡插入電腦以查看影像。
|
|
|
|
|
|

|
|
|
💁 相機的白平衡可能需要幾張圖片來進行自我調整。您會根據拍攝的圖片顏色注意到這一點,前幾張可能顏色看起來不太正確。您可以通過修改程式碼,在 `setup` 函數中拍攝幾張被忽略的圖片來解決這個問題。
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
**免責聲明**:
|
|
|
本文件已使用 AI 翻譯服務 [Co-op Translator](https://github.com/Azure/co-op-translator) 進行翻譯。雖然我們致力於提供準確的翻譯,但請注意,自動翻譯可能包含錯誤或不準確之處。原始文件的母語版本應被視為權威來源。對於關鍵資訊,建議尋求專業人工翻譯。我們對因使用此翻譯而引起的任何誤解或錯誤解釋不承擔責任。 |