National Central University
Uedu Main Site
Explore Uedu
Student Console
Register as Member/Login
Research Informed Consent Center
Survey Center
Educational Big Data Challenge
Claim or create a course
Try an AI Scenario Task
Support & Messages
Uptime Data

UeduGPTs

--

Jupyters

3

Local AI

--

Uedu Code

--

中央大學 AQI 80 24°C PM2.5 21
AI Reply Desktop Notifications

Show a desktop notification when the AI TA finishes replying

Chat Message Notifications

Notify me when classmates post messages in the forum

Sound notification

Play an alert sound whenever there is a new notification

Unit 13

相機與影像

影像是什麼、相機模型、從像素到方向
Interaction實作 30 minute(s)

Learning Objectives

  • 說明影像在程式裡是什麼:寬、高、通道與 sensor_msgs/Image 的 data
  • 用針孔相機模型,把像素位置換算成方位角
  • 分辨單眼相機與深度相機能回答什麼問題
  • 說明曝光與模糊怎麼影響感知,以及什麼情況下相機的資料不能信

你看到一張教室,程式看到一堆數字

光達告訴機器人「那個方向 1.8 公尺有東西」,但說不出那是牆、是門,還是一個人。相機能分辨這些,代價是它給的資料很原始:一張 640 × 480 的彩色影像是 640 × 480 × 3 = 921,600 個 0 到 255 的數字,每秒 30 張。

這一課處理兩件事:這些數字怎麼排,以及某個像素代表哪一個方向。第 14 課才會讓模型從數字裡認出東西。

下面這張圖是生成的校園場景。點圖上任何一點,右邊會顯示附近的像素值;所有數字都是從這張圖真的讀出來的。

互動練習:影像就是數字

綠框是選到的位置,右邊是放大的像素。中間的白色虛線是畫面的中線。

sensor_msgs/Image 怎麼放這些數字

ROS 2 裡未壓縮的影像是 sensor_msgs/Image,資料放在一維的陣列裡:

欄位 意思 範例(640 × 480 彩色)
height、width 列數、行數 480、640
encoding 像素的格式與順序 rgb8(有些相機與函式庫是 bgr8)
step 一列佔幾個位元組 640 × 3 = 1920
data 所有像素,一列接一列 長度 = step × height = 921,600

所以像素 (u, v) 的紅色在 data[v × step + u × 3],綠色、藍色在後面兩個。u 是橫的、v 是直的,(0, 0) 在左上角,v 往下變大,和數學課的 y 軸相反。

encoding 看錯是常見的錯誤:把 bgr8 當成 rgb8,紅色的東西會變藍色,程式不會報錯,只是模型的判斷變差。第 14 課的 YOLOX 輸入就要 BGR 順序。

用 numpy 處理時會把這個一維陣列變成 height × width × 3 的陣列,寫法是 img[v, u],先列後行。下面的範例用一張 3 × 4 的小影像走一次。

一張很小的影像

右邊會畫出陣列。注意 img[1, 2] 的順序是先列再行,以及曝光加倍時 clip 把超過 255 的值都截成 255。

針孔相機:像素代表哪一個方向

相機模型最簡單的版本是針孔相機(pinhole camera):光線穿過一個小孔,投影到感光面上。真實的影像在小孔後面而且是倒的,為了好算,通常把影像平面畫在小孔前面,算出來的結果一樣。從上往下看:

| 光軸(正前方)
|
|<--- X ---->* 物體
|           /
|          /
|         /
|        /     Z:物體往前多遠
|       /
|      /
|     /
|----*------- 影像平面
|   /
|  /   fx:針孔到影像平面
| /
O 針孔

光線從物體直線穿過針孔,在影像平面上留下一個點。這個點到光軸的距離,就是像素 u 和影像中心 cx 的差。

兩個相似三角形,得到:

(u − cx) ÷ fx = X ÷ Z = tan θ
  • fx:焦距,單位是像素,不是毫米。fx 越大,同樣的角度佔越多像素,看起來越「望遠」。
  • cx:主點(principal point),光軸打到感光面的位置,通常接近影像中央。

所以 θ = atan((u − cx) ÷ fx)。數字例子:寬 640、fx = 554、cx = 320,物體在 u = 480:

θ = atan((480 − 320) ÷ 554) = atan(0.289) ≈ 16.1°

只知道水平視角時,fx = (寬 ÷ 2) ÷ tan(視角 ÷ 2)。視角 60°、寬 640 就是 320 ÷ 0.577 ≈ 554。

正負號是一個常見的陷阱。 相機的光學座標系依 REP-103 是 z 往前、x 往右、y 往下,而機器人本體是 x 往前、y 往左、z 往上(第 2 課)。像素在中線右邊時,相機座標的 x 是正的,對機器人來說卻是往右,方位角要算成負的:

bearing = math.atan((cx - u) / fx)      # 左正右負,和機器人的旋轉方向一致

這些參數在 ROS 2 的 sensor_msgs/CameraInfo 裡,k 是 3 × 3 的內參矩陣:k[0] 是 fx、k[2] 是 cx、k[4] 是 fy、k[5] 是 cy。訊息定義規定 k[0] 是 0 代表沒有校正過。真實鏡頭還有畸變(直線在畫面邊緣會變彎),校正得到的畸變係數放在 d,要先修正才能用這個公式。

從像素算到方位角

物體越靠近畫面邊緣,同樣多的像素代表的角度越小,所以方位角和像素差不是正比,要用 atan。

實作:像素位置算方位角

寫出 bearing_deg(u, width, fov_deg):回傳方位角(度),左正右負。假設主點在影像中央。

你的輸出
預期輸出

方向有了,距離呢

一個像素只決定一條射線,物體可能在這條射線上任何位置。單眼相機(monocular)本身沒有尺度:近處的玩具車和遠處的真車,在影像裡可以一模一樣大。

感測方式 怎麼得到距離 會在哪裡騙你
單眼+已知大小 距離 = fy × 真實高度 ÷ 影像高度 大小要先知道;玩具車會被算成很遠的真車
雙眼(stereo) 兩台相機的視差,像人的兩隻眼睛 白牆這類沒有紋理的表面找不到對應點;距離越遠越不準
結構光、飛行時間(ToF)深度相機 自己打紅外光再量回來的訊號 強烈日光、玻璃、黑色或反光表面常常量不到
單眼深度模型 神經網路從一張圖猜 是猜測,尺度不一定可靠,要驗證才能用於安全判斷

已知大小的例子:一張椅子真實高度約 0.8 公尺,fy = 554,影像裡高 148 像素,距離 ≈ 554 × 0.8 ÷ 148 ≈ 3.0 公尺。如果同樣 148 像素的是一張玩具椅(0.2 公尺),實際距離只有 0.75 公尺。

所以機器人常把相機和光達一起用:相機說「那個方向是一張椅子」,光達說「那個方向 2.1 公尺有東西」。兩者的時間和座標系要對齊,就是第 7 課的 TF 要做的事。

曝光、模糊與該不該相信這張圖

相機在快門打開的一小段時間內累積光線,這段時間就是曝光時間,於是出現一個取捨:

  • 曝光長:暗處看得清楚,但機器人或物體一動就模糊。
  • 曝光短:不模糊,但暗;把增益(gain)調高可以變亮,雜訊也跟著放大。

模糊可以直接算。機器人原地轉 1 rad/s,fx = 554,曝光 1/30 秒:

影像移動 ≈ fx × 角速度 × 曝光時間 = 554 × 1 × (1 ÷ 30) ≈ 18 像素

18 像素的模糊足以讓小物體糊掉,就是你剛才把滑桿調到 15 以上看到的情況。所以機器人通常在轉得慢或停下來時,影像的判斷才比較可靠。

其他會騙人的情況:背光(前景全黑)、日光燈或螢幕的閃爍、鏡頭髒了或起霧、鏡子與玻璃(看到的是反射)。還有一種最容易被忽略:相機斷線或卡住,程式還在讀舊畫面。影像的 header.stamp 記錄拍攝時間,拿來判斷資料是不是太舊。

機器人會碰到人,所以規則要反過來想:不是「看不到障礙物就繼續走」,而是影像不可信時,要減速或停下來。例如整張影像的平均亮度太低、太久沒有新影像,都應該被當成感測器失效。第 15 課會實際寫這個判斷。

確認一下

1.

img 是 numpy 陣列,shape 是 (480, 640, 3)。要讀 u=100、v=200 這個像素的值,怎麼寫?

2.

寬 640、視角 60°(fx≈554)的相機,物體在 u=320。方位角是多少?

3.

把鏡頭換成視角更窄的望遠鏡頭(同樣解析度),fx 會怎麼變?

4.

只用一台單眼相機,看到畫面裡一輛車高 100 像素。不知道任何其他資訊,能知道車有多遠嗎?

5.

程式把 bgr8 的影像當成 rgb8 處理,會發生什麼?

第 1 次作答

重點整理

  • 影像是數字陣列。sensor_msgs/Image 的 data 是一維的,像素 (u, v) 在 v × step + u × 3;numpy 寫 img[v, u],先列後行。
  • 方位角 = atan((cx − u) ÷ fx),左正右負。fx 和 cx 來自校正,在 CameraInfo 的 k 裡;真實鏡頭還要處理畸變。
  • 單眼相機給方向、不給距離。距離要靠已知大小、雙眼、深度相機或光達,而它們各有會失準的情況。
  • 曝光和模糊是拍攝當下就決定的:過曝的細節找不回來,轉得快就模糊。
  • 影像不可信(太暗、太舊、斷線)要當成感測器失效,減速或停下來。

下一課讓一個真的模型在你的瀏覽器裡看這些數字,並找出它在哪裡會看錯。

這一課完成了!
Next lesson:物件偵測
你目前沒有登入,學習進度只存在這個瀏覽器。 Login 後,進度會跟著帳號,換電腦也看得到。