目標檢測
圖像中物體的形狀信息是比較明顯和重要的信息 我們可以通過對形狀的識別來實現對物體的檢測
檢測形狀可以確定目標的位置 通過對目標大小位置的處理 我們可以進一步得到圖像中重要的信息
本文 主要有 直線 圓形等圖像的檢測 以及如何檢測圖像中的區域輪廓 擬合輪廓形狀 統計面積
形狀檢測
物體的形狀檢測多基於特殊形狀的特性 opencv中提供了檢測圖像邊緣中是否存在 直線 和 圓形的算法
直線檢測
霍夫變換是圖像處理中檢測是否存在直線的重要算法 霍夫變換可以通過將圖像中的像素從圖像空間
變換到參數空間 使具有相同特性的曲線或者直線映射到參數空間形成峰值 從而將檢測任意形狀的問題
轉化為統計峰值的問題
例如檢測兩個像素所在的直線 需要構建檢測形狀的數學解析式
y = ax+b
y0 = ax0 + b
b = -x0a - y0
代表在一點有無數條直線
而 y0 x0 定值 則 a和b當變量形成一條直線
霍夫變換就是將空間圖形經過一點的全部直線映射為參數空間的一條直線
直線上每個點都對應直角坐標是的一條直線
兩個像素點 的連線 映射到參數空間
是兩條直線的交點
霍夫變換可以得到兩個重要的結論:
圖像空間中的每條直線在參數空間都對應一個點
在圖像空間的直線上的任何一個點在參數空間對應的直線都較於一點
所以利用霍夫變換 在參數空間找到交點 就可以得到直線
但是注意 在直角坐標系中 垂直的直線不能被檢測出來
因為他們在參數空間的線是平行的
為了解決垂直直線的問題 一般采用極坐標來表示空間圖形中的直線
r = xcos + ycos
他的參數空間 變為了 正弦曲線
因為上述的直線檢測是尋找點的問題 由於曲線是連線的 而實際情況下是離散的
所以我們要將參數空間離散化 離散后的每一個格子代表一個直線
然后尋找符合的格子
綜上: 霍夫變換檢測直線:
1. 將參數空間離散化
2. 通過映射關系求取圖像中每個非零像素點在參數空間通過的方格
3. 統計參數空間每個方格出現次數 選取最大的作為表示直線的方格
4. 將根據直線的方格得到圖像中的直線
在參數空間累加的點 映射到直角坐標系 不一定是連續的點
Opencv提供了兩種用於檢測圖像中直線的函數
1.
lines = cv.HoughLines(img,rho,theta,threshold)
以極坐標形式將圖像中 直線參數返回
img: 必須是二值圖,對灰度圖的計算 需要用邊緣檢測得到二值圖
rho theta: 設置離散的精度
一般為1 和 np.pi/180
threshold: 參數空間中的經過次數 大於閾值的將被當為直線
lines: 只能輸出極坐標形式的參數 若需要繪制直線 需要得到直線上的兩點坐標
點擊查看代碼
import cv2
import cv2 as cv
import numpy as np
import sys
import math
def func(x):
pass
def lines_draw(img_1, lines):
img_2 = img_1.copy()
for i in range(0, len(lines)):
r, th = lines[i][0][0], lines[i][0][1]
a = np.cos(th)
b = np.sin(th)
x = a * r
y = b * r
x1 = int(x + 1000 * (-b))
y1 = int(y + 1000 * a)
x2 = int(x - 1000 * (-b))
y2 = int(y - 1000 * a)
cv.line(img_2, (x1, y1), (x2, y2), (255, 255, 255), 2)
return img_2
cv.namedWindow('lines')
cv.createTrackbar('thr', 'lines', 200, 500, func)
if __name__ == '__main__':
img = cv.imread('./img.jpg')
if img is None:
print('圖讀取失敗')
sys.exit()
img = cv.cvtColor(img, cv2.COLOR_BGR2GRAY)
cv.imshow('old', img)
img = cv.GaussianBlur(img, (5, 5), 10, 20)
cv.imshow('Gas', img)
img = cv.Canny(img, 70, 140, apertureSize=3)
img = cv.convertScaleAbs(img)
cv.imshow('pad', img)
while True:
thrs = cv.getTrackbarPos('thr', 'lines')
print(thrs)
lines = cv.HoughLines(img, 1, np.pi / 180, thrs)
img_new = lines_draw(img, lines)
cv.imshow('lines', img_new)
if cv.waitKey(1000) == ord('q'):
break
cv.destroyAllWindows()
缺點:該函數無法獲得具體的線的位置和長度 只能直到是否有符合的線的存在
較大閾值
較小閾值
2. lines = cv.HoughLinesP(img,rho,theta,threshold, minLinelength,maxLineGap)
lines 存儲的是滿足條件的直線或者線段兩個端點的坐標
x1,y1 = lines[i][0][0], lines[i][0][1]
x2,y2 = lines[i][0][1], lines[i][0][3]
上面已經說了 累加不一定是連續的 所以 累加的直線可能比連續的點長
minLinelength : 最短長度 如果直線長度小於這個值 及時是直線也不會被輸出
maxLineGap: 最短距離 若兩個點間的間隙小於這個值 直接連接
lines = cv.HoughLinesP(img, 1, np.pi / 180, thrs, minLineLength=thrs + 50, maxLineGap=5)
對於想要提取斜線的情況 我們應該加大maxLineGap
!:
minLinelength = threashold
maxLineGap = 0
表示全部是連續的點組成的直線或者線段
而設置了這兩個參數 就代表不需要是絕對的連續點組成的直線 增加了檢測的正確率
效果如下:
排除山的影響:
通過測試可以直知道 遠處的山和地平線影響了我們對車道的檢測
所以 可以通過賦值的方法 來將上半部分直接用黑布蓋起來,避免影響
為什么不用感興趣區域: 因為x和y的坐標會發生改變 導致不能正確的畫出直線
img3 = img.copy()
img3[:200, :] = 0
效果如下:
直線判斷:
前面兩個函數都可以檢測圖像中是否存在直線
而實際工程中 我們有時候 得到的是一些點 而非是一張完整的圖像
opencv提供了能在含有坐標的點中 判斷 這些點是否是一個直線
_lines = cv.HoughLinesPointSet(_point,lines_max,threshold,min_rho,max_rho,tho_step,min_theta,max_theta,theta_step)
_point: 點集
lines_max: 最多拿多少直線
threshold: 累加器閾值
min_rho: 直線上兩點最短距離
max_rho: 直線上兩點最長距離
rho_stop: 離散
min_theta: 直線過原點的垂線與x軸最小夾角
max_theta: 直線過原點的垂線與x軸最大夾角
theta_step: 離散
直線擬合:
有時候我們知道一些點在一條線上 我們想要將所有點擬合為一條線
opencv提供了最小二乘函數:
line = cv.fitLine(points,distType,param,reps,aeps)
distTyppe: 使用的距離類型
param: 某些距離類型的數值參數
reps: 坐標原點與擬合直線間的距離精度
aeps: 擬合直線的角度精度
圓形檢測
詳細參照直線檢測 原理相似 利用霍夫變換
circles = cv.HuoghCircles(img,method,dp,minDist,param1,param2,minRadius,maxRadius)
dp: 離散化
method: 當前版本 不能進行選擇 僅有 cv.HOUGH_GRADIENT 方法
dp: 離散化 為1 與原圖一樣 為2 都為原圖一半
circles : 1* N * 3 的數組 [0][0][0]:第0個圓的x [0][0][1]第0個圓的y [0][0][2]r
minDist: 太小會導致 錯誤的多個相鄰圓 太大會導致 遺漏一些圓 兩圓心最小距離
該函數 直接給灰度圖即可 會自動進行邊緣檢測
param1: 傳給Canny邊緣檢測的較大閾值
param2: 檢測圓形的累加器閾值
minRadius: 最小半徑
maxRadius: 最大半徑
點擊查看代碼
import cv2
import cv2 as cv
import numpy as np
import sys
import math
def func(x):
pass
def lines_draw(img_1, cir):
img_2 = img_1.copy()
for i in range(0, len(cir)):
r = int(cir[0][i][2])
x = int(cir[0][i][0])
y = int(cir[0][i][1])
cv.circle(img_2, (x, y), r, (255, 0, 0), 5)
cv.circle(img_2, (x, y), 2, (255, 0, 0), 5)
return img_2
if __name__ == '__main__':
img = cv.imread('./img_5.png')
if img is None:
print('圖讀取失敗')
sys.exit()
img_old = img.copy()
img = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
img = cv.GaussianBlur(img, (5, 5), 10, 20)
img_2 = cv.Canny(img, 50, 150)
cv.imshow('1',img_2)
cirs = cv.HoughCircles(img, cv.HOUGH_GRADIENT, 2, 10, param1=150, param2=300)
print(cirs)
if cirs is not None:
img_new = lines_draw(img_old, cirs)
cv.imshow('new', img_new)
cv.waitKey(0)
cv.destroyAllWindows()
效果如下: