4.14. CSI 基礎

csi 模組是 Python 程式碼驅動相機感測器的方式。每一個擷取影格的指令碼都遵循同樣的三段式結構:頂部的匯入、中間的一次性設定,以及底部一個 while True 迴圈,逐一從相機拉取影格。

4.14.1. 典型的迴圈

import csi, image, time

csi0 = csi.CSI()
csi0.reset()
csi0.pixformat(csi.RGB565)
csi0.framesize(csi.QVGA)

clock = time.clock()
while True:
    clock.tick()
    img = csi0.snapshot()
    # process img here
    print(clock.fps())

4.14.2. 每個呼叫的作用

import csi, image, time

匯入三個模組。csi 控制感測器,image 定義 snapshot() 回傳的 Image 類別,而 time 提供用來量測每秒影格數的 time.clock() 輔助工具。

csi.CSI()

建構一個包裝單一實體相機感測器的 CSI 實例。建構子會占用相機周邊裝置並記錄逐感測器的設定。只有單一感測器的相機會有一個 CSI 實例;具有兩個感測器的相機(色彩加上熱感、色彩加上事件)則有兩個,各以傳給建構子的 cid 引數選定。

csi0.reset()

為感測器供電並進行設定。預設情況下,它會脈衝觸發感測器的重置接腳,然後寫入感測器的 I2C 暫存器使其進入已知的起始狀態。後續的設定呼叫(pixformatframesize、各種自動控制旋鈕)會透過同一條 I2C 控制匯流排推送更多的暫存器寫入。

csi0.pixformat(csi.RGB565)

寫入用以挑選輸出像素格式的感測器暫存器。可用的選擇就是像素格式頁面所介紹的格式:RGB565GRAYSCALEBAYERYUV422,以及在支援的感測器上的 JPEG

csi0.framesize(csi.QVGA)

寫入用以挑選輸出解析度的暫存器。QVGA 為 320 × 240;在支援的感測器上,這些具名尺寸最高可達 WQXGA2(2592 × 1944,約 5 MP)。只要能對齊感測器的輸出能力,自訂的 (width, height) 元組也行得通。

clock = time.clock()

建立一個時鐘輔助工具。迴圈內每次呼叫 clock.tick() 都會記錄該次疊代的起始時間;time.clock.fps() 會以每秒影格數回報近期的迴圈速率。

img = csi0.snapshot()

從感測器擷取一個影格,並以 Image 的形式回傳。該影格最終如何進入記憶體,其運作機制值得細看。

4.14.3. snapshot 如何填滿記憶體

感測器以每秒數百 MB 的速率在感測器匯流排所述的像素資料匯流排上傳遞像素,這對 CPU 而言遠遠太快,無法以軟體逐像素複製。

取而代之,MCU 把傳輸工作卸載給 直接記憶體存取(DMA),這是一個獨立於 CPU 的硬體引擎,能在 MCU 內部把位元組從一處複製到另一處而完全不需要 CPU 介入。相機輸入周邊裝置會把每個進來的像素位元組接入一個小型的晶片內 FIFO;任何在 MCU 端執行的 ISP 階段會在資料通過時加以處理;而 DMA 引擎則把完成的像素寫入 RAM 中影格緩衝區的對應像素偏移處。一旦 DMA 通道完成程式設定,這條鏈中的任何環節都不再需要 CPU。

snapshot() 被呼叫時:

  1. CSI 驅動程式以影格緩衝區的位址、傳輸長度(一個影格的像素量),以及 DMA 完成中斷的回呼函式來程式設定 DMA 引擎。

  2. 驅動程式啟用相機輸入周邊裝置,並等待感測器發出下一個影格開始的訊號。

  3. 當感測器把影格串流送出時,周邊裝置會把每個像素位元組經過 ISP 再交給 DMA 引擎,由其把結果寫入 RAM 中下一個影格緩衝區偏移處。傳輸期間 CPU 可自由執行其他程式碼。

  4. 當影格的最後一個像素抵達時,DMA 觸發其完成中斷,驅動程式把影格緩衝區包裝進一個 Imagesnapshot() 隨即把它回傳給使用者程式碼。

回傳的 Image 並不擁有像素資料的副本,它指向相機在 RAM 中的其中一個影格緩衝區。相機保留多少個影格緩衝區,以及每次呼叫 snapshot() 時它們如何在 DMA 與使用者程式碼之間移交,取決於應用透過 framebuffers() 所選定的緩衝模式。