日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

OpenCV之imgproc 模块. 图像处理(1)图像平滑处理 腐蚀与膨胀(Eroding and Dilating) 更多形态学变换 图像金字塔 基本的阈值操作

發(fā)布時(shí)間:2025/3/21 编程问答 61 豆豆
生活随笔 收集整理的這篇文章主要介紹了 OpenCV之imgproc 模块. 图像处理(1)图像平滑处理 腐蚀与膨胀(Eroding and Dilating) 更多形态学变换 图像金字塔 基本的阈值操作 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

圖像平滑處理

目標(biāo)

本教程教您怎樣使用各種線性濾波器對(duì)圖像進(jìn)行平滑處理,相關(guān)OpenCV函數(shù)如下:

  • blur
  • GaussianBlur
  • medianBlur
  • bilateralFilter

原理

Note

?

以下原理來源于Richard Szeliski 的著作?Computer Vision: Algorithms and Applications?以及?Learning OpenCV

  • 平滑?也稱?模糊, 是一項(xiàng)簡(jiǎn)單且使用頻率很高的圖像處理方法。

  • 平滑處理的用途有很多, 但是在本教程中我們僅僅關(guān)注它減少噪聲的功用 (其他用途在以后的教程中會(huì)接觸到)。

  • 平滑處理時(shí)需要用到一個(gè)?濾波器?。 最常用的濾波器是?線性?濾波器,線性濾波處理的輸出像素值 (i.e.?) 是輸入像素值 (i.e.?)的加權(quán)和 :

    ?稱為?核, 它僅僅是一個(gè)加權(quán)系數(shù)。

    不妨把?濾波器?想象成一個(gè)包含加權(quán)系數(shù)的窗口,當(dāng)使用這個(gè)濾波器平滑處理圖像時(shí),就把這個(gè)窗口滑過圖像。

  • 濾波器的種類有很多, 這里僅僅提及最常用的:

歸一化塊濾波器 (Normalized Box Filter)

  • 最簡(jiǎn)單的濾波器, 輸出像素值是核窗口內(nèi)像素值的?均值?( 所有像素加權(quán)系數(shù)相等)

  • 核如下:

高斯濾波器 (Gaussian Filter)

  • 最有用的濾波器 (盡管不是最快的)。 高斯濾波是將輸入數(shù)組的每一個(gè)像素點(diǎn)與?高斯內(nèi)核?卷積將卷積和當(dāng)作輸出像素值。

  • 還記得1維高斯函數(shù)的樣子嗎?

    假設(shè)圖像是1維的,那么觀察上圖,不難發(fā)現(xiàn)中間像素的加權(quán)系數(shù)是最大的, 周邊像素的加權(quán)系數(shù)隨著它們遠(yuǎn)離中間像素的距離增大而逐漸減小。

Note

?

2維高斯函數(shù)可以表達(dá)為 :

其中??為均值 (峰值對(duì)應(yīng)位置),??代表標(biāo)準(zhǔn)差 (變量??和 變量??各有一個(gè)均值,也各有一個(gè)標(biāo)準(zhǔn)差)

中值濾波器 (Median Filter)

中值濾波將圖像的每個(gè)像素用鄰域 (以當(dāng)前像素為中心的正方形區(qū)域)像素的?中值?代替 。

雙邊濾波 (Bilateral Filter)

  • 目前我們了解的濾波器都是為了?平滑?圖像, 問題是有些時(shí)候這些濾波器不僅僅削弱了噪聲, 連帶著把邊緣也給磨掉了。 為避免這樣的情形 (至少在一定程度上 ), 我們可以使用雙邊濾波。
  • 類似于高斯濾波器,雙邊濾波器也給每一個(gè)鄰域像素分配一個(gè)加權(quán)系數(shù)。 這些加權(quán)系數(shù)包含兩個(gè)部分, 第一部分加權(quán)方式與高斯濾波一樣,第二部分的權(quán)重則取決于該鄰域像素與當(dāng)前像素的灰度差值。
  • 詳細(xì)的解釋可以查看?鏈接

源碼

  • 本程序做什么?

    • 裝載一張圖像
    • 使用4種不同濾波器 (見原理部分) 并顯示平滑圖像
  • 下載代碼: 點(diǎn)擊?這里

  • 代碼一瞥:

#include "opencv2/imgproc/imgproc.hpp" #include "opencv2/highgui/highgui.hpp"using namespace std; using namespace cv;/// 全局變量 int DELAY_CAPTION = 1500; int DELAY_BLUR = 100; int MAX_KERNEL_LENGTH = 31;Mat src; Mat dst; char window_name[] = "Filter Demo 1";/// 函數(shù)申明 int display_caption( char* caption ); int display_dst( int delay );/** * main 函數(shù) */int main( int argc, char** argv ){namedWindow( window_name, CV_WINDOW_AUTOSIZE );/// 載入原圖像src = imread( "../images/lena.jpg", 1 );if( display_caption( "Original Image" ) != 0 ) { return 0; }dst = src.clone();if( display_dst( DELAY_CAPTION ) != 0 ) { return 0; }/// 使用 均值平滑if( display_caption( "Homogeneous Blur" ) != 0 ) { return 0; }for ( int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2 ){ blur( src, dst, Size( i, i ), Point(-1,-1) );if( display_dst( DELAY_BLUR ) != 0 ) { return 0; } }/// 使用高斯平滑if( display_caption( "Gaussian Blur" ) != 0 ) { return 0; }for ( int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2 ){ GaussianBlur( src, dst, Size( i, i ), 0, 0 );if( display_dst( DELAY_BLUR ) != 0 ) { return 0; } }/// 使用中值平滑if( display_caption( "Median Blur" ) != 0 ) { return 0; }for ( int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2 ){ medianBlur ( src, dst, i );if( display_dst( DELAY_BLUR ) != 0 ) { return 0; } }/// 使用雙邊平滑if( display_caption( "Bilateral Blur" ) != 0 ) { return 0; }for ( int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2 ){ bilateralFilter ( src, dst, i, i*2, i/2 );if( display_dst( DELAY_BLUR ) != 0 ) { return 0; } }/// 等待用戶輸入display_caption( "End: Press a key!" );waitKey(0);return 0;}int display_caption( char* caption ){dst = Mat::zeros( src.size(), src.type() );putText( dst, caption,Point( src.cols/4, src.rows/2),CV_FONT_HERSHEY_COMPLEX, 1, Scalar(255, 255, 255) );imshow( window_name, dst );int c = waitKey( DELAY_CAPTION );if( c >= 0 ) { return -1; }return 0;}int display_dst( int delay ){imshow( window_name, dst );int c = waitKey ( delay );if( c >= 0 ) { return -1; }return 0;}

解釋

  • 下面看一看有關(guān)平滑的OpenCV函數(shù),其余部分大家已經(jīng)很熟了。

  • 歸一化塊濾波器:

    OpenCV函數(shù)?blur?執(zhí)行了歸一化塊平滑操作。

    for ( int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2 ){ blur( src, dst, Size( i, i ), Point(-1,-1) );if( display_dst( DELAY_BLUR ) != 0 ) { return 0; } }

    我們輸入4個(gè)實(shí)參 (詳細(xì)的解釋請(qǐng)參考 Reference):

    • src: 輸入圖像
    • dst: 輸出圖像
    • Size( w,h ): 定義內(nèi)核大小(?w?像素寬度,?h?像素高度)
    • Point(-1, -1): 指定錨點(diǎn)位置(被平滑點(diǎn)), 如果是負(fù)值,取核的中心為錨點(diǎn)。
  • 高斯濾波器:

    OpenCV函數(shù)?GaussianBlur?執(zhí)行高斯平滑 :

    for ( int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2 ){ GaussianBlur( src, dst, Size( i, i ), 0, 0 );if( display_dst( DELAY_BLUR ) != 0 ) { return 0; } }
  • 我們輸入4個(gè)實(shí)參 (詳細(xì)的解釋請(qǐng)參考 Reference):

    • src: 輸入圖像
    • dst: 輸出圖像
    • Size(w, h): 定義內(nèi)核的大小(需要考慮的鄰域范圍)。??和??必須是正奇數(shù),否則將使用??和?參數(shù)來計(jì)算內(nèi)核大小。
    • : x 方向標(biāo)準(zhǔn)方差, 如果是??則??使用內(nèi)核大小計(jì)算得到。
    • : y 方向標(biāo)準(zhǔn)方差, 如果是??則??使用內(nèi)核大小計(jì)算得到。.
  • 中值濾波器:

    OpenCV函數(shù)?medianBlur?執(zhí)行中值濾波操作:

    for ( int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2 ){ medianBlur ( src, dst, i );if( display_dst( DELAY_BLUR ) != 0 ) { return 0; } }

    我們用了3個(gè)參數(shù):

    • src: 輸入圖像
    • dst: 輸出圖像, 必須與?src?相同類型
    • i: 內(nèi)核大小 (只需一個(gè)值,因?yàn)槲覀兪褂谜叫未翱?,必須為奇數(shù)。
  • 雙邊濾波器

    OpenCV函數(shù)?bilateralFilter?執(zhí)行雙邊濾波操作:

    for ( int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2 ){ bilateralFilter ( src, dst, i, i*2, i/2 );if( display_dst( DELAY_BLUR ) != 0 ) { return 0; } }

    我們使用了5個(gè)參數(shù):

    • src: 輸入圖像
    • dst: 輸出圖像
    • d: 像素的鄰域直徑
    • : 顏色空間的標(biāo)準(zhǔn)方差
    • : 坐標(biāo)空間的標(biāo)準(zhǔn)方差(像素單位)
  • 結(jié)果

    • 程序顯示了原始圖像(?lena.jpg) 和使用4種濾波器之后的效果圖。

    • 這里顯示的是使用?中值濾波?之后的效果圖:




    腐蝕與膨脹(Eroding and Dilating)

    目標(biāo)

    本文檔嘗試解答如下問題:

    • 如何使用OpenCV提供的兩種最基本的形態(tài)學(xué)操作,腐蝕與膨脹( Erosion 與 Dilation):
      • erode
      • dilate

    原理

    Note

    ?

    以下內(nèi)容來自于Bradski和Kaehler的大作:?Learning OpenCV?.

    形態(tài)學(xué)操作

    • 簡(jiǎn)單來講,形態(tài)學(xué)操作就是基于形狀的一系列圖像處理操作。通過將?結(jié)構(gòu)元素?作用于輸入圖像來產(chǎn)生輸出圖像。

    • 最基本的形態(tài)學(xué)操作有二:腐蝕與膨脹(Erosion 與 Dilation)。 他們的運(yùn)用廣泛:

      • 消除噪聲
      • 分割(isolate)獨(dú)立的圖像元素,以及連接(join)相鄰的元素。
      • 尋找圖像中的明顯的極大值區(qū)域或極小值區(qū)域。
    • 通過以下圖像,我們簡(jiǎn)要來討論一下膨脹與腐蝕操作(譯者注:注意這張圖像中的字母為黑色,背景為白色,而不是一般意義的背景為黑色,前景為白色):

    膨脹

    • 此操作將圖像??與任意形狀的內(nèi)核 (),通常為正方形或圓形,進(jìn)行卷積。

    • 內(nèi)核??有一個(gè)可定義的?錨點(diǎn), 通常定義為內(nèi)核中心點(diǎn)。

    • 進(jìn)行膨脹操作時(shí),將內(nèi)核??劃過圖像,將內(nèi)核??覆蓋區(qū)域的最大相素值提取,并代替錨點(diǎn)位置的相素。顯然,這一最大化操作將會(huì)導(dǎo)致圖像中的亮區(qū)開始”擴(kuò)展” (因此有了術(shù)語膨脹?dilation?)。對(duì)上圖采用膨脹操作我們得到:

    背景(白色)膨脹,而黑色字母縮小了。

    腐蝕

    • 腐蝕在形態(tài)學(xué)操作家族里是膨脹操作的孿生姐妹。它提取的是內(nèi)核覆蓋下的相素最小值。

    • 進(jìn)行腐蝕操作時(shí),將內(nèi)核??劃過圖像,將內(nèi)核??覆蓋區(qū)域的最小相素值提取,并代替錨點(diǎn)位置的相素。

    • 以與膨脹相同的圖像作為樣本,我們使用腐蝕操作。從下面的結(jié)果圖我們看到亮區(qū)(背景)變細(xì),而黑色區(qū)域(字母)則變大了。

    源碼

    下面是本教程的源碼, 你也可以從?here?下載。

    #include "opencv2/imgproc/imgproc.hpp" #include "opencv2/highgui/highgui.hpp" #include "highgui.h" #include <stdlib.h> #include <stdio.h>using namespace cv;/// 全局變量 Mat src, erosion_dst, dilation_dst;int erosion_elem = 0; int erosion_size = 0; int dilation_elem = 0; int dilation_size = 0; int const max_elem = 2; int const max_kernel_size = 21;/** Function Headers */ void Erosion( int, void* ); void Dilation( int, void* );/** @function main */ int main( int argc, char** argv ) {/// Load 圖像src = imread( argv[1] );if( !src.data ){ return -1; }/// 創(chuàng)建顯示窗口namedWindow( "Erosion Demo", CV_WINDOW_AUTOSIZE );namedWindow( "Dilation Demo", CV_WINDOW_AUTOSIZE );cvMoveWindow( "Dilation Demo", src.cols, 0 );/// 創(chuàng)建腐蝕 TrackbarcreateTrackbar( "Element:\n 0: Rect \n 1: Cross \n 2: Ellipse", "Erosion Demo",&erosion_elem, max_elem,Erosion );createTrackbar( "Kernel size:\n 2n +1", "Erosion Demo",&erosion_size, max_kernel_size,Erosion );/// 創(chuàng)建膨脹 TrackbarcreateTrackbar( "Element:\n 0: Rect \n 1: Cross \n 2: Ellipse", "Dilation Demo",&dilation_elem, max_elem,Dilation );createTrackbar( "Kernel size:\n 2n +1", "Dilation Demo",&dilation_size, max_kernel_size,Dilation );/// Default startErosion( 0, 0 );Dilation( 0, 0 );waitKey(0);return 0; }/** @function Erosion */ void Erosion( int, void* ) {int erosion_type;if( erosion_elem == 0 ){ erosion_type = MORPH_RECT; }else if( erosion_elem == 1 ){ erosion_type = MORPH_CROSS; }else if( erosion_elem == 2) { erosion_type = MORPH_ELLIPSE; }Mat element = getStructuringElement( erosion_type,Size( 2*erosion_size + 1, 2*erosion_size+1 ),Point( erosion_size, erosion_size ) );/// 腐蝕操作erode( src, erosion_dst, element );imshow( "Erosion Demo", erosion_dst ); }/** @function Dilation */ void Dilation( int, void* ) {int dilation_type;if( dilation_elem == 0 ){ dilation_type = MORPH_RECT; }else if( dilation_elem == 1 ){ dilation_type = MORPH_CROSS; }else if( dilation_elem == 2) { dilation_type = MORPH_ELLIPSE; }Mat element = getStructuringElement( dilation_type,Size( 2*dilation_size + 1, 2*dilation_size+1 ),Point( dilation_size, dilation_size ) );///膨脹操作dilate( src, dilation_dst, element );imshow( "Dilation Demo", dilation_dst ); }

    解釋

  • 大部分代碼應(yīng)該不需要解釋了 (如果有任何疑問,請(qǐng)回頭參考前面的教程)。 讓我們來回顧一下本程序的總體流程:

    • 裝載圖像 (可以是 RGB圖像或者灰度圖 )
    • 創(chuàng)建兩個(gè)顯示窗口 (一個(gè)用于膨脹輸出,一個(gè)用于腐蝕輸出)
    • 為每個(gè)操作創(chuàng)建兩個(gè) Trackbars:
      • 第一個(gè) trackbar “Element” 返回?erosion_elem?或者?dilation_elem
      • 第二個(gè) trackbar “Kernel size” 返回?erosion_size?或者?dilation_size?。
    • 每次移動(dòng)標(biāo)尺, 用戶函數(shù)?Erosion?或者?Dilation?就會(huì)被調(diào)用,函數(shù)將根據(jù)當(dāng)前的trackbar位置更新輸出圖像。

    讓我們分析一下這兩個(gè)函數(shù):

  • Erosion:

    /** @function Erosion */ void Erosion( int, void* ) {int erosion_type;if( erosion_elem == 0 ){ erosion_type = MORPH_RECT; }else if( erosion_elem == 1 ){ erosion_type = MORPH_CROSS; }else if( erosion_elem == 2) { erosion_type = MORPH_ELLIPSE; }Mat element = getStructuringElement( erosion_type,Size( 2*erosion_size + 1, 2*erosion_size+1 ),Point( erosion_size, erosion_size ) );/// 腐蝕操作erode( src, erosion_dst, element );imshow( "Erosion Demo", erosion_dst ); }
    • 進(jìn)行?腐蝕?操作的函數(shù)是?erode?。 它接受了三個(gè)參數(shù):

      • src: 原圖像

      • erosion_dst: 輸出圖像

      • element: 腐蝕操作的內(nèi)核。 如果不指定,默認(rèn)為一個(gè)簡(jiǎn)單的??矩陣。否則,我們就要明確指定它的形狀,可以使用函數(shù)?getStructuringElement:

        Mat element = getStructuringElement( erosion_type,Size( 2*erosion_size + 1, 2*erosion_size+1 ),Point( erosion_size, erosion_size ) );

      我們可以為我們的內(nèi)核選擇三種形狀之一:

      • 矩形: MORPH_RECT
      • 交叉形: MORPH_CROSS
      • 橢圓形: MORPH_ELLIPSE

      然后,我們還需要指定內(nèi)核大小,以及?錨點(diǎn)?位置。不指定錨點(diǎn)位置,則默認(rèn)錨點(diǎn)在內(nèi)核中心位置。

    • 就這些了,我們現(xiàn)在可以對(duì)圖像進(jìn)行腐蝕操作了。

    Note

    ?

    OpenCV的?erode?函數(shù)還有另外的參數(shù),其中一個(gè)參數(shù)允許你一下對(duì)圖像進(jìn)行多次腐蝕操作。在這個(gè)簡(jiǎn)單的文檔中沒有用到它,但是你可以參考OpenCV的使用手冊(cè)。

  • Dilation:

  • 下面是膨脹的代碼,你可以看到,它和?Erosion?函數(shù)是多么相似。 這里我們同樣可以指定內(nèi)核的形狀,錨點(diǎn)和大小。

    /** @function Dilation */ void Dilation( int, void* ) {int dilation_type;if( dilation_elem == 0 ){ dilation_type = MORPH_RECT; }else if( dilation_elem == 1 ){ dilation_type = MORPH_CROSS; }else if( dilation_elem == 2) { dilation_type = MORPH_ELLIPSE; }Mat element = getStructuringElement( dilation_type,Size( 2*dilation_size + 1, 2*dilation_size+1 ),Point( dilation_size, dilation_size ) );/// 膨脹操作dilate( src, dilation_dst, element );imshow( "Dilation Demo", dilation_dst ); }

    結(jié)果

    • 編譯并使用圖像路徑作為參數(shù)運(yùn)行程序,比如我們使用以下圖像:

      下面是操作的結(jié)果。 更改Trackbars的位置就會(huì)產(chǎn)生不一樣的輸出圖像,自己試試吧。 最后,你還可以通過增加第三個(gè)Trackbar來控制膨脹或腐蝕的次數(shù)。








    更多形態(tài)學(xué)變換

    目標(biāo)

    本文檔嘗試解答如下問題:

    • 如何使用OpenCV函數(shù)?morphologyEx?進(jìn)行形態(tài)學(xué)操作:
      • 開運(yùn)算 (Opening)
      • 閉運(yùn)算 (Closing)
      • 形態(tài)梯度 (Morphological Gradient)
      • 頂帽 (Top Hat)
      • 黑帽(Black Hat)

    原理

    Note

    ?

    以下內(nèi)容來自于Bradski和Kaehler的大作?Learning OpenCV?。

    前一節(jié)我們討論了兩種最基本的形態(tài)學(xué)操作:

    • 腐蝕 (Erosion)
    • 膨脹 (Dilation)

    運(yùn)用這兩個(gè)基本操作,我們可以實(shí)現(xiàn)更高級(jí)的形態(tài)學(xué)變換。這篇文檔將會(huì)簡(jiǎn)要介紹OpenCV提供的5種高級(jí)形態(tài)學(xué)操作:

    開運(yùn)算 (Opening)

    • 開運(yùn)算是通過先對(duì)圖像腐蝕再膨脹實(shí)現(xiàn)的。

    • 能夠排除小團(tuán)塊物體(假設(shè)物體較背景明亮)

    • 請(qǐng)看下面,左圖是原圖像,右圖是采用開運(yùn)算轉(zhuǎn)換之后的結(jié)果圖。 觀察發(fā)現(xiàn)字母拐彎處的白色空間消失。

    閉運(yùn)算(Closing)

    • 閉運(yùn)算是通過先對(duì)圖像膨脹再腐蝕實(shí)現(xiàn)的。

    • 能夠排除小型黑洞(黑色區(qū)域)。

    形態(tài)梯度(Morphological Gradient)

    • 膨脹圖與腐蝕圖之差

    • 能夠保留物體的邊緣輪廓,如下所示:

    頂帽(Top Hat)

    • 原圖像與開運(yùn)算結(jié)果圖之差

    黑帽(Black Hat)

    • 閉運(yùn)算結(jié)果圖與原圖像之差

    源碼

    下面是本教程的源碼, 你也可以從?這里?下載。

    #include "opencv2/imgproc/imgproc.hpp" #include "opencv2/highgui/highgui.hpp" #include <stdlib.h> #include <stdio.h>using namespace cv;/// 全局變量 Mat src, dst;int morph_elem = 0; int morph_size = 0; int morph_operator = 0; int const max_operator = 4; int const max_elem = 2; int const max_kernel_size = 21;char* window_name = "Morphology Transformations Demo";/** 回調(diào)函數(shù)申明 */ void Morphology_Operations( int, void* );/** @函數(shù) main */ int main( int argc, char** argv ) {/// 裝載圖像src = imread( argv[1] );if( !src.data ){ return -1; }/// 創(chuàng)建顯示窗口namedWindow( window_name, CV_WINDOW_AUTOSIZE );/// 創(chuàng)建選擇具體操作的 trackbarcreateTrackbar("Operator:\n 0: Opening - 1: Closing \n 2: Gradient - 3: Top Hat \n 4: Black Hat", window_name, &morph_operator, max_operator, Morphology_Operations );/// 創(chuàng)建選擇內(nèi)核形狀的 trackbarcreateTrackbar( "Element:\n 0: Rect - 1: Cross - 2: Ellipse", window_name,&morph_elem, max_elem,Morphology_Operations );/// 創(chuàng)建選擇內(nèi)核大小的 trackbarcreateTrackbar( "Kernel size:\n 2n +1", window_name,&morph_size, max_kernel_size,Morphology_Operations );/// 啟動(dòng)使用默認(rèn)值Morphology_Operations( 0, 0 );waitKey(0);return 0;}/** * @函數(shù) Morphology_Operations */ void Morphology_Operations( int, void* ) {// 由于 MORPH_X的取值范圍是: 2,3,4,5 和 6int operation = morph_operator + 2;Mat element = getStructuringElement( morph_elem, Size( 2*morph_size + 1, 2*morph_size+1 ), Point( morph_size, morph_size ) );/// 運(yùn)行指定形態(tài)學(xué)操作morphologyEx( src, dst, operation, element );imshow( window_name, dst );}

    解釋

  • 看一下程序的總體流程:

    • 裝載圖像

    • 創(chuàng)建顯示形態(tài)學(xué)操作的窗口

    • 創(chuàng)建3個(gè)trackbar獲取用戶參數(shù):

      • 第一個(gè)trackbar?“Operator”?返回用戶選擇的形態(tài)學(xué)操作類型 (morph_operator).

        createTrackbar("Operator:\n 0: Opening - 1: Closing \n 2: Gradient - 3: Top Hat \n 4: Black Hat",window_name, &morph_operator, max_operator,Morphology_Operations );
      • 第二個(gè)trackbar?“Element”?返回?morph_elem, 指定內(nèi)核形狀:

        createTrackbar( "Element:\n 0: Rect - 1: Cross - 2: Ellipse", window_name,&morph_elem, max_elem,Morphology_Operations );
      • 第三個(gè)trackbar?“Kernel Size”?返回內(nèi)核大小(morph_size)

        createTrackbar( "Kernel size:\n 2n +1", window_name,&morph_size, max_kernel_size,Morphology_Operations );
    • 每當(dāng)任一標(biāo)尺被移動(dòng), 用戶函數(shù)?Morphology_Operations?就會(huì)被調(diào)用,該函數(shù)獲取trackbar的當(dāng)前值運(yùn)行指定操作并更新顯示結(jié)果圖像。

      /** * @函數(shù) Morphology_Operations */ void Morphology_Operations( int, void* ) {// 由于 MORPH_X的取值范圍是: 2,3,4,5 和 6int operation = morph_operator + 2;Mat element = getStructuringElement( morph_elem, Size( 2*morph_size + 1, 2*morph_size+1 ), Point( morph_size, morph_size ) );/// 運(yùn)行指定形態(tài)學(xué)操作morphologyEx( src, dst, operation, element );imshow( window_name, dst );}

      運(yùn)行形態(tài)學(xué)操作的核心函數(shù)是?morphologyEx?。在本例中,我們使用了4個(gè)參數(shù)(其余使用默認(rèn)值):

      • src?: 原 (輸入) 圖像
      • dst: 輸出圖像
      • operation: 需要運(yùn)行的形態(tài)學(xué)操作。 我們有5個(gè)選項(xiàng):
        • Opening: MORPH_OPEN : 2
        • Closing: MORPH_CLOSE: 3
        • Gradient: MORPH_GRADIENT: 4
        • Top Hat: MORPH_TOPHAT: 5
        • Black Hat: MORPH_BLACKHAT: 6

      你可以看到, 它們的取值范圍是 <2-6>, 因此我們要將從tracker獲取的值增加(+2):

      int operation = morph_operator + 2;
      • element: 內(nèi)核,可以使用函數(shù):get_structuring_element:getStructuringElement <>?自定義。
  • 結(jié)果

    • 在編譯上面的代碼之后, 我們可以運(yùn)行結(jié)果,將圖片路徑輸入。這里使用圖像:?baboon.png:

    • 這里是顯示窗口的兩個(gè)截圖。第一幅圖顯示了使用交錯(cuò)內(nèi)核和?開運(yùn)算?之后的結(jié)果, 第二幅圖顯示了使用橢圓內(nèi)核和?黑帽?之后的結(jié)果。











    圖像金字塔

    目標(biāo)

    本文檔嘗試解答如下問題:

    • 如何使用OpenCV函數(shù)?pyrUp?和?pyrDown?對(duì)圖像進(jìn)行向上和向下采樣。

    原理

    Note

    ?

    以下內(nèi)容來自于Bradski和Kaehler的大作:?Learning OpenCV?。

    • 當(dāng)我們需要將圖像轉(zhuǎn)換到另一個(gè)尺寸的時(shí)候, 有兩種可能:
    • 放大?圖像 或者
    • 縮小?圖像。
    • 盡管OpenCV?幾何變換?部分提供了一個(gè)真正意義上的圖像縮放函數(shù)(resize, 在以后的教程中會(huì)學(xué)到),不過在本篇我們首先學(xué)習(xí)一下使用?圖像金字塔?來做圖像縮放, 圖像金字塔是視覺運(yùn)用中廣泛采用的一項(xiàng)技術(shù)。

    圖像金字塔

    • 一個(gè)圖像金字塔是一系列圖像的集合 - 所有圖像來源于同一張?jiān)紙D像 - 通過梯次向下采樣獲得,直到達(dá)到某個(gè)終止條件才停止采樣。
    • 有兩種類型的圖像金字塔常常出現(xiàn)在文獻(xiàn)和應(yīng)用中:
      • 高斯金字塔(Gaussian pyramid):?用來向下采樣
      • 拉普拉斯金字塔(Laplacian pyramid):?用來從金字塔低層圖像重建上層未采樣圖像
    • 在這篇文檔中我們將使用?高斯金字塔?。

    高斯金字塔

    • 想想金字塔為一層一層的圖像,層級(jí)越高,圖像越小。

    • 每一層都按從下到上的次序編號(hào), 層級(jí)??(表示為??尺寸小于層級(jí)??())。

    • 為了獲取層級(jí)為??的金字塔圖像,我們采用如下方法:

      • 將??與高斯內(nèi)核卷積:

      • 將所有偶數(shù)行和列去除。

    • 顯而易見,結(jié)果圖像只有原圖的四分之一。通過對(duì)輸入圖像??(原始圖像) 不停迭代以上步驟就會(huì)得到整個(gè)金字塔。

    • 以上過程描述了對(duì)圖像的向下采樣,如果將圖像變大呢?:

      • 首先,將圖像在每個(gè)方向擴(kuò)大為原來的兩倍,新增的行和列以0填充()
      • 使用先前同樣的內(nèi)核(乘以4)與放大后的圖像卷積,獲得 “新增像素” 的近似值。
    • 這兩個(gè)步驟(向下和向上采樣) 分別通過OpenCV函數(shù)?pyrUp?和?pyrDown?實(shí)現(xiàn), 我們將會(huì)在下面的示例中演示如何使用這兩個(gè)函數(shù)。

    Note

    ?

    我們向下采樣縮小圖像的時(shí)候, 我們實(shí)際上?丟失?了一些信息。

    源碼

    本教程的源碼如下,你也可以從?這里?下載

    #include "opencv2/imgproc/imgproc.hpp" #include "opencv2/highgui/highgui.hpp" #include <math.h> #include <stdlib.h> #include <stdio.h>using namespace cv;/// 全局變量 Mat src, dst, tmp; char* window_name = "Pyramids Demo";/** * @函數(shù) main */ int main( int argc, char** argv ) {/// 指示說明printf( "\n Zoom In-Out demo \n " );printf( "------------------ \n" );printf( " * [u] -> Zoom in \n" );printf( " * [d] -> Zoom out \n" );printf( " * [ESC] -> Close program \n \n" );/// 測(cè)試圖像 - 尺寸必須能被 2^{n} 整除src = imread( "../images/chicky_512.jpg" );if( !src.data ){ printf(" No data! -- Exiting the program \n");return -1; }tmp = src;dst = tmp;/// 創(chuàng)建顯示窗口namedWindow( window_name, CV_WINDOW_AUTOSIZE );imshow( window_name, dst );/// 循環(huán)while( true ){int c;c = waitKey(10);if( (char)c == 27 ){ break; }if( (char)c == 'u' ){ pyrUp( tmp, dst, Size( tmp.cols*2, tmp.rows*2 ) );printf( "** Zoom In: Image x 2 \n" );}else if( (char)c == 'd' ){ pyrDown( tmp, dst, Size( tmp.cols/2, tmp.rows/2 ) );printf( "** Zoom Out: Image / 2 \n" );}imshow( window_name, dst );tmp = dst;}return 0; }

    解釋

  • 讓我們來回顧一下本程序的總體流程:

    • 裝載圖像(此處路徑由程序設(shè)定,用戶無需將圖像路徑當(dāng)作參數(shù)輸入)

      /// 測(cè)試圖像 - 尺寸必須能被 2^{n} 整除 src = imread( "../images/chicky_512.jpg" ); if( !src.data ){ printf(" No data! -- Exiting the program \n");return -1; }
    • 創(chuàng)建兩個(gè)Mat實(shí)例, 一個(gè)用來儲(chǔ)存操作結(jié)果(dst), 另一個(gè)用來存儲(chǔ)零時(shí)結(jié)果(tmp)。

      Mat src, dst, tmp; /* ... */ tmp = src; dst = tmp;
    • 創(chuàng)建窗口顯示結(jié)果

      namedWindow( window_name, CV_WINDOW_AUTOSIZE ); imshow( window_name, dst );
    • 執(zhí)行無限循環(huán),等待用戶輸入。

      while( true ) {int c;c = waitKey(10);if( (char)c == 27 ){ break; }if( (char)c == 'u' ){ pyrUp( tmp, dst, Size( tmp.cols*2, tmp.rows*2 ) );printf( "** Zoom In: Image x 2 \n" );}else if( (char)c == 'd' ){ pyrDown( tmp, dst, Size( tmp.cols/2, tmp.rows/2 ) );printf( "** Zoom Out: Image / 2 \n" );}imshow( window_name, dst );tmp = dst; }

      如果用戶按?ESC?鍵程序退出。 此外,它還提供兩個(gè)選項(xiàng):

      • 向上采樣 (按 ‘u’)

        pyrUp( tmp, dst, Size( tmp.cols*2, tmp.rows*2 )

        函數(shù)?pyrUp?接受了3個(gè)參數(shù):

        • tmp: 當(dāng)前圖像, 初始化為原圖像?src?。
        • dst: 目的圖像( 顯示圖像,為輸入圖像的兩倍)
        • Size( tmp.cols*2, tmp.rows*2 )?: 目的圖像大小, 既然我們是向上采樣,?pyrUp?期待一個(gè)兩倍于輸入圖像(?tmp?)的大小。
      • 向下采樣(按 ‘d’)

        pyrDown( tmp, dst, Size( tmp.cols/2, tmp.rows/2 )

        類似于?pyrUp, 函數(shù)?pyrDown?也接受了3個(gè)參數(shù):

        • tmp: 當(dāng)前圖像, 初始化為原圖像?src?。
        • dst: 目的圖像( 顯示圖像,為輸入圖像的一半)
        • Size( tmp.cols/2, tmp.rows/2 )?:目的圖像大小, 既然我們是向下采樣,?pyrDown?期待一個(gè)一半于輸入圖像(?tmp)的大小。
      • 注意輸入圖像的大小(在兩個(gè)方向)必須是2的冥,否則,將會(huì)顯示錯(cuò)誤。

      • 最后,將輸入圖像?tmp?更新為當(dāng)前顯示圖像, 這樣后續(xù)操作將作用于更新后的圖像。

        tmp = dst;
  • 結(jié)果

    • 在編譯上面的代碼之后, 我們可以運(yùn)行結(jié)果。 程序調(diào)用了圖像?chicky_512.jpg?,你可以在?tutorial_code/image?文件夾找到它。 注意圖像大小是?, 因此向下采樣不會(huì)產(chǎn)生錯(cuò)誤()。 原圖像如下所示:

    • 首先按兩次 ‘d’ 連續(xù)兩次向下采樣?pyrDown?,結(jié)果如圖:

    • 由于我們縮小了圖像,我們也因此丟失了一些信息。通過連續(xù)按兩次 ‘u’ 向上采樣兩次?pyrUp?,很明顯圖像有些失真:








    基本的閾值操作

    目標(biāo):

    本節(jié)簡(jiǎn)介:

    • OpenCV中的閾值(threshold)函數(shù):?threshold?的運(yùn)用。

    基本理論:

    注意:
    本節(jié)的解釋出自Bradski與Kaehler的書籍?Learning OpenCV?。

    什么是閾值?

    • 最簡(jiǎn)單的圖像分割的方法。

    • 應(yīng)用舉例:從一副圖像中利用閾值分割出我們需要的物體部分(當(dāng)然這里的物體可以是一部分或者整體)。這樣的圖像分割方法是基于圖像中物體與背景之間的灰度差異,而且此分割屬于像素級(jí)的分割。

    • 為了從一副圖像中提取出我們需要的部分,應(yīng)該用圖像中的每一個(gè)像素點(diǎn)的灰度值與選取的閾值進(jìn)行比較,并作出相應(yīng)的判斷。(注意:閾值的選取依賴于具體的問題。即:物體在不同的圖像中有可能會(huì)有不同的灰度值。

    • 一旦找到了需要分割的物體的像素點(diǎn),我們可以對(duì)這些像素點(diǎn)設(shè)定一些特定的值來表示。(例如:可以將該物體的像素點(diǎn)的灰度值設(shè)定為:‘0’(黑色),其他的像素點(diǎn)的灰度值為:‘255’(白色);當(dāng)然像素點(diǎn)的灰度值可以任意,但最好設(shè)定的兩種顏色對(duì)比度較強(qiáng),方便觀察結(jié)果)。

    閾值化的類型:

    • OpenCV中提供了閾值(threshold)函數(shù):?threshold?。

    • 這個(gè)函數(shù)有5種閾值化類型,在接下來的章節(jié)中將會(huì)具體介紹。

    • 為了解釋閾值分割的過程,我們來看一個(gè)簡(jiǎn)單有關(guān)像素灰度的圖片,該圖如下。該圖中的藍(lán)色水平線代表著具體的一個(gè)閾值。

    閾值類型1:二進(jìn)制閾值化

    • 該閾值化類型如下式所示:

    • 解釋:在運(yùn)用該閾值類型的時(shí)候,先要選定一個(gè)特定的閾值量,比如:125,這樣,新的閾值產(chǎn)生規(guī)則可以解釋為大于125的像素點(diǎn)的灰度值設(shè)定為最大值(如8位灰度值最大為255),灰度值小于125的像素點(diǎn)的灰度值設(shè)定為0。

    閾值類型2:反二進(jìn)制閾值化

    • 該閾值類型如下式所示:

    • 解釋:該閾值化與二進(jìn)制閾值化相似,先選定一個(gè)特定的灰度值作為閾值,不過最后的設(shè)定值相反。(在8位灰度圖中,例如大于閾值的設(shè)定為0,而小于該閾值的設(shè)定為255)。

    閾值類型3:截?cái)嚅撝祷?/h4>
    • 該閾值化類型如下式所示:

    • 解釋:同樣首先需要選定一個(gè)閾值,圖像中大于該閾值的像素點(diǎn)被設(shè)定為該閾值,小于該閾值的保持不變。(例如:閾值選取為125,那小于125的閾值不改變,大于125的灰度值(230)的像素點(diǎn)就設(shè)定為該閾值)。

    閾值類型4:閾值化為0

    • 該閾值類型如下式所示:

    • 解釋:先選定一個(gè)閾值,然后對(duì)圖像做如下處理:1 像素點(diǎn)的灰度值大于該閾值的不進(jìn)行任何改變;2 像素點(diǎn)的灰度值小于該閾值的,其灰度值全部變?yōu)?。

    閾值類型5:反閾值化為0

    • 該閾值類型如下式所示:

    • 解釋:原理類似于0閾值,但是在對(duì)圖像做處理的時(shí)候相反,即:像素點(diǎn)的灰度值小于該閾值的不進(jìn)行任何改變,而大于該閾值的部分,其灰度值全部變?yōu)?。

    代碼示范:

    簡(jiǎn)單的代碼如下。同樣也可以在網(wǎng)站中?下載?以下代碼。

    #include "opencv2/imgproc/imgproc.hpp" #include "opencv2/highgui/highgui.hpp" #include <stdlib.h> #include <stdio.h>using namespace cv;/// 全局變量定義及賦值int threshold_value = 0; int threshold_type = 3;; int const max_value = 255; int const max_type = 4; int const max_BINARY_value = 255;Mat src, src_gray, dst; char* window_name = "Threshold Demo";char* trackbar_type = "Type: \n 0: Binary \n 1: Binary Inverted \n 2: Truncate \n 3: To Zero \n 4: To Zero Inverted"; char* trackbar_value = "Value";/// 自定義函數(shù)聲明 void Threshold_Demo( int, void* );/** * @主函數(shù) */ int main( int argc, char** argv ) {/// 讀取一副圖片,不改變圖片本身的顏色類型(該讀取方式為DOS運(yùn)行模式)src = imread( argv[1], 1 );/// 將圖片轉(zhuǎn)換成灰度圖片cvtColor( src, src_gray, CV_RGB2GRAY );/// 創(chuàng)建一個(gè)窗口顯示圖片namedWindow( window_name, CV_WINDOW_AUTOSIZE );/// 創(chuàng)建滑動(dòng)條來控制閾值createTrackbar( trackbar_type,window_name, &threshold_type,max_type, Threshold_Demo );createTrackbar( trackbar_value,window_name, &threshold_value,max_value, Threshold_Demo );/// 初始化自定義的閾值函數(shù)Threshold_Demo( 0, 0 );/// 等待用戶按鍵。如果是ESC健則退出等待過程。while(true){int c;c = waitKey( 20 );if( (char)c == 27 ){ break; }}}/** * @自定義的閾值函數(shù) */ void Threshold_Demo( int, void* ) {/* 0: 二進(jìn)制閾值 1: 反二進(jìn)制閾值 2: 截?cái)嚅撝?/span> 3: 0閾值 4: 反0閾值 */threshold( src_gray, dst, threshold_value, max_BINARY_value,threshold_type );imshow( window_name, dst ); }

    解釋:

  • 先看一下整個(gè)程序的結(jié)構(gòu):

    • 先讀取一副圖片,如果是圖片顏色類型是RGB3色類型,則轉(zhuǎn)換成灰度類型的圖像。轉(zhuǎn)換顏色類型可以運(yùn)用OpenCV中的 cvtColor<> 函數(shù)。

      src = imread( argv[1], 1 );/// 顏色類型從RGB 轉(zhuǎn)換成灰度 cvtColor( src, src_gray, CV_RGB2GRAY );
    • 然后創(chuàng)建一個(gè)窗口來顯示該圖片可以檢驗(yàn)轉(zhuǎn)換結(jié)果

      namedWindow( window_name, CV_WINDOW_AUTOSIZE );
    • 接著該程序創(chuàng)建兩個(gè)滾動(dòng)條來等待用戶的輸入:

      • 第一個(gè)滾動(dòng)條作用:選擇閾值類型:二進(jìn)制,反二進(jìn)制,截?cái)?#xff0c;0,反0。
      • 第二個(gè)滾動(dòng)條作用:選擇閾值的大小。
      createTrackbar( trackbar_type,window_name, &threshold_type,max_type, Threshold_Demo );createTrackbar( trackbar_value,window_name, &threshold_value,max_value, Threshold_Demo );
    • 在這里等到用戶拖動(dòng)滾動(dòng)條來輸入閾值類型以及閾值的大小,或者是用戶鍵入ESC健退出程序。

    • 無論何時(shí)拖動(dòng)滾動(dòng)條,用戶自定義的閾值函數(shù)都將會(huì)被調(diào)用。

      /** * @自定義的閾值函數(shù) */ void Threshold_Demo( int, void* ) {/* 0: 二進(jìn)制閾值 1: 反二進(jìn)制閾值 2: 截?cái)嚅撝?/span> 3: 0閾值 4: 反0閾值 */threshold( src_gray, dst, threshold_value, max_BINARY_value,threshold_type );imshow( window_name, dst ); }

      就像你看到的那樣,在這樣的過程中,函數(shù) threshold<> 會(huì)接受到5個(gè)參數(shù):

      • src_gray: 輸入的灰度圖像的地址。
      • dst: 輸出圖像的地址。
      • threshold_value: 進(jìn)行閾值操作時(shí)閾值的大小。
      • max_BINARY_value: 設(shè)定的最大灰度值(該參數(shù)運(yùn)用在二進(jìn)制與反二進(jìn)制閾值操作中)。
      • threshold_type: 閾值的類型。從上面提到的5種中選擇出的結(jié)果。
  • 結(jié)果:

  • 程序編譯過后,從正確的路徑中讀取一張圖片。例如,該輸入圖片如下所示:

  • 首先,閾值類型選擇為反二進(jìn)制閾值類型。我們希望灰度值大于閾值的變暗,即這一部分像素的灰度值設(shè)定為0。從下圖中可以很清楚的看到這樣的變化。(在原圖中,狗的嘴和眼睛部分比圖像中的其他部分要亮,在結(jié)果圖中可以看到由于反二進(jìn)制閾值分割,這兩部分變的比其他圖像的都要暗。原理具體參見本節(jié)中反二進(jìn)制閾值部分解釋)

  • 現(xiàn)在,閾值的類型選擇為0閾值。在這種情況下,我們希望那些在圖像中最黑的像素點(diǎn)徹底的變成黑色,而其他大于閾值的像素保持原來的面貌。其結(jié)果如下圖所示:




  • from: http://www.opencv.org.cn/opencvdoc/2.3.2/html/doc/tutorials/imgproc/table_of_content_imgproc/table_of_content_imgproc.html#table-of-content-imgproc

    總結(jié)

    以上是生活随笔為你收集整理的OpenCV之imgproc 模块. 图像处理(1)图像平滑处理 腐蚀与膨胀(Eroding and Dilating) 更多形态学变换 图像金字塔 基本的阈值操作的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。