1. 程式人生 > 實用技巧 >opencv學習(三)

opencv學習(三)

1、影象梯度

x,y方向上的梯度

# 影象梯度(由x,y方向上的偏導數和偏移構成),有一階導數(sobel運算元)和二階導數(Laplace運算元)
# 用於求解影象邊緣,一階的極大值,二階的零點
# 一階偏導在影象中為一階差分,再變成運算元(即權值)與影象畫素值乘積相加,二階同理
def sobel_demo(iamge):
    grad_x=cv.Sobel(iamge,cv.CV_32F,1,0)
    grad_y=cv.Sobel(iamge,cv.CV_32F,0,1)
    gradx=cv.convertScaleAbs(grad_x)
    grady=cv.convertScaleAbs(grad_y)
    cv.imshow(
"gradient_x",gradx) cv.imshow("gradient_y",grady)

xy方向相加得最終結果

gradxy=cv.addWeighted(gradx,0.5,grady,0.5,0)

選用更有效的 Scharr運算元

lapalian運算元得出的結果

def lapalian_demo(image):
    dst=cv.Laplacian(image,cv.CV_32F)
    lpls=cv.convertScaleAbs(dst)
    cv.imshow("lapalian_demo",lpls)

自定義lapalian運算元,八領域

def custom_lapalian(image):
    kernel=np.array([[1, 1, 1], [1, -8, 1], [1, 1, 1]])
    dst=cv.filter2D(image,cv.CV_32F,kernel=kernel)
    grady=cv.convertScaleAbs(dst)
    cv.imshow("lapalian_custom",grady)

2、Canny邊緣提取

# canny運算步驟:5步
# 1. 高斯模糊 - GaussianBlur 去噪聲
# 2. 灰度轉換 - cvtColor 多通道變成單通道
# 3. 計算梯度 - Sobel/Scharr
# 4. 非極大值抑制 進行高值的過濾 t1 t2高低閾值
# 5. 高低閾值輸出二值影象

# 非極大值抑制:
# 演算法使用一個3×3鄰域作用在幅值陣列M[i,j]的所有點上;
# 每一個點上,鄰域的中心畫素M[i,j]與沿著梯度線的兩個元素進行比較,
# 其中梯度線是由鄰域的中心點處的扇區值ζ[i,j]給出。
# 如果在鄰域中心點處的幅值M[i,j]不比梯度線方向上的兩個相鄰點幅值大,則M[i,j]賦值為零,否則維持原值;
# 此過程可以把M[i,j]寬屋脊帶細化成只有一個畫素點寬,即保留屋脊的高度值。

# 高低閾值連線
# T1,T2為閾值,凡是高於T2的都保留,凡是低於T1的都丟棄
# 從高於T2的畫素出發,凡是大於T1而且相互連線的都保留。最終得到一個輸出二值影象
# 推薦高低閾值比值為T2:T1 = 3:1/2:1,其中T2高閾值,T1低閾值

Canny邊緣檢測 彩色和黑白

def edge_demo(image):
    blurred=cv.GaussianBlur(image,(3,3),0)
    gray=cv.cvtColor(blurred,cv.COLOR_BGR2GRAY)

    grad_x=cv.Sobel(gray,cv.CV_16SC1,1,0)
    grad_y=cv.Sobel(gray,cv.CV_16SC1,0,1)

    edge_output=cv.Canny(grad_x,grad_y,50,150)
    cv.imshow("gray",gray)
    cv.imshow("Canny demo",edge_output)
    dst=cv.bitwise_and(image,image,mask=edge_output)
    cv.imshow("color edge",dst)
edge_output=cv.Canny(gray,50,150)直接呼叫也可以


3、直線檢測,霍夫直線變換

另一方面,也可以寫成關於(k,q)的函式表示式(霍夫空間):

對應的變換可以通過圖形直觀表示:

變換後的空間成為霍夫空間。即:笛卡爾座標系中一條直線,對應霍夫空間的一個點。

第二種,直接給定x1x2等

def line_detect_possible_demo(image):
    gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY)
    edges = cv.Canny(gray, 50, 150, apertureSize=3)
    lines = cv.HoughLinesP(edges, 1, np.pi / 180, 200,minLineLength=50,maxLineGap=10)
    for line in lines:
        print(type(line))
        x1,y1,x2,y2=line[0]
        cv.line(image,(x1,y1),(x2,y2),(0,0,255),2)
    cv.imshow("line_detect_possible_demo",image)

圓檢測:

還不熟悉引數的具體用法,調了很多次要不就是少了要補多了

後來發現,霍夫圓檢測是對噪聲敏感的演算法,所以將均值遷移的屬性值更改,最終達到目的

dst=cv.pyrMeanShiftFiltering(image,15,100)
# 均值遷移,sp,sr為空間域核與畫素範圍域核半徑
def detection_circles_demo(image):
    dst=cv.pyrMeanShiftFiltering(image,10,100)
    # 均值遷移,sp,sr為空間域核與畫素範圍域核半徑
    gray=cv.cvtColor(dst,cv.COLOR_BGR2GRAY)
    circles=cv.HoughCircles(gray,cv.HOUGH_GRADIENT,1,20,param1=40,param2=27,minRadius=0,maxRadius=0)
    """
    .   @param image 8-bit, single-channel, grayscale input image.
    .   @param circles Output vector of found circles. Each vector is encoded as  3 or 4 element
    .   floating-point vector \f$(x, y, radius)\f$ or \f$(x, y, radius, votes)\f$ .
    .   @param method Detection method, see #HoughModes. Currently, the only implemented method is #HOUGH_GRADIENT
    .   @param dp Inverse ratio of the accumulator resolution to the image resolution. For example, if
    .   dp=1 , the accumulator has the same resolution as the input image. If dp=2 , the accumulator has
    .   half as big width and height.
        累加器影象的解析度。這個引數允許建立一個比輸入影象解析度低的累加器。
        (這樣做是因為有理由認為影象中存在的圓會自然降低到與影象寬高相同數量的範疇)。
        如果dp設定為1,則解析度是相同的;如果設定為更大的值(比如2),累加器的解析度受此影響會變小(此情況下為一半)。
        dp的值不能比1小。
    .   @param minDist Minimum distance between the centers of the detected circles. If the parameter is
    .   too small, multiple neighbor circles may be falsely detected in addition to a true one. If it is
    .   too large, some circles may be missed.
        該引數是讓演算法能明顯區分的兩個不同圓之間的最小距離。
    .   @param param1 First method-specific parameter. In case of #HOUGH_GRADIENT , it is the higher
    .   threshold of the two passed to the Canny edge detector (the lower one is twice smaller).
        用於Canny的邊緣閥值上限,下限被置為上限的一半。
    .   @param param2 Second method-specific parameter. In case of #HOUGH_GRADIENT , it is the
    .   accumulator threshold for the circle centers at the detection stage. The smaller it is, the more
    .   false circles may be detected. Circles, corresponding to the larger accumulator values, will be
    .   returned first.
        累加器的閥值。
    .   @param minRadius Minimum circle radius.
        最小圓半徑
    .   @param maxRadius Maximum circle radius. If <= 0, uses the maximum image dimension. If < 0, returns
    .   centers without finding the radius.
        最大圓半徑。
    """
    circles=np.uint16(np.around(circles))
    print(circles.shape)
    for i in circles[0,:]:
        cv.circle(image,(i[0],i[1]),i[2],(0,255,0),2)
        cv.circle(image,(i[0],i[1]),2,(0,0,255),3)
    cv.imshow("detection_circles",image)


4、輪廓發現

"""
. 輪廓可以簡單認為成將連續的點(連著邊界)連在一起的曲線,具有相同 的顏色或者灰度。
輪廓在形狀分析和物體的檢測和識別中很有用。
. 為了更加準確,要使用二值化影象。在尋找輪廓之前,要進行閾值化處理或者 Canny 邊界檢測
. 查詢輪廓的函式會修改原始影象。如果你在找到輪廓之後還想使用原始影象的話,
你應該將原始影象儲存到其他變數中.
. 在 OpenCV 中,查詢輪廓就像在黑色背景中超白色物體。要找的物體應該是白色而背景應該是黑色。
"""
def contours_demo(image):
    dst=cv.GaussianBlur(image,(3,3),0)
    gray=cv.cvtColor(dst,cv.COLOR_BGR2GRAY)
    ret,binary=cv.threshold(gray,0,255,cv.THRESH_OTSU|cv.THRESH_BINARY)
    cv.imshow("binary image",binary)


    contours,heriachy=cv.findContours(binary,cv.RETR_EXTERNAL,cv.CHAIN_APPROX_SIMPLE)
    """
    • 函式 cv2.findContours() 有三個引數, 第一個是輸入影象,第二個是輪廓檢索模式,第三個是輪廓近似方法。
    • 檢索模式:
        • CV_RETR_EXTERNAL - 只提取外層的輪廓  
        • CV_RETR_LIST - 提取所有輪廓,並且放置在 list 中  
        • CV_RETR_CCOMP - 提取所有輪廓,並且將其組織為兩層的 hierarchy: 
            頂層為連通域的 外圍邊界,次層為洞的內層邊界。  
        • CV_RETR_TREE - 提取所有輪廓,並且重構巢狀輪廓的全部 hierarchy
    • 逼近方法 (對所有節點, 不包括使用內部逼近的 CV_RETR_RUNS).  點的存貯情況,是不是都被存貯
        • CV_CHAIN_CODE - Freeman 鏈碼的輸出輪廓. 其它方法輸出多邊形(定點序列).  
        • CV_CHAIN_APPROX_NONE - 將所有點由鏈碼形式翻譯為點序列形式  
        • CV_CHAIN_APPROX_SIMPLE - 壓縮水平、垂直和對角分割,即函式只保留末端的象素 點;  
        • CV_CHAIN_APPROX_TC89_L1,  CV_CHAIN_APPROX_TC89_KCOS - 應用 Teh-Chin 鏈逼近演算法.  
        • CV_LINK_RUNS - 通過連線為 1 的水平碎片使用完全不同的輪廓提取演算法。僅有 CV_RETR_LIST 提取模式可以在本方法中應用.  
    • offset:每一個輪廓點的偏移量. 當輪廓是從影象 ROI 中提取出來的時候,使用偏移量有用,因為可以從整個影象上下文來對輪廓做分析. 
    • 返回值有三個,第一個是輪廓,個是影象,第二第三個是(輪廓的)層析結構。
        輪廓(第二個返回值)是一個 Python 列表,其中儲存這影象中的所有輪廓。
        每一個輪廓都是一個 Numpy 陣列,包含物件邊界點(x,y)的座標。
    """
    for i,contour in enumerate(contours):
        # 函式 cv2.drawContours() 可以被用來繪製輪廓。它可以根據你提供的邊界點繪製任何形狀。
        # 它的第一個引數是原始影象,第二個引數是輪廓,一個 Python 列表。
        # 第三個引數是輪廓的索引(在繪製獨立輪廓是很有用,當設 置為 -1 時繪製所有輪廓)。
        # 接下來的引數是輪廓的顏色和厚度等。
        cv.drawContours(image,contours,i,(0,0,255),-1)
        print(i)
    cv.imshow("detect contours",image)

利用canny檢測

def edge_demo(image):
    blurred = cv.GaussianBlur(image, (3, 3), 0)
    gray = cv.cvtColor(blurred, cv.COLOR_BGR2GRAY)

    grad_x = cv.Sobel(gray, cv.CV_16SC1, 1, 0)
    grad_y = cv.Sobel(gray, cv.CV_16SC1, 0, 1)

    # edge_output = cv.Canny(grad_x, grad_y, 30, 150)
    edge_output = cv.Canny(gray, 50, 150)
    return edge_output

5、物件測量

def measure_demo(image):
    gray=cv.cvtColor(image,cv.COLOR_BGR2GRAY)
    ret,binary=cv.threshold(gray,0,255,cv.THRESH_BINARY_INV|cv.THRESH_OTSU)
    print("threshold value:%s"%ret)
    cv.imshow("binary image",binary)

    contours,hierarchy=cv.findContours(binary,cv.RETR_EXTERNAL,cv.CHAIN_APPROX_SIMPLE)
    for i,contour in enumerate(contours):
        area=cv.contourArea(contour)
        x,y,w,h=cv.boundingRect(contour)
        mm=cv.moments(contour)# 函式 cv2.moments() 會將計算得到的矩以一個字典的形式返回
        # 計算出物件的重心
        type(mm)
        if mm['m00']==0:
            continue
        else:
            cx = mm['m10'] / mm['m00']
            cy = mm['m01'] / mm['m00']

        cv.circle(image,(np.int(cx),np.int(cy)),2,(0,255,255),3)
        cv.rectangle(image,(x,y),(x+w,y+h),(0,0,255),2)
    cv.imshow("measure_contours",image)

6、腐蝕膨脹

def erode_demo(image):
    print(image.shape)
    gray=cv.cvtColor(image,cv.COLOR_BGR2GRAY)
    ret ,binary=cv.threshold(gray,0,255,cv.THRESH_BINARY|cv.THRESH_OTSU)
    cv.imshow("binary",binary)

    kernel=cv.getStructuringElement(cv.MORPH_RECT,(5,5))
    dst=cv.erode(binary,kernel)
    cv.imshow("erode_demo",dst)
def dilate_demo(image):
    print(image.shape)
    gray=cv.cvtColor(image,cv.COLOR_BGR2GRAY)
    ret ,binary=cv.threshold(gray,0,255,cv.THRESH_BINARY|cv.THRESH_OTSU)
    cv.imshow("binary",binary)

    kernel=cv.getStructuringElement(cv.MORPH_RECT,(5,5))
    dst=cv.dilate(binary,kernel)
    cv.imshow("dilate_demo",dst)

就像土壤侵蝕一樣,這個操作會把前景物體的邊界腐蝕掉(但是前景仍然 是白色)。
卷積核沿著影象滑動,如果與卷積核對應的原影象的所有畫素值都是 1,
那麼中心元素就保持原來的畫素值,否則就變為零。
這回產生什麼影響呢?根據卷積核的大小靠近前景的所有畫素都會被腐蝕掉(變為 0),
所以前景物體會變小,整幅影象的白色區域會減少。
這對於去除白噪聲很有用,也可以用來斷開兩個連在一塊的物體等。

與腐蝕相反,與卷積核對應的原影象的畫素值中只要有一個是 1,中心元 素的畫素值就是 1。
所以這個操作會增加影象中的白色區域(前景)。一般在去 噪聲時先用腐蝕再用膨脹。
因為腐蝕在去掉白噪聲的同時,也會使前景物件變 小。所以我們再對他進行膨脹。
這時噪聲已經被去除了,不會再回來了,但是 前景還在並會增加。
膨脹也可以用來連線兩個分開的物體。