網頁

2017年12月18日 星期一

OpenQCam樹莓派開源迷你相機之ePTZ之應用實例

本專案是利用上次已完成之OpenQCam樹莓派迷你開源相機(https://omnixri.blogspot.tw/2017/12/openqcam.html)(如圖一右圖所示)所衍生的應用實例【ePTZ攝影機】,一樣也會使用到OpenCV,希望這個專案能提供大家多一點使用上的想法。完整專案已上傳GITHUB請參考https://github.com/OmniXRI/OpenQCam_ePTZ

傳統監視攝影機解析度通常不高,大約30萬畫像素(VGA)至二百萬畫像素(FHD)左右,若要監看較大範圍區域或特定一個小區域時常須搭配致動機構來使攝影機可以左右移動(PAN)、上下移動(Tilt)及調整光學倍率縮放(Zoom),一般稱為PTZ攝影機(如圖1左圖所示)。這類攝影機由於須要搭配致動機構(馬達、齒輪等)及控制迴路,所以通常價格也會高出一般固定式攝影機許多。隨著攝影機取像晶片的快速進步,通常解析度已高出顯示器許多。以常見FHD(2K)的顯示幕來說不過約二百萬畫像素(1920*1080),而4K顯示器雖然已可顯示八百萬畫像素,但高端攝影機早已高達五千萬甚至一億畫像素,而高過顯示器的解析度明顯就變成有些浪費。因此有許多廠商提出只需一隻固定式定焦(倍率不變)的高解析度攝影機,不須任何額外致動機構及元作,只要在高解析度影像中截剪(Crop)出顯示器所需解析度內容來顯示,即可取代部份PTZ攝影機使用場合,這樣的技術就稱為電子式或數位式PTZ,簡稱ePTZ。



圖1 左圖為傳統PTZ攝影機,右圖為利用OpenQCam所完成的ePTZ攝影機。

在上一專案OpenQCam中,我們使用了一個五百萬畫像素(2592*1944)的攝像頭模組和一片QVGA(320*240)的LCD顯示屏,剛好符合上述情境,很適合應用在ePTZ上。如圖2左圖所示,取像時為2592*1944畫像素,我們可選擇性的截切一小塊再丟給LCD顯示,如圖2右圖所示,當截切內容大於LCD顯示解析度時,則先把影像縮小至320*240畫像素再顯示即可。


圖2 左圖為攝影機原始解析度及局部截切後解析度,右圖為LCD顯示解析度。

在工作前我們要先規畫巡行方式及速度,模仿機械式轉動的效果。以下列公式可求得巡行一趟的時間T。

T = ((CW – EW) / SW) * ((CH-EH) / SH) * ST  (公式一)

CW: 取像水平解析度,     CH: 取像垂直解析度。
EW: 影像截切水平解析度, EH: 影像截切垂直解析度。
SW: 水平移動步階,       SH: 垂直移動步階。
ST: 為步階移動間隔時間,可自由指定,通常希望以1個影格(Frame)時間1/30秒為間隔,但實際上會因Pi Zero處理速度來不及可能須降至 1/15~1/5秒。

為了使巡行速度能滿足週期內完成一輪且畫面顯示平順,因此影像水平截切尺寸(EW, EH)、移動距離(SH, SW)及步階移動間隔時間等參數選定時須仔細考慮,才不會造成畫面顯示時有嚴重跳動感。巡行時會由左上角開始,接著由左而右,由上而下,最後再回左上角,持續巡行,其流程如圖2左圖所示。



圖3 左圖為ePTZ巡行速度計算參數示意圖,右圖為巡行工作流程圖

我們簡單設計幾個巡行尺度及速度如下表1所示。第一種尺度為全視野顯示,即不巡行,暫停一秒。第二至四種為ePTZ(裁切)顯示,顯示時除顯示目前裁切及巡行內容,會左上角顯示全視野影像(120*90),方便了解目前巡行位置。一般橫向移動不宜幅度過大避免產生跳動感,而縱向移動大家比較無感,所以可以移動較大行程,節省整體巡行時間。此外大家也可依自己想要的裁切尺寸和速度來調整,但設計上儘量讓數字能整除會較方便估計巡行時間。

表1 不同巡行尺度參數


CW
CH
EW
EH
SW
SH
總移動
Full
2592
1944
2592
1944
0
1
0
1
1
QVGA
2592
1944
320
240
71
32
113
8
256
VGA
2592
1944
640
480
122
16
122
12
192
HD
2592
1944
1280
960
82
16
123
8
128


舉例來說,以表1中取像後裁切出VGA解析度影像,縮小至QVGA解析度後再顯示為例,假設處理一次步階移動時間(ST)為60ms,所需巡行一趟時間T如下所示。

T = ((CW – EW) / SW) * ((CH-EH) / SH) * ST
 = ((2592 – 640) / 122) * ((1994 – 480) / 122) * 60ms
 = 16 * 12 * 60ms = 11.520 sec

再來僅針對主程式部份進行說明,其它部份請參見前一專案OpenQCam ( https://omnixri.blogspot.tw/2017/12/openqcam.html )說明。本專案一樣使用到OpenCV作為影像處理用工具,其中應用到一個最重要的概念【感興趣區域(ROI)】,主要用途就是從一張影像中裁剪出一小塊影像或者反過來拿一小塊影像貼到指定位置。取出影像後再縮小至顯示尺寸(320*240),即可顯示於到LCD上,其主要程式如下說明。另外會將取得之全畫面縮小至120*90後貼到左上角。

Mat matCap; // 儲存原始影像
cap >> matCap; // 從攝像頭取得影像並存到matCap中

//取得原始影像matSrc中指定位置及大小(Rect(左上X座標,左上Y座標,寬度,高度))的影像到matROI
Mat matROI = matSrc(Rect(begin_x, begin_y, width, height));

// 宣告LCD顯示及原始影像縮小圖用矩陣
Mat matDisp;
Mat matCapResize;

// 將取得之ROI影像縮小至320*240畫像素並放到matDisp中
// 一般縮放採雙線性內差(CV_INTER_LINEAR)就夠用,如果不在乎影像品質將縮放速度加快一點,可採用CV_INTER_NEAREST內插法。
cv::Resize(matROI, matDisp, cv::Size(320, 240), 0, 0, CV_INTER_LINEAR);

// 將原始影像縮小至120*90畫像素後貼至顯示區左上角
Mat matDispROI = matDisp(Rect(0, 0, 120, 90));
cv::Resize(matCap, matCapResize, cv::Size(120,90), 0, 0, CV_INTER_NEAREST);
matCapResize.CopyTo(matDispROI);

以下為完整程式,請參考。

main.cpp

#include <stdio.h>
#include <iostream>
#include "ILI9341.h"
#include <opencv2/opencv.hpp>
#include <ctime>

#define MAX_W 2592
#define MAX_H 1944

using namespace std;
using namespace cv;

int main(int argc, char **argv)
{
 // 巡行參數 0:原始解析度 / 1:QVGA / 2:VGA / 3:HD
 int para_num = 0; // 巡行參數編號
 int EW[4] = {2592, 320, 640, 1280}; // 裁切影像寬度
 int EH[4] = {1944, 240, 480, 960};  // 裁切影像高度
 int SW[4] = {2592, 71,122,82};      // 水平移動距離
 int SH[4] = {1994, 113, 122, 123};  // 垂直移動距離
 int curr_x = 0; // 目前巡行起點X
 int curr_y = 0; // 目前巡行起點Y
 int curr_ew = EW[0]; // 目前裁切影像寬度
 int curr_eh = EH[0]; // 目前裁切影像高度
 int curr_sx = SW[0]; // 目前水平移動距離
 int curr_sh = SH[0]; // 目前垂直移動距離

 double t0,t1,t2;
 int result = LCD_Inital(); // LCD初始化

 switch(result){ // 顯示初始化錯誤訊息
   case 0: 
          printf("LCD inital OK.\n");
          break;
   case 1:
          printf("BCM2835 inital failed. Are you running as root??\n");
          break;
   case 2:
          printf("GPIO inital failed. Are you running as root??\n");
          break;
   case 3:
          printf("SPI inital failed. Are you running as root??\n");
          break;
   case 4:
          printf("ILI9341 inital failed. Please check PCB connection is OK.\n");
          break;
   default:
          printf("LCD inital failed.\n");
 }

 if(result != 0)
   return 0;

  VideoCapture cap(0); // 啟動攝像頭連續取像

  if (!cap.isOpened()) { // 若無法開啟則結束
    cerr << "ERROR: Unable to open the camera" << endl;
    return 0;
  }

  cap.set(CV_CAP_PROP_FRAME_WIDTH, 2592); // 設定攝像頭輸入為最大解析度
  cap.set(CV_CAP_PROP_FRAME_HEIGHT,1944);

  Mat matCap;
  Mat matCapResize;
  Mat matROI;
  Mat matDisp;
  int count = 0;

  cout << "Start grabbing !" << endl;
  LCD_SetHorizontalDisplay(); // 設定LCD為橫式顯示

  char strFps[10]; // 儲存速度字串
  t1 = (double)getTickCount(); // 取得目前時間

  while(1) {
    for(curr_y=0; curr_y<MAX_H-EH[para_num]; curr_y += SH[para_num]){
    
      for(curr_x=0; curr_x<MAX_W-EW[para_num]; curr_x += SW[para_num]){
        t0 = t1; // 儲存舊時間
        t1 = (double)getTickCount(); // 儲存目前時間

        cap >> matCap; // 將取得的影像複製到matCap
      
        if(para_num == 0){
          resize(matCap, matDisp, cv::Size(320,240), 0, 0, INTER_LINEAR); // 將影像縮至QVGA解析度到顯示區
        }
        else{
          matROI = matCap(Rect(curr_x,curr_y,curr_ew,curr_eh)); // 裁切指定位置大小影像
          resize(matROI, matDisp, cv::Size(320,240), 0, 0, INTER_LINEAR); // 將影像縮至QVGA解析度到顯示區
        }

        Mat matDispROI = matDisp(Rect(0, 0, 120, 90)); // 指定ROI區
      
        rectangle(matCap, cv::Point(curr_x, curr_y),
                  cv::Point(curr_x+EW[para_num],curr_y+EH[para_num]), Scalar(0,0,255), 20); // 在原影像上畫ROI框
        resize(matCap, matCapResize, cv::Size(120,90), 0, 0, INTER_NEAREST); // 將原始影像縮至120*90
        matCapResize.copyTo(matDispROI); // 貼至顯示區左上角

        // 計算兩次執行時間差 取倒數即為每秒幀數 顯示在LCD左上角
        // 若不需顯示則註解掉下面兩行
        sprintf(strFps,"%2.1f FPS", 1.0 / ((t1-t0) / getTickFrequency()));   
        putText(matDisp, strFps, Point(200,20), FONT_HERSHEY_DUPLEX, 0.8, Scalar(0,255,0),1);

        // 將影像逐行顯示在LCD上
        for(int i = 0; i < matDisp.rows; i++){
          char *ptrS = matDisp.ptr<char>(i);

          ILI9341_WriteLineBGR2RGB565(ptrS, matDisp.cols);
        }

        // 若無法取得影像則結束程式
        if (matCap.empty()) {
          cerr << "ERROR: Unable to grab from the camera" << endl;
          return 0;
        }

        count ++;

        if(count%2 == 0) // 工作中LED1閃爍
          bcm2835_gpio_write(PIN_GPIO_LED_R, 1); // 點亮LED1
        else
          bcm2835_gpio_write(PIN_GPIO_LED_R, 0); // 熄減LED1

        // 若SW1按下則切換到下一組巡行參數
        if(bcm2835_gpio_lev(PIN_GPIO_SW1) != 0){
          delay(10); // 去除按鍵彈跳    
          curr_x = MAX_W; // 強迫離開雙for loop
          curr_y = MAX_H; //
        }     

        // 若SW2按下則綠燈閃一下結束程式
        if(bcm2835_gpio_lev(PIN_GPIO_SW2) != 0){
          bcm2835_gpio_write(PIN_GPIO_LED_G, 1); // 點亮LED2
          delay(50);
          bcm2835_gpio_write(PIN_GPIO_LED_G, 0); // 熄減LED2
          bcm2835_gpio_write(PIN_GPIO_LED_R, 0); // 熄減LED1
          cap.release();
          BCM2835_End();
          cout << "Done!" <<endl;
          return 0;
        }

        if(curr_x >= MAX_W)
          break;

      } // end of for loop curr_x
    
      if(curr_y >= MAX_H)
        break;

    } // end of for loop curr_y

    para_num ++;

    if(para_num >= 4)
      para_num = 0;

    curr_ew = EW[para_num]; // 目前裁切影像寬度
    curr_eh = EH[para_num]; // 目前裁切影像高度
    curr_sx = SW[para_num]; // 目前水平移動距離
    curr_sh = SH[para_num]; // 目前垂直移動距離
  } // end of while
}

接著執行本專案已預建編譯指令批次檔 go.sh 進行編譯,等待約30 秒後即可完成,產生執行檔ePTZ,由於BCM2835 須要較高權限,所以執行時要加上 sudo,
完整操作如下所示。

sudo ./go.sh (編譯程式)
sudo ./ePTZ (執行程式)

執行後,會依序執行表1中四種尺度巡行參數,當按下SW1時會直接跳到下一組參數,每按一次則依序循環切換巡行參數,巡行時LED1(紅)閃爍表示工作中,當按下SW2時可結束程式。經實測在未進行程式優化下,執行速度大約有4.0~4.5 FPS,相當於移動一個步階大約200~220ms。

本專案是延續前一專案OpenQCam及做為熟悉本平台軟硬體架構很好的範例,程式的相關註解詳見各程式原始碼。受限於個人能力有限,撰文上難免產生誤解或疏漏,如有任何問題歡迎留言或來信指教!

作者:歐尼克斯實境互動工作室 https://omnixri.blogspot.tw/ (Dec. 2017)

2017年12月10日 星期日

OpenQCam樹莓派開源迷你相機

這麼多年來總是從網路上找到許多幫助我完成工作的參考資料或是程式片段,但自己卻從未貢獻過,包括連在討論區回覆都沒有,實在有點不好意思。這是我的第一個開源專案,但與其說是開源,不如說是我把網路上找到的一堆破碎可用片段重新整理好並實做出來。此次選擇用中文撰寫,主要是因為網路上許多有用的內容都是用英文寫的不易閱讀,為方便大家,才選用中文撰寫,未來若有時間或許會再加寫英文版本提供更多網友利用,若有網友願意協助翻譯成各國文字,亦非常歡迎連結推廣。

本專案主要利用樹莓派Pi Zero W, 500 萬素相機模組, 2.2 吋SPI 介面QVGA(320*240 像素) 解析度TFT LCD, OpenCV (C++版本), 完成一台迷你可愛版數位相機【OpenQCam】,有兩個按鍵及兩個LED,可按鈕拍照、儲存並同步顯示在TFTLCD 上,可做為後續開發更進階的小型嵌入式智慧型攝影機、電腦(機器)視覺、人工智慧影像分析應用的軟硬體前置環境建立,大家可依此架構再擴展成自己想要的功能。完成的作品如圖一所示。

圖1-1 開源迷你相機OpenQCam 成品圖


本開源專案內容長了些,所以將其分為六個部份說明,請點選下列連結繼續閱讀

OpenQCam樹莓派開源迷你相機#1_硬體需求
OpenQCam樹莓派開源迷你相機#2_硬體線路圖
OpenQCam樹莓派開源迷你相機#3_作業系統安裝
OpenQCam樹莓派開源迷你相機#4_工作環境建置
OpenQCam樹莓派開源迷你相機#5_系統開發
OpenQCam樹莓派開源迷你相機#6_程式源碼

所有程式碼可至GITHUB下載 https://github.com/OmniXRI/OpenQCam

參考文獻及網址

樹莓派基金會 https://www.raspberrypi.org
記憶卡備份Win32 Disk Imager https://sourceforge.net/projects/win32diskimager/
遠端操控 RealVNC https://www.realvnc.com
電腦視覺OpenCV https://opencv.org/
介面控制 BCM2835 http://www.airspayce.com/
LCD 驅動IC 資料手冊 https://cdn-shop.adafruit.com/datasheets/ILI9341.pdf

OpenQCam樹莓派開源迷你相機#6_程式源碼

完整專案可直接到GITHUB下載,或者直接將下列內容複製到工作路徑執行。

// **********                   ILI9341.h                    **********
// ********** ILI9341 LCD SPI 驅動程式標頭檔 **********

 #include <bcm2835.h>

#define PIN_LCD_RESET RPI_V2_GPIO_P1_11
#define PIN_LCD_DC    RPI_V2_GPIO_P1_13
#define PIN_LCD_LED   RPI_V2_GPIO_P1_15
//#define PIN_LCD_MOSI  PRI_V2_GPIO_P1_19
//#define PIN_LCD_MISO  PRI_V2_GPIO_P1_21
//#define PIN_LCD_SCLK  PRI_V2_GPIO_P1_23
//#define PIN_LCD_CS    PRI_V2_GPIO_P1_24

#define PIN_GPIO_SW1 RPI_V2_GPIO_P1_40
#define PIN_GPIO_SW2 RPI_V2_GPIO_P1_37
#define PIN_GPIO_LED_R RPI_V2_GPIO_P1_35
#define PIN_GPIO_LED_G RPI_V2_GPIO_P1_33

int LCD_Inital(void);
int bcm2835_init(void);
void BCM2835_End(void);
int GPIO_Inital(void);
int SPI_Inital(void);
void LCD_SetReset(uint8_t);
void LCD_SetBacklight(uint8_t);
void LCD_SetMode(uint8_t);
void LCD_SetVerticalDisplay(void);
void LCD_SetHorizontalDisplay(void);
void LCD_HW_Reset(void);
void ILI9341_WriteCommand(char);
void ILI9341_WritePara(char);
void ILI9341_WriteData(char*,uint32_t);
void ILI9341_WriteLineBGR2RGB565(char*, uint32_t);
void ILI9341_SetWindow(uint16_t,uint16_t,uint16_t,uint16_t);
int  ILI9341_Inital(void);
void ILI9341_Wakeup(void);
void ILI9341_Sleep(void);


// **********                   ILI9341.cpp      **********
// ********** ILI9341 LCD SPI 驅動程式 **********

#include "ILI9341.h"

// LCD module inital
int LCD_Inital(void)
{
 if(!bcm2835_init()){ //若BCM2835庫初始化失敗則返回錯誤碼1
   return 1;
 }

 if(!GPIO_Inital()){ //若BCM2835 GPIO初始化失敗則返回錯誤碼2
   return 2;
 }

 if(!SPI_Inital()){ //若BCM2835 SPI初始化失敗則返回錯誤碼3
   return 3;
 }

 LCD_SetBacklight(HIGH); //點亮LCD背光
 LCD_HW_Reset(); //重置LCD
 delay(50); //延時50ms

 if(!ILI9341_Inital()){
   LCD_SetBacklight(LOW); //若LCD初始化失敗則關閉背光
   return 4; // 返回錯誤碼4
 }

 delay(300); //延時300ms

 return 0;
}

// BCM2835 函式庫初始化
int BCM2835_Inital()
{
 if(!bcm2835_init()){
   return 0;
 }

 return 1;
}

// 關閉 BCM2835 庫
void BCM2835_End()
{
 LCD_SetBacklight(LOW); //關閉LCD背光
 bcm2835_spi_end(); //結束SPI
 bcm2835_close();
}

// GPIO 初始化 設定接腳輸出入模式
int GPIO_Inital(void)
{
 bcm2835_gpio_fsel(PIN_LCD_RESET, BCM2835_GPIO_FSEL_OUTP);
 bcm2835_gpio_fsel(PIN_LCD_DC, BCM2835_GPIO_FSEL_OUTP);
 bcm2835_gpio_fsel(PIN_LCD_LED, BCM2835_GPIO_FSEL_OUTP);

 bcm2835_gpio_fsel(PIN_GPIO_SW1, BCM2835_GPIO_FSEL_INPT);
 bcm2835_gpio_fsel(PIN_GPIO_SW2, BCM2835_GPIO_FSEL_INPT);
 bcm2835_gpio_fsel(PIN_GPIO_LED_R, BCM2835_GPIO_FSEL_OUTP);
 bcm2835_gpio_fsel(PIN_GPIO_LED_G, BCM2835_GPIO_FSEL_OUTP);

 return 1;
}

// SPI 初始化
int SPI_Inital(void)
{
 if(!bcm2835_spi_begin()){
   return 0;
 }

 bcm2835_spi_setBitOrder(BCM2835_SPI_BIT_ORDER_MSBFIRST);      // 最高位元先行
 bcm2835_spi_setDataMode(BCM2835_SPI_MODE0);                   // 模式0
 bcm2835_spi_setClockDivider(BCM2835_SPI_CLOCK_DIVIDER_16);    // 256MHz/DIVIDER = 16 MHz for LCD SCLK
 bcm2835_spi_chipSelect(BCM2835_SPI_CS0);                      // 使用CE0_N
 bcm2835_spi_setChipSelectPolarity(BCM2835_SPI_CS0, LOW);      // 低電位方式驅動CE0_N

 return 1;
}

// 重置LCD模組
void LCD_SetReset(uint8_t state)
{
 bcm2835_gpio_write(PIN_LCD_RESET, state);
}

// 設定LCD背光 0關閉 1開啟
void LCD_SetBacklight(uint8_t state)
{
 bcm2835_gpio_write(PIN_LCD_LED, state);
}

// 設定LCD讀寫模式
// LOW(0x0): 命令模式; HIGH(0x1): 資料模式
void LCD_SetMode(uint8_t state)
{
 bcm2835_gpio_write(PIN_LCD_DC, state);
}

// 設定LCD為垂直(240x320)顯示模式
void LCD_SetVerticalDisplay(void){
 ILI9341_WriteCommand(0x36);    // Memory Access Control
 ILI9341_WritePara(0x48);    // Row Address Order(MY), Column Address Order(MX), Top-Down refresh order, BGR Color Filter
 ILI9341_SetWindow(0, 239, 0, 319);
}

// 設定LCD為水平(320x240)顯示模式
void LCD_SetHorizontalDisplay(void){
 ILI9341_WriteCommand(0x36);    // Memory Access Control
 ILI9341_WritePara(0x28);    // Row Address Order(MY), Column Address Order(MX), Top-Down refresh order, BGR Color Filter
 ILI9341_SetWindow(0, 319, 0, 239);
}

// LCD硬體重置
void LCD_HW_Reset(void)
{
 LCD_SetReset(HIGH);
 delay(5);
 LCD_SetReset(LOW);
 delay(20);
 LCD_SetReset(HIGH);
 delay(150);
}

// 寫入 1 byte 命令到LCD模組
void ILI9341_WriteCommand(char cmd)
{
 char buf = cmd;

 LCD_SetMode(LOW); // set LCD module to command mode
 bcm2835_spi_transfern(&buf,1); // write to SPI without read back
}

// 寫入 1 byte 資料到LCD模組
void ILI9341_WritePara(char para)
{
 char buf = para;

 LCD_SetMode(HIGH); // set LCD module to data mode
 bcm2835_spi_transfern(&buf,1); // write to SPI without read back
}

// 連績寫入 n byte 資料到LCD模組
void ILI9341_WriteData(char *buf, uint32_t len)
{
 LCD_SetMode(HIGH); // set data mode
 bcm2835_spi_transfern(buf,len); // write to SPI without read back   
}


// 轉換OPENCV BGR(24bit)資料格式變為RGB565(16bit)並傳輸到LCD模組
// BGR24bit:[B7][B6][B5][B4][B3][B2][B1][B0]|[G7][G6][G5][G4][G3][G2][G1][G0]|[R7][R6][R5][R4][R3][R2][R1][R0]
// RGB16bit:[R7][R6][R5][R4][R3][G7][G6][G5]|[G4][G3][G2][B7][B6][B5][B4][B3]|
void ILI9341_WriteLineBGR2RGB565(char *buf, uint32_t len)
{
 char* pRGB565 = new char[len*2];
 int posS = 0;
 int posT = 0;

 for(int i=0; i<len; i++){
   posS += 3;
   posT += 2;
   pRGB565[ posT ] = (buf[posS+2] & 0xF8) | (buf[posS+1]>>5);
   pRGB565[posT+1] = ((buf[posS+1] & 0x1C) << 3) | (buf[posS]>>3);
 }

 LCD_SetMode(HIGH); // set data mode
 bcm2835_spi_transfern(pRGB565,len*2); // write to SPI without read back   
}

// Set Display Window
void ILI9341_SetWindow(uint16_t sx, uint16_t ex, uint16_t sy, uint16_t ey)
{
 ILI9341_WriteCommand(0x2A);    // Column Address Set (X Axis)
 ILI9341_WritePara(sx >> 8);    // Start Column High Byte
 ILI9341_WritePara(sx);            // Start Column Low Byte
 ILI9341_WritePara(ex >> 8);    // End Column High Byte
 ILI9341_WritePara(ex);            // End Column Low Byte

 ILI9341_WriteCommand(0x2B);    // Page Address Set (Y Axis)
 ILI9341_WritePara(sy >> 8);    // Start Page High Byte
 ILI9341_WritePara(sy);            // Start Page Low Byte
 ILI9341_WritePara(ey >> 8);    // End Page High Byte
 ILI9341_WritePara(ey);            // End Page Low Byte

 ILI9341_WriteCommand(0x2C);    // Memory Write
}

// LCD module inital
int ILI9341_Inital()
{
 ILI9341_WriteCommand(0xCB);    // Power Control A 
 ILI9341_WritePara(0x39);
 ILI9341_WritePara(0x2C);
 ILI9341_WritePara(0x00);
 ILI9341_WritePara(0x34);    // vcore control, 1.6v
 ILI9341_WritePara(0x02);     // ddvdh control, 5.6v

 ILI9341_WriteCommand(0xCF);    // Power Control B 
 ILI9341_WritePara(0x00);
 ILI9341_WritePara(0XC1);    // power control
 ILI9341_WritePara(0X30);     // discharge path enable, enable ESD protection

 ILI9341_WriteCommand(0xE8);    // Driver Timing Control A 
 ILI9341_WritePara(0x85);    // get driver non-overlap timing control, default + 1 unit
 ILI9341_WritePara(0x00);     // EQ and CR timing control, both default-1unit
 ILI9341_WritePara(0x78);     // pre-charge timing, default - 2 unit

 ILI9341_WriteCommand(0xEA);    // Driver Timing Control B
 ILI9341_WritePara(0x00);    // gate driver timing control
 ILI9341_WritePara(0x00);     

 ILI9341_WriteCommand(0xED);    // Power on Sequence Control
 ILI9341_WritePara(0x64);    // CP1 soft start keep 1 frame, CP23 soft start keep 3 frame
 ILI9341_WritePara(0x03);     // Power on sequence control, En_vcl 1st frame enable, En_ddvdh 4th frame enable
 ILI9341_WritePara(0X12);    // Power on sequence control, En_vgh 2nd frame enable, En_vgl 3rd frame enable
 ILI9341_WritePara(0X81);    // DDVDH enhance mode, enable

 ILI9341_WriteCommand(0xF7);    // Pump Ratio Control 
 ILI9341_WritePara(0x20);    // Ratio control, DDVDH = 2xVCI
 
 ILI9341_WriteCommand(0xC0);    // Power control 1
 ILI9341_WritePara(0x23);    // Set the GVDD level, 4.60v

 ILI9341_WriteCommand(0xC1);    // Power control 2
 ILI9341_WritePara(0x10);    // Sets the factor used in the  step-up circuits, DDVDH=VCI*2, VGH=VCI*7, VGL = -VCI*4

 ILI9341_WriteCommand(0xC5);    // VCOM Control
 ILI9341_WritePara(0x3e);    // Set VCOMH voltage, 4.200v
 ILI9341_WritePara(0x28);    // Set VCOML voltage, -0.700v

 ILI9341_WriteCommand(0xC7);    // VCOM control2
 ILI9341_WritePara(0x86);    // Set the CVOM offset voltage, VCOMH = VMH-58, VCOML = VML-58

 ILI9341_WriteCommand(0x36);    // Memory Access Control
 ILI9341_WritePara(0x48);    // Row Address Order(MY), Column Address Order(MX), Top-Down refresh order, BGR Color Filter

 ILI9341_WriteCommand(0x3A);    // COLMOD: Pixel Format Set   
 ILI9341_WritePara(0x55);    // DPI and DBI are 16 bits / pixel

 ILI9341_WriteCommand(0xB1);    // Frame Rate Control (In Normal Mode) 
 ILI9341_WritePara(0x00);    // DIVA setting, fosc / 1
 ILI9341_WritePara(0x18);    // RTNA setting, Frame rate 79 Hz

 ILI9341_WriteCommand(0xB6);    // Display Function Control
 ILI9341_WritePara(0x08);    // Non-display area interval scan, determine source in partial display mode
 ILI9341_WritePara(0x82);      // REV normally white, GS SM G1->G320, SS S1-S720, ISC 5 frames 85ms
 ILI9341_WritePara(0x27);      // LCD driver line 320 lines

 ILI9341_WriteCommand(0xF2);    // Enable 3G
 ILI9341_WritePara(0x00);    // disable 3 gamma control

 ILI9341_WriteCommand(0x26);    // Gamma Set
 ILI9341_WritePara(0x01);    // Gamma curve 1 (G2.2)

 ILI9341_WriteCommand(0xE0);    // Positive Gamma Correction
 ILI9341_WritePara(0x0F);   
 ILI9341_WritePara(0x31);
 ILI9341_WritePara(0x2B);
 ILI9341_WritePara(0x0C);
 ILI9341_WritePara(0x0E);
 ILI9341_WritePara(0x08);
 ILI9341_WritePara(0x4E);
 ILI9341_WritePara(0xF1);
 ILI9341_WritePara(0x37);
 ILI9341_WritePara(0x07);
 ILI9341_WritePara(0x10);
 ILI9341_WritePara(0x03);
 ILI9341_WritePara(0x0E);
 ILI9341_WritePara(0x09);
 ILI9341_WritePara(0x00);

 ILI9341_WriteCommand(0XE1);    // Negative Gamma Correction
 ILI9341_WritePara(0x00);
 ILI9341_WritePara(0x0E);
 ILI9341_WritePara(0x14);
 ILI9341_WritePara(0x03);
 ILI9341_WritePara(0x11);
 ILI9341_WritePara(0x07);
 ILI9341_WritePara(0x31);
 ILI9341_WritePara(0xC1);
 ILI9341_WritePara(0x48);
 ILI9341_WritePara(0x08);
 ILI9341_WritePara(0x0F);
 ILI9341_WritePara(0x0C);
 ILI9341_WritePara(0x31);
 ILI9341_WritePara(0x36);
 ILI9341_WritePara(0x0F);

 ILI9341_Wakeup();

 return 1;
}

// LCD module exit sleep mode
void ILI9341_Wakeup(void)
{
 ILI9341_WriteCommand(0x11);    // Sleep Out
 delay(120);            // delay 120ms, must be > 5 ms
 ILI9341_WriteCommand(0x29);    // Display On
 ILI9341_WriteCommand(0x2C);    // Memory Write
}

//LCD module into sleep mode
void ILI9341_Sleep(void)
{
 ILI9341_WriteCommand(0x28);    // Display Off
 delay(20);                     // delay 20ms
 ILI9341_WriteCommand(0x10);    // Enter Sleep Mode
}


// ********** main.cpp **********
// **********  主程式    **********
#include <stdio.h>
#include <iostream>
#include "ILI9341.h"
#include <opencv2/opencv.hpp>
#include <ctime>

using namespace std;
using namespace cv;

int main(int argc, char **argv)
{
 double t0,t1,t2;
 bool bPress = false; // 記錄快門按下狀態
 int result = LCD_Inital();

 switch(result){ // 顯示初始化錯誤訊息
   case 0:  
          printf("LCD inital OK.\n");
          break;
   case 1:
          printf("BCM2835 inital failed. Are you running as root??\n");
          break;
   case 2:
          printf("GPIO inital failed. Are you running as root??\n");
          break;
   case 3:
          printf("SPI inital failed. Are you running as root??\n");
          break;
   case 4:
          printf("ILI9341 inital failed. Please check PCB connection is OK.\n");
          break;
   default:
          printf("LCD inital failed.\n");
 }

 if(result != 0) 
   return 0;

  VideoCapture cap(0); // 啟動攝像頭連續取像

  if (!cap.isOpened()) { // 若無法開啟則結束
    cerr << "ERROR: Unable to open the camera" << endl;
    return 0;
  }

  cap.set(CV_CAP_PROP_FRAME_WIDTH, 320); // 設定攝像頭輸入解析度為320*240
  cap.set(CV_CAP_PROP_FRAME_HEIGHT, 240);

  Mat frame;

  cout << "Start grabbing !" << endl;
  LCD_SetHorizontalDisplay(); // 設定LCD為橫式顯示

  char strFps[10]; // 儲存速度字串
  t1 = (double)getTickCount(); // 取得目前時間

  while(1) {
    t0 = t1; // 儲存舊時間
    t1 = (double)getTickCount(); // 儲存目前時間

    // 若快門鍵(SW1)按下則閃光燈(LED1)點亮
    if(bcm2835_gpio_lev(PIN_GPIO_SW1)!=0){
      bPress = true;
      bcm2835_gpio_write(PIN_GPIO_LED_R, 1); // 開啟閃光燈
      delay(30); // 等待取到有打光的影像
    }
 
    cap >> frame; // 將取得的影像複製到frame

    // 計算兩次執行時間差 取倒數即為每秒幀數 顯示在LCD左上角
    // 若不需顯示則註解掉下面兩行
    sprintf(strFps,"%2.1f FPS", 1.0 / ((t1-t0) / getTickFrequency()));    
    putText(frame, strFps, Point(20,40), FONT_HERSHEY_DUPLEX, 0.8, Scalar(0,255,0),1);

//    // 測試按鍵SW1 SW2是否正常 並顯示在LCD上
//    if(bcm2835_gpio_lev(PIN_GPIO_SW1)!=0)
//      putText(frame, "SW1 ON ", Point (20,70), FONT_HERSHEY_DUPLEX,1, Scalar(0,255,0),1);
//    else
//      putText(frame, "SW1 OFF", Point (20,70), FONT_HERSHEY_DUPLEX,1, Scalar(0,255,0),1);

//    if(bcm2835_gpio_lev(PIN_GPIO_SW2)!=0)
//      putText(frame, "SW2 ON ", Point (20,100), FONT_HERSHEY_DUPLEX,1, Scalar(0,255,0),1);
//    else
//      putText(frame, "SW2 OFF", Point (20,100), FONT_HERSHEY_DUPLEX,1, Scalar(0,255,0),1);

    // 將取得影像逐行顯示在LCD上
    for(int i = 0; i < frame.rows; i++){ 
      char *ptrS = frame.ptr<char>(i);

      ILI9341_WriteLineBGR2RGB565(ptrS, frame.cols);
    }

    // 若無法取得影像則結束程式
    if (frame.empty()) {
        cerr << "ERROR: Unable to grab from the camera" << endl;
        break;
    }
   
    // 若快門鍵有按過則清除按下旗標 關閉閃光燈 並存檔
    if(bPress){
      bPress = false;
      bcm2835_gpio_write(PIN_GPIO_LED_R, 0); // 關閉閃光燈

      char file_name[30];
      time_t now = time(0);
      tm *ltm = localtime(&now);
 
      sprintf(file_name,"%02d%02d%02d.jpg",1+ltm->tm_hour,1+ltm->tm_min,1+ltm->tm_sec); // 以時間當作檔名
      imwrite(file_name,frame); // 儲存影像
      delay(1000); // 畫面暫停一秒
    }

    // 若SW2按下則綠燈閃一下結束程式
    if(bcm2835_gpio_lev(PIN_GPIO_SW2) != 0){
       bcm2835_gpio_write(PIN_GPIO_LED_G, 1);
       delay(50);
       bcm2835_gpio_write(PIN_GPIO_LED_G, 0);
       break;
    }
  }

  cap.release();
  BCM2835_End();
  cout << "Done!" <<endl;
  return 0;
}

// ***********     go.sh    **********
// ***********  編譯指令 **********

clear
date
g++ `pkg-config --libs opencv` main.cpp ILI9341.cpp -o camera -l bcm2835
date

OpenQCam樹莓派開源迷你相機 (專案首頁)
OpenQCam樹莓派開源迷你相機#1_硬體需求
OpenQCam樹莓派開源迷你相機#2_硬體線路圖
OpenQCam樹莓派開源迷你相機#3_作業系統安裝
OpenQCam樹莓派開源迷你相機#4_工作環境建置
OpenQCam樹莓派開源迷你相機#5_系統開發
OpenQCam樹莓派開源迷你相機#6_程式源碼

OpenQCam樹莓派開源迷你相機#5_系統開發

五、開源迷你相機OpenQCam 系統開發

接下來就要開始說明如何進行程式開發。本專案是採C++語言,原則上只須文字編輯器(可用內建nano 或其它習慣編輯器亦可)即可,不需視窗也不需要IDE 環境就可獨立完成。若不習慣傳統文字編輯器的人,亦可在視窗環境點選檔名,按滑鼠右鍵,啟用Text Editor(類似Windows上的工具軟體-[筆記本]),即可進行編輯。另外在Linux 上知名的視窗開發整合環境軟體QT,經測試在Pi Zero W 上執行非常卡,且和OpenCV 有些許不相容,後續若有找到比較合適的解法會再另外撰文說明,如想使用其它IDE 亦可。

本專案目前僅支援TFT LCD 320*240 像素(一般正常是直式顯示,目前預設用程式轉成橫式顯示),ILI9341 驅動IC 之SPI 介面,此類LCD 在網路上為主流產品很容易買到。程式中有提供顯示直式介面的函式,可自行修改利用。若有其它顯示規格需求請上網查閱驅動IC 資料手冊自行修改,但請注意改錯參數有可能造成LCD損壞,後果請自行負責。

工作前建議先建立一個Project 目錄,再將本專案所有檔案下載該目錄下。接著執行本專案已預建編譯指令批次檔 go.sh 進行編譯,等待約30 秒後即可完成,產生執行檔camera,由於BCM2835 須要較高權限,所以執行時要加上 sudo,完整操作如下所示。

sudo ./go.sh
(編譯程式)
sudo ./camera (執行程式)

執行後,攝像頭會自動取像並降低影像解析度,從五百萬像素降至QVGA (320*240 像素),LCD 上會同步顯示取像內容,在LCD 左上角會顯示目前速度(取像加顯示),單位是FPS(每秒幀數),正常狀況約有15 ~ 18 FPS。當按下SW1 時紅燈會亮,模擬快門按下後閃光燈會亮,畫面涷結1 秒並自動存檔。按下SW2 時綠燈會亮,並結束程式。

本專案是做為後續利用OpenCV 進行影像辨識的很好入門平台,程式的相關註解詳見各程式原始碼。受限於個人能力有限,撰文上難免產生誤解或疏漏,如有任何問題歡迎留言或來信指教! 

OpenQCam樹莓派開源迷你相機#4_工作環境建置

四、工作環境建置

接下來可依需求安裝相關軟體,大部份安裝都在命令模式下進行,點擊畫面上左上方,黑色[ >_ ]的圖案,就會開啟一黑色視窗,進入命令(CMD)模式。請注意大部份的命令英文字母大小寫是被認為不同文字,所以不要打錯。

4.1 更新系統

雖然樹莓派的作業系統可能已是最新的,但其它依賴包(函式庫)就不一定,因此首先要更新樹莓派,輸入以下指令。

sudo apt-get update
sudo apt-get upgrade
sudo rpi-update (更新樹莓派韌體,可忽略不作)
sudo reboot (重新開機)

4.2 測試相機

輸入下列指令,測試相機是否可正常拍照,執行後應會在同一目錄下產生一test.jpg 的影像檔。若無法取像,則需檢查相機的軟排線是否正確的插在連接座上沒有鬆脫。

raspistill -o test.jpg

4.3 安裝中文輸入法 (選配,可不裝)

這裡推薦 SCIM,因為它支援的輸入法包括兩岸三地常見中文輸入法。當然如你有習慣的其它輸入法亦可安裝。

sudo apt-get install scim scim-tables-zh scim-chewing

4.4 安裝影音播放軟體 (本專案用不到,但建議安裝)

由於之後可能會透過攝像頭錄製影片,為方便觀看影片建議可安裝著名開源影音播放軟體 VLC。

sudo apt-get install vlc

4.5 安裝OpenCV 及編譯程式必要依賴包

OpenCV 是網路上最多人使用的開源電腦視覺、影像處理工具,功能與時俱進,內容包山包海,連最新的深度學習都有,目前(Dec. 2017)已到了3.3 版,想多了解一下可參閱官網 https://opencv.org/ 。這裡為方便大家工作,不要浪費太多時間及遇到奇怪情況裝不起來的問題,建議大家直接用編譯好的OpenCV 2.4.9 版就非常夠用了,安裝後包含C, C++及Python 都可使用。若各位想使用最新版本可自行編譯, 請參考
https://www.pyimagesearch.com/2015/12/14/installing-opencv-on-your-raspberry-pi-zero/ ,這裡有完整說明,不過要有心理準備,整個過程會超過十二小時,甚至偶而還會發生花了二十四小時以上還編譯不過的情況產生,祝好運。為加快各位上線時間請依序執行下面指令安裝OpenCV 2.4.9 版即可,安裝時間頗長,請多一些耐性。

sudo apt-get -y install build-essential cmake cmake-curses-gui pkg-config
sudo apt-get -y install libgtkglext1-dev
sudo apt-get -y install libv4l-dev v4l-utils
sudo apt-get -y install libopencv-dev python-opencv

4.6 安裝樹莓派GPIO 及介面驅動庫

一般驅動樹莓派的GPIO(包括SPI, I2C, PWM…)有很多方式,網路上有提供很多免費工具包可驅動,亦可直接對直接位置寫入,由於本專案是用C++語言完成,所以推薦採用BCM2835(樹莓派Pi Zero W 的主晶片名稱為BCM2835)套件包來驅動即可,可免去很多設定及變數使用問題。更進一步資訊可參考官網 http://www.airspayce.com/mikem/bcm2835/index.html,目前(Dec. 2017)最新版本為1.52。請依以下步驟下載、解壓及安裝。另外要注意的一點,這裡用的40pin GPIO,所以用程式控制時要選用RPI_V2_GPIO_P1_X (X 表示第幾腳,而非GPIO 編號)。

wget http://www.airspayce.com/mikem/bcm2835/bcm2835-1.52.tar.gz
tar zxvf bcm2835-1.52.tar.gz
cd bcm2835-1.52
./configure
make
sudo make check
sudo make install

為使一開機相機就能啟用且可讓OpenCV 直接驅動相機不必透過其它工具,所以要修改系統檔案,其步驟如下。

cd /etc/modules-load.d/
sudo nano modules.conf (這裡使用內建文字編輯器nano 進行編輯,亦可用其它文字編輯器)進入後在最後一行按下ENTER,新增一空白行,加入bcm2835-v4l2,按下[Ctrl + O](兩鍵同時按)存檔,再按[Ctrl + X]離開,完成編輯工作。
再輸入reboot 重新開機完成所有系統環境設定。

4.7 系統備份 (選配,可自由決定)

作到這裡已大致完成系統環境建置,如果怕開發過程安裝太多東西搞亂環境,或者後續要複製到另一台Pi Zero W 上,建議可再利用Win32 Disk Imager 將系統備份。首先將Pi Zero W 關機,抽出目前已正常工作的記憶卡,放入桌機或筆記型電腦記憶卡槽,啟動Win32 Disk Imager,指定存檔之路徑及檔名,選擇欲備份的記憶卡磁碟機代號,按下[Read],耐心等待進度條到100%,就完成備份。

OpenQCam樹莓派開源迷你相機#3_作業系統安裝

三、作業系統安裝

一般如果上網買樹莓派Pi Zero W 套餐時,通常會附一片4GB 或8GB SD 卡,裡面已安裝好原廠作業系統,開機就能使用。為了後續系統擴充及安裝更新版本的OpenCV,強烈建議至少要換成16GB 記憶卡。為避免要從8GB 記憶卡搬到16GB時會產生許多設定工作,建議直接以16GB 空白記憶卡開始安裝會簡單些。首先到樹莓派官網下載視窗版環境最新版本的作業系統Raspbian Stretch with DESKTOP 的ZIP 檔,https://www.raspberrypi.org/downloads/raspbian/,解壓縮後會得到作業系統影像檔 (Image File) 2017-11-29-raspbian-stretch.img。


圖3-1 樹莓派官網下載畫面
接著利用Win32 Disk Imager將影像檔(Image File)燒寫到記憶卡中,如圖3-2所示。首先選擇要燒寫的檔案路徑及名稱,接著選擇要寫入的記憶卡磁碟機代號,最後按下【Write】就開始燒錄工作,此過程大約要半小時左右。如果沒有此記憶卡燒錄工具可以到 https://sourceforge.net/projects/win32diskimager/ 進行下載安裝。


圖3-2 燒錄影像檔步驟

記憶卡燒錄完成,放入Pi Zero W 記憶卡槽後就可準備送電啟動。第一次送電前有幾個動作要注意。
  1. 以HDMI 連接線連接Pi Zero W和HDMI 螢幕(可用有HDMI 功能的電視取代),第一次啟動無法以遠端(無螢幕)方式進入。
  2. 先不要將無線鍵盤滑鼠的接收器插入USB OTG 插孔中,不然啟動後會進不了作業系統,一直反覆重新開機,估計可能是啟動電流過大造成,可以等到進到桌面環境後再插入。
  3. 接著就可送電,樹莓派Pi Zero W 板子上有兩個Micro USB 孔,靠外側那個是+5V 電源,原則上Pi Zero W 非常省電,一般5V 1A 的變壓器就足夠所需電力,甚至一般電腦的USB 2.0 插孔(500mA)也能推的動,而靠板子中間那個是USBOTG,切記不要搞錯,以免後續外接USB 裝置無法工作。
樹莓派Pi Zero W 有支援無線網路功能,強烈建議使用,如此後續就可直接遠端連線操作,不必再連接螢幕及無線鍵盤滑鼠。因此進入視窗作業環境桌面後,第一件事就是點選右上角網路符號,啟動無線網路(WIFI ON),連線到自家的無線網路環境,輸入帳號密碼。為確保無線網路工作正常,可點選畫面左上方地球圖案,啟動瀏覽器進行測試,可試著隨便連到任意一個網址(如GOOGLE)看看是否正常工作。接著將滑鼠移到畫面右上角無線符號處,此時會出現目前WIFI 動態配置的IP 位置(192.168.xxx.xxx),先記下來,等下會要用到。

由於系統預設是英文介面,而其中許多系統參數設定不符需求加上特殊介面也未開啟,所以要先點選畫面左上角樹莓圖案進入主選單/Preference/Raspberry Pi Configuration。進入後共有四個子頁面,如圖3-3 所示。樹莓派原廠預設使用者名稱pi,密碼為raspberry,且會自動登入(Auto Login),若不喜歡預設值,可於System 頁面下修改,建議可先不更動。由於後續要遠端登入操作,所以螢幕解析度可依自己需求按下[Resolution]鍵進行修改。再來切到Interfaces 頁面,預設全部都為Disable,為讓本專案能順利完成,必須將Camera, VNC 及SPI 致能(Enable)起來。而Performance 頁面預設撥給GPU 128MB,這個部份可暫時不動。最後切到Localisation 頁面,設定下列參數:
  1. 按下[Set Local]鍵,將Language 設為Zh (Chinese) 或 lzh (Literay Chinese),Country 設為TW(Taiwan), Character Set 設為UTF-8。
  2. 按下[Set Time Zone]鍵,將Area 設為Asia,Location 設為Taipei。
  3. 按下[Keyboard]鍵,系統預設為英國鍵盤,這會導致有些符號打不出來(如@),因此須選擇左邊United States,選擇右邊English(US),鍵盤才能正常工作。
  4. 按下[Set WiFi Country],將設定改為TW (Taiwan),如此系統時間才會和網路同步。
最後按下[OK]鍵,系統會詢問是否重新開機(Reboot),選擇YES 後,馬上拔掉USB OTG 上的接收器,讓系統重新啟動。接下來就不需要螢幕、鍵盤、滑鼠,只須利用VNC 軟體遠端連線操作即可。


 圖3-3 樹莓派系統參數設定

根據實測,強烈建議不要直接用apt-get 安裝tightvncserver 到樹莓派上,有部份軟體(如QT)在遠端連線時會造成鍵盤無法正確輸入文字問題,建議依上一步驟說明將系統參數VNC 設定為Enable 即可。接下來要在桌機或筆電上安裝RealVNC才能遠端操控Pi Zero W , 相關軟體可至https://www.realvnc.com/en/connect/download/vnc/ 下載。啟動RealVNC 後輸入剛才記下的IP(192.168.xxx.xxx),帳號:pi,密碼raspberry,即可遠端登入,進行所有操作。如果有習慣常的VNC 軟體亦可。 

OpenQCam樹莓派開源迷你相機#2_硬體線路圖

二、硬體線路圖

本專案的線路基本上不難,如圖2-1 左圖所示。主要是連接樹莓派Pi Zero GPIO到LCD模組、按鍵及LED,另外有留了一組接頭和LCD 模組信號線並聯,方便邏輯分析儀連接分析問題用,原則上可不必接。為方便工作,可選擇自行以雙面洞洞板自行焊接,簡易接線圖如圖2-1 右圖所示,藍色為實際焊在板上的裸銅線,紅色線為跳線(有膠皮絕緣線),請注意勿和其它線短路,以免電路無法正常工作。若要全部用OK 線(有膠皮鍍銀線)焊接亦可。若實在不熟悉焊接工作,亦可使用杜邦端子線連接或用麵包板依電路圖連線亦可,只是無法像一台相機拿在手上操作。

電路上的SW1 & SW2 可直接按鍵操作或者以外部觸發(Strob)信號連接亦可,但請注意系統目前以3.3V 為主要控制電源,因此當外部觸發信號GPIO 21(SW1)或GPIO 26(SW2) 提供3.3V 時,相當於按鍵被按下。另外LED 1 & 2 可做為指示信號燈或推動閃光燈,不過由樹莓派GPIO 的輸出電流很小(10~20mA),因此若要推動大電流的燈具時要額外接一電晶體來放大驅動電流,避免把GPIO 埠燒壞。


OpenQCam樹莓派開源迷你相機#1_硬體需求

一、硬體需求:

在執行本專案前,首先要備妥下列硬體元件,詳如圖1-2 所示。

  1. 樹莓派 Pi Zero W 一片 (使用Pi 1, 2 ,3, Zero 皆可,只是有些設定方式不同,後面會補充說明)
  2. 樹莓派 Pi Zero 專用500 萬畫素相機 (其它解析度相機亦可,請注意非Pi Zero使用者必須使用Pi 對應版本的相機)
  3. 16GB 記憶卡一張 (一般買樹莓派套餐附的都是8GB 記憶卡,要跑OpenCV 建議要升級)
  4.  SPI 介面2.2 吋TFT LCD(QVGA 320*240) 一片 (一般市購2~3.5 吋TFT LCD 多半是用ILI9341 驅動,若非該驅動IC 則需另外撰寫驅動程式。另外此類模組常會自帶記憶卡或觸控模組,本專案暫未使用)
  5. 雙面洞洞板 一片 (若不想自己焊板子,可直接用麵包板插線完成,只是不方便拿在手上把玩)
  6. 90 度按鍵 兩個 (做為操作用按鍵,或當外部觸發拍照,數量可自行增減)
  7. 紅色、綠色LED 各一個 (顏色不据,可當成閃光燈控制或指示燈用)
  8. 10k 及 220 歐姆1/4W 電阻 各二個 (提供按鍵及LED 使用)
  9. 40 (20 pin 雙排) pin 2.54 mm 間距公、母排針 各一組 (連接樹莓派和洞洞板用)
  10. 20 (20 pin 單排) pin 2.54 mm 間距公、母排針 各一組 (拆成 9pin 和 4 pin,分別提供LCD 模組上顯示用及記憶卡用)
  11. 5V 變壓器及Micro USB 連接線 一組 (負責供電給Pi Zero,若使用電腦USB或行動電源供電亦可)
  12. HDMI 顯示幕及Mini HDMI 轉大頭HDMI 連接線 一組 (一開始設定Pi Zero 使用,之後就完全用不到,可拿有HDMI 功能的電視充當一下)
  13. 無線鍵盤滑鼠及USB OTG 連接線 一組 (一開始設定Pi Zero 使用,之後使用遠端連線操作就用不到,亦可直接借用一下家中電腦的無線鍵鼠)
  14. 可選購Pi Zero CPU 專用散熱片 一組 (除非長時間CPU 滿載情況造成熱當,原則不一定要裝)
其中第1, 2, 3, 4, 12, 13 項可自行到露天拍賣或淘寶上採購,其它部份可自行到電子零件商場或網路上採購。一般上網購買樹莓派Pi Zero 套餐(建議主板、相機及排線一起買,外殼依需求搭配)時會附4GB 或8GB 記憶卡(記得要換購16GB)、5V變壓器,Micro USB 充電線、USB OTG 連接線及Mini HDMI 轉大頭HDMI 連接線,有些還會附CPU 散熱片。根據不同採購管道及配套大致上總體成本(不含第12, 13項)約在2000 ~ 3000 台幣。

圖1-2 開源迷你相機硬體需求

另外本專案對於新手而言線路還算有點小複雜,在製作或除錯過程中難免會遇到電氣信號不良或信號被干擾等問題產生,一般家裡可能沒有高性能的儀表(如示波器、邏輯分析儀)可協助分析問題,建議至少要有一部數位三用電錶,另外可考慮選配(原則上用不到)一台簡易型低速邏輯分析儀(不到台幣500 元,如圖1-3所示)來協助。依十倍(至少也要三倍以上)取樣速度理論,這台僅適合分析2 MHz以下信號,但對於重寫LCD SPI 驅動程式抓問題來說已非常足夠。