感興趣區域(Region of Interest, ROI)是一個核心概念,它允許我們專注於圖像的特定子集進行分析或操作。在 Python 環境下,由於 OpenCV 將圖像表示為NumPy 數組(numpy.ndarray),ROI 的實現和操作完全依賴於 NumPy 強大的切片和索引機制。
基本定義
ROI 是通過指定圖像的行(高度)和列(寬度)範圍來確定的。
座標約定
OpenCV/NumPy 中圖像的座標系統遵循以下約定:
- 原點: (0,0) 位於圖像的左上角。
- 軸向:
- **行(Row)**對應 Y 軸,向下遞增(控制圖像高度)。
- **列(Column)**對應 X 軸,向右遞增(控制圖像寬度)。
- 索引順序: 在 NumPy 切片中,順序始終是 Row→Column(即 Y→X)。
切片語法
使用標準的 NumPy 切片語法定義 ROI:
這裏的範圍是前閉後開區間:
- ystart 到 yend−1 行。
- xstart 到 xend−1 列。
示例: 要提取左上角點 (100,50),右下角點 (300,250) 的區域:
# 左上角 (x=100, y=50),右下角 (x=300, y=250)
x1, y1 = 100, 50
x2, y2 = 300, 250
roi = image[y1:y2, x1:x2]
視圖與副本
這是使用 NumPy 切片定義 ROI 時最重要的概念,直接影響數據處理的安全性和效率。
視圖(View)—— 默認行為
默認情況下,通過切片操作獲得的 roi 變量,並不是原始圖像的一個獨立副本,而是一個指向原始圖像內存區域的視圖(View)或引用。
- 優勢: 極高的性能和內存效率,因為沒有發生數據複製。
- 後果: 對
roi數組的任何修改(例如改變像素值),都會同步反映到原始圖像image
用途: 當需要直接在圖像的特定區域上進行原地修改時,使用視圖非常方便。
副本(Copy)—— 安全操作
如果希望對 ROI 進行修改,但同時保留原始圖像不變,就必須顯式地創建一個副本:
roi_copy = image[y1:y2, x1:x2].copy()
此時,對 roi_copy 的任何操作都不會影響原始圖像 image。
ROI 的操作與應用
區域賦值與修改
可以直接對 ROI 進行賦值操作,實現局部修改。這比循環遍歷像素要快得多,因為它利用了 NumPy 的底層優化。
- 設置顏色:
image[y1:y2, x1:x2] = [B, G, R] - 粘貼圖像: 將一個 ROI 區域複製並粘貼到另一個區域,實現對象克隆或移動:
ball_roi = image[280:340, 330:390] # 提取球的 ROI
image[273:333, 100:160] = ball_roi # 將球粘貼到新位置
性能優化與聚焦處理
在許多計算機視覺任務中(如人臉識別、目標跟蹤),通常只需要處理圖像的一個小區域。使用 ROI 可以極大地提高性能:
- 減少計算量: 算法只需在較小的 ROI 矩陣上運行,而不是整個圖像。
- 提高準確性: 將算法聚焦在特定對象或特徵上,避免背景干擾。
OpenCV 的輔助函數:cv2.selectROI()
彈出一個窗口,允許用户通過鼠標拖動選擇一個矩形區域。
函數定義
retval = cv2.selectROI(windowName, img, showCrosshair=None, fromCenter=None)
參數説明:
windowName: (可選)顯示圖像的窗口名稱。如果未指定,它會創建一個默認窗口。img: 要選擇 ROI 的原始圖像(NumPy 數組)。showCrosshair: (可選,默認為True)如果為True,在選擇矩形中心顯示十字線。fromCenter: (可選,默認為False)如果為True,用户從中心點拖動來繪製矩形;如果為False,用户從左上角拖動到右下角繪製矩形。
返回值 retval:
- 成功選擇: 返回一個包含矩形邊界框信息的元組:
(x, y, w, h)
x, y: ROI 矩形左上角的座標(列索引,行索引)。w: 寬度 (Width)。h: 高度 (Height)。
- 未選擇或取消: 如果用户按
c鍵或窗口關閉,返回值可能是一個包含四個零的元組(0, 0, 0, 0)。
示例
加載一個圖像,使用 cv2.selectROI() 讓用户選擇一個區域,然後使用 NumPy 切片將該區域裁剪並顯示出來。
import cv2
import numpy as np
# 1. 準備圖像 (使用純色矩陣模擬加載圖像)
# 實際應用中,請替換為 cv2.imread('your_image_path.jpg')
try:
# 嘗試加載一個不存在的文件,如果失敗,則創建一個示例圖像
img = cv2.imread('test_image.jpg')
if img is None:
raise FileNotFoundError
except FileNotFoundError:
print("未找到 'test_image.jpg',已創建示例圖像。")
# 創建一個 400x600 的藍色圖像作為示例
img = np.zeros((400, 600, 3), dtype=np.uint8)
img[:, :] = (255, 100, 0) # 設置為淺藍色
# 在圖像中央畫一個綠色矩形
img[150:250, 250:350] = (0, 255, 0)
# 設置窗口名稱
WINDOW_NAME = "Select ROI - Press ENTER or SPACE to confirm"
# 2. 調用 cv2.selectROI()
# 顯示窗口並等待用户選擇一個矩形區域
# 提示: 在選擇完成後,請按 ENTER 或 SPACE 鍵確認。按 C 鍵取消。
roi_rect = cv2.selectROI(
WINDOW_NAME,
img,
showCrosshair=True,
fromCenter=False
)
# 關閉 selectROI 窗口
cv2.destroyWindow(WINDOW_NAME)
# 3. 解析返回值並裁剪圖像
# roi_rect 是 (x, y, w, h)
x, y, w, h = roi_rect
print(f"ROI 矩形座標 (x, y, w, h): {roi_rect}")
if w > 0 and h > 0:
# 使用 NumPy 切片裁剪圖像。
# 注意:NumPy 切片的順序是 [y_start:y_end, x_start:x_end]
# y_start = y; y_end = y + h
# x_start = x; x_end = x + w
cropped_img = img[int(y):int(y+h), int(x):int(x+w)]
# 4. 顯示裁剪結果
cv2.imshow("Cropped ROI Image", cropped_img)
cv2.waitKey(0)
else:
print("未選擇有效的 ROI 區域或用户已取消。")
cv2.destroyAllWindows()