<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          實(shí)踐教程 | 使用Pytorch從頭實(shí)現(xiàn)Canny邊緣檢測(cè)

          共 9649字,需瀏覽 20分鐘

           ·

          2021-10-17 18:05

          ↑ 點(diǎn)擊藍(lán)字?關(guān)注極市平臺(tái)

          作者 | Axel Thevenot
          來(lái)源 | AI公園
          編輯 | 極市平臺(tái)

          極市導(dǎo)讀

          ?

          Canny邊緣檢測(cè)器的詳細(xì)介紹以及Pytorch實(shí)現(xiàn)。?>>加入極市CV技術(shù)交流群,走在計(jì)算機(jī)視覺(jué)的最前沿

          Canny濾波器當(dāng)然是最著名和最常用的邊緣檢測(cè)濾波器。我會(huì)逐步解釋用于輪廓檢測(cè)的canny濾波器。因?yàn)閏anny濾波器是一個(gè)多級(jí)濾波器。Canny過(guò)濾器很少被集成到深度學(xué)習(xí)模型中。所以我將描述不同的部分,同時(shí)使用Pytorch實(shí)現(xiàn)它。它可以幾乎沒(méi)有限制的進(jìn)行定制,我允許自己一些偏差。

          使用輸入圖像和kernel進(jìn)行卷積

          我來(lái)介紹一下什么是卷積矩陣,或者說(shuō)核。卷積矩陣描述了我們要傳遞給輸入圖像的一個(gè)濾波器。為了簡(jiǎn)單起見,kernel將通過(guò)應(yīng)用一個(gè)卷積,從左到右,從上到下移動(dòng)整個(gè)圖像。這個(gè)操作的輸出稱為圖像濾波

          高斯濾波器

          首先,我們通常通過(guò)應(yīng)用一個(gè)模糊濾波器來(lái)消除輸入圖像中的噪聲。這個(gè)濾波器的選擇取決于你,但我們通常使用一個(gè)高斯濾波器。

          高斯核
          def?get_gaussian_kernel(k=3,?mu=0,?sigma=1,?normalize=True):
          ????#?compute?1?dimension?gaussian
          ????gaussian_1D?=?np.linspace(-1,?1,?k)
          ????#?compute?a?grid?distance?from?center
          ????x,?y?=?np.meshgrid(gaussian_1D,?gaussian_1D)
          ????distance?=?(x?**?2?+?y?**?2)?**?0.5

          ????#?compute?the?2?dimension?gaussian
          ????gaussian_2D?=?np.exp(-(distance?-?mu)?**?2?/?(2?*?sigma?**?2))
          ????gaussian_2D?=?gaussian_2D?/?(2?*?np.pi?*sigma?**2)

          ????#?normalize?part?(mathematically)
          ????if?normalize:
          ????????gaussian_2D?=?gaussian_2D?/?np.sum(gaussian_2D)
          ????return?gaussian_2D

          可以制作不同大小的高斯核,或多或少都是居中或扁平的。顯然,kernel越大,輸出的圖像越容易模糊。

          Sobel 濾波

          為了檢測(cè)邊緣,必須對(duì)圖像應(yīng)用一個(gè)濾波器來(lái)提取梯度。

          在X上應(yīng)用Sobel kernel
          def?get_sobel_kernel(k=3):
          ????#?get?range
          ????range?=?np.linspace(-(k?//?2),?k?//?2,?k)
          ????#?compute?a?grid?the?numerator?and?the?axis-distances
          ????x,?y?=?np.meshgrid(range,?range)
          ????sobel_2D_numerator?=?x
          ????sobel_2D_denominator?=?(x?**?2?+?y?**?2)
          ????sobel_2D_denominator[:,?k?//?2]?=?1??#?avoid?division?by?zero
          ????sobel_2D?=?sobel_2D_numerator?/?sobel_2D_denominator
          ????return?sobel_2D

          最常用的濾波器是Sobel濾波器。分解成兩個(gè)濾波器,第一個(gè)核用于提取水平梯度。粗略地說(shuō),右邊的像素比左邊的像素越亮,過(guò)濾后的圖像的結(jié)果就越高。反之亦然。這在Lena帽子的左邊可以清楚地看到。

          Y方向應(yīng)用Sobel kernel

          第二個(gè)核用于提取垂直的梯度。這個(gè)kernel是另一個(gè)的轉(zhuǎn)置。這兩個(gè)kernel具有相同的作用,但在不同的軸上。

          計(jì)算梯度

          現(xiàn)在,我們?cè)趫D像的兩個(gè)軸上都有了梯度。為了檢測(cè)輪廓,我們需要梯度的大小。我們可以使用絕對(duì)值范數(shù)或歐幾里得范數(shù)

          梯度的大小和方向

          邊緣現(xiàn)在使用我們的梯度的大小被完美地檢測(cè),但是很厚。如果我們能只保留輪廓的細(xì)線就好了。因此,我們同時(shí)計(jì)算我們的梯度的方向,這將用于保持這些細(xì)線。在Lena的圖像中,梯度是由強(qiáng)度表示的,因?yàn)樘荻鹊慕嵌确浅V匾?/p>

          非極大值抑制

          為了細(xì)化邊緣,可以使用非最大抑制方法。在此之前,我們需要?jiǎng)?chuàng)建45°× 45°方向的kernel

          有方向的kernel
          def?get_thin_kernels(start=0,?end=360,?step=45):
          ????????k_thin?=?3??#?actual?size?of?the?directional?kernel
          ????????#?increase?for?a?while?to?avoid?interpolation?when?rotating
          ????????k_increased?=?k_thin?+?2

          ????????#?get?0°?angle?directional?kernel
          ????????thin_kernel_0?=?np.zeros((k_increased,?k_increased))
          ????????thin_kernel_0[k_increased?//?2,?k_increased?//?2]?=?1
          ????????thin_kernel_0[k_increased?//?2,?k_increased?//?2?+?1:]?=?-1

          ????????#?rotate?the?0°?angle?directional?kernel?to?get?the?other?ones
          ????????thin_kernels?=?[]
          ????????for?angle?in?range(start,?end,?step):
          ????????????(h,?w)?=?thin_kernel_0.shape
          ????????????#?get?the?center?to?not?rotate?around?the?(0,?0)?coord?point
          ????????????center?=?(w?//?2,?h?//?2)
          ????????????#?apply?rotation
          ????????????rotation_matrix?=?cv2.getRotationMatrix2D(center,?angle,?1)
          ????????????kernel_angle_increased?=?cv2.warpAffine(thin_kernel_0,?rotation_matrix,?(w,?h),?cv2.INTER_NEAREST)

          ????????????#?get?the?k=3?kerne
          ????????????kernel_angle?=?kernel_angle_increased[1:-1,?1:-1]
          ????????????is_diag?=?(abs(kernel_angle)?==?1)??????#?because?of?the?interpolation
          ????????????kernel_angle?=?kernel_angle?*?is_diag???#?because?of?the?interpolation
          ????????????thin_kernels.append(kernel_angle)
          ????????return?thin_kernels

          因此,該過(guò)程要求檢查8鄰域(或稱為Moore的鄰域)。這個(gè)概念很容易理解。對(duì)于每個(gè)像素,我們將檢查方向。我們要看看這個(gè)像素是否比它的鄰居的梯度方向更強(qiáng)。如果是,那么我們將其與相反方向的相鄰像素進(jìn)行比較。如果這個(gè)像素與它的雙向鄰居相比具有最大強(qiáng)度,那么它是局部最大。這個(gè)像素將被保存。在所有其他情況下,它不是一個(gè)局部最大值,像素被刪除。

          閾值和滯后

          最后,只需要應(yīng)用閾值。有三種方法可以做到這一點(diǎn):

          • 低-高閾值:將亮度高于閾值的像素設(shè)為1,其他設(shè)為0。
          • 低-弱弱-高閾值:我們?cè)O(shè)置高強(qiáng)度像素為1,低強(qiáng)度像素為0,介于兩個(gè)閾值之間,我們?cè)O(shè)置它們?yōu)?.5,并被認(rèn)為是弱像素。
          • 低-弱弱-高滯后:同上,弱像素滯后進(jìn)行評(píng)估,并重新分配為高或低。
          閾值

          “滯后是系統(tǒng)狀態(tài)對(duì)其歷史的依賴。”—— 維基百科

          在我們的例子中,滯后可以理解為一個(gè)像素對(duì)其相鄰像素的依賴。在Canny濾波器的滯后步驟中,我們說(shuō)如果一個(gè)弱像素在它的8個(gè)鄰居中有一個(gè)高強(qiáng)度的鄰居,那么它將被歸類為高。

          我喜歡使用不同的方法,我最后使用一個(gè)濾波器對(duì)弱像素進(jìn)行分類。如果它的卷積乘積大于1那么我把它歸為High。

          滯后kernel

          你說(shuō)過(guò)用PyTorch的

          是的,現(xiàn)在可以看看Pytorch代碼了。所有的東西都被組合成一個(gè)nn.Module。我不能保證實(shí)現(xiàn)會(huì)得到優(yōu)化。使用OpenCV的特性可以加快處理速度。但是這種實(shí)現(xiàn)至少具有靈活、可參數(shù)化和根據(jù)需要容易修改的優(yōu)點(diǎn)。

          class?CannyFilter(nn.Module):
          ????def?__init__(self,
          ?????????????????k_gaussian=3,
          ?????????????????mu=0,
          ?????????????????sigma=1,
          ?????????????????k_sobel=3,
          ?????????????????use_cuda=False)
          :

          ????????super(CannyFilter,?self).__init__()
          ????????#?device
          ????????self.device?=?'cuda'?if?use_cuda?else?'cpu'

          ????????#?gaussian

          ????????gaussian_2D?=?get_gaussian_kernel(k_gaussian,?mu,?sigma)
          ????????self.gaussian_filter?=?nn.Conv2d(in_channels=1,
          ?????????????????????????????????????????out_channels=1,
          ?????????????????????????????????????????kernel_size=k_gaussian,
          ?????????????????????????????????????????padding=k_gaussian?//?2,
          ?????????????????????????????????????????bias=False)
          ????????self.gaussian_filter.weight[:]?=?torch.from_numpy(gaussian_2D)

          ????????#?sobel

          ????????sobel_2D?=?get_sobel_kernel(k_sobel)
          ????????self.sobel_filter_x?=?nn.Conv2d(in_channels=1,
          ????????????????????????????????????????out_channels=1,
          ????????????????????????????????????????kernel_size=k_sobel,
          ????????????????????????????????????????padding=k_sobel?//?2,
          ????????????????????????????????????????bias=False)
          ????????self.sobel_filter_x.weight[:]?=?torch.from_numpy(sobel_2D)


          ????????self.sobel_filter_y?=?nn.Conv2d(in_channels=1,
          ????????????????????????????????????????out_channels=1,
          ????????????????????????????????????????kernel_size=k_sobel,
          ????????????????????????????????????????padding=k_sobel?//?2,
          ????????????????????????????????????????bias=False)
          ????????self.sobel_filter_y.weight[:]?=?torch.from_numpy(sobel_2D.T)


          ????????#?thin

          ????????thin_kernels?=?get_thin_kernels()
          ????????directional_kernels?=?np.stack(thin_kernels)

          ????????self.directional_filter?=?nn.Conv2d(in_channels=1,
          ????????????????????????????????????????????out_channels=8,
          ????????????????????????????????????????????kernel_size=thin_kernels[0].shape,
          ????????????????????????????????????????????padding=thin_kernels[0].shape[-1]?//?2,
          ????????????????????????????????????????????bias=False)
          ????????self.directional_filter.weight[:,?0]?=?torch.from_numpy(directional_kernels)

          ????????#?hysteresis

          ????????hysteresis?=?np.ones((3,?3))?+?0.25
          ????????self.hysteresis?=?nn.Conv2d(in_channels=1,
          ????????????????????????????????????out_channels=1,
          ????????????????????????????????????kernel_size=3,
          ????????????????????????????????????padding=1,
          ????????????????????????????????????bias=False)
          ????????self.hysteresis.weight[:]?=?torch.from_numpy(hysteresis)


          ????def?forward(self,?img,?low_threshold=None,?high_threshold=None,?hysteresis=False):
          ????????#?set?the?setps?tensors
          ????????B,?C,?H,?W?=?img.shape
          ????????blurred?=?torch.zeros((B,?C,?H,?W)).to(self.device)
          ????????grad_x?=?torch.zeros((B,?1,?H,?W)).to(self.device)
          ????????grad_y?=?torch.zeros((B,?1,?H,?W)).to(self.device)
          ????????grad_magnitude?=?torch.zeros((B,?1,?H,?W)).to(self.device)
          ????????grad_orientation?=?torch.zeros((B,?1,?H,?W)).to(self.device)

          ????????#?gaussian

          ????????for?c?in?range(C):
          ????????????blurred[:,?c:c+1]?=?self.gaussian_filter(img[:,?c:c+1])

          ????????????grad_x?=?grad_x?+?self.sobel_filter_x(blurred[:,?c:c+1])
          ????????????grad_y?=?grad_y?+?self.sobel_filter_y(blurred[:,?c:c+1])

          ????????#?thick?edges

          ????????grad_x,?grad_y?=?grad_x?/?C,?grad_y?/?C
          ????????grad_magnitude?=?(grad_x?**?2?+?grad_y?**?2)?**?0.5
          ????????grad_orientation?=?torch.atan(grad_y?/?grad_x)
          ????????grad_orientation?=?grad_orientation?*?(360?/?np.pi)?+?180?#?convert?to?degree
          ????????grad_orientation?=?torch.round(grad_orientation?/?45)?*?45??#?keep?a?split?by?45

          ????????#?thin?edges

          ????????directional?=?self.directional_filter(grad_magnitude)
          ????????#?get?indices?of?positive?and?negative?directions
          ????????positive_idx?=?(grad_orientation?/?45)?%?8
          ????????negative_idx?=?((grad_orientation?/?45)?+?4)?%?8
          ????????thin_edges?=?grad_magnitude.clone()
          ????????#?non?maximum?suppression?direction?by?direction
          ????????for?pos_i?in?range(4):
          ????????????neg_i?=?pos_i?+?4
          ????????????#?get?the?oriented?grad?for?the?angle
          ????????????is_oriented_i?=?(positive_idx?==?pos_i)?*?1
          ????????????is_oriented_i?=?is_oriented_i?+?(positive_idx?==?neg_i)?*?1
          ????????????pos_directional?=?directional[:,?pos_i]
          ????????????neg_directional?=?directional[:,?neg_i]
          ????????????selected_direction?=?torch.stack([pos_directional,?neg_directional])

          ????????????#?get?the?local?maximum?pixels?for?the?angle
          ????????????is_max?=?selected_direction.min(dim=0)[0]?>?0.0
          ????????????is_max?=?torch.unsqueeze(is_max,?dim=1)

          ????????????#?apply?non?maximum?suppression
          ????????????to_remove?=?(is_max?==?0)?*?1?*?(is_oriented_i)?>?0
          ????????????thin_edges[to_remove]?=?0.0

          ????????#?thresholds

          ????????if?low_threshold?is?not?None:
          ????????????low?=?thin_edges?>?low_threshold

          ????????????if?high_threshold?is?not?None:
          ????????????????high?=?thin_edges?>?high_threshold
          ????????????????#?get?black/gray/white?only
          ????????????????thin_edges?=?low?*?0.5?+?high?*?0.5

          ????????????????if?hysteresis:
          ????????????????????#?get?weaks?and?check?if?they?are?high?or?not
          ????????????????????weak?=?(thin_edges?==?0.5)?*?1
          ????????????????????weak_is_high?=?(self.hysteresis(thin_edges)?>?1)?*?weak
          ????????????????????thin_edges?=?high?*?1?+?weak_is_high?*?1
          ????????????else:
          ????????????????thin_edges?=?low?*?1


          ????????return?blurred,?grad_x,?grad_y,?grad_magnitude,?grad_orientation,?thin_edges
          —END—

          原鏈:https://towardsdatascience.com/implement-canny-edge-detection-from-scratch-with-pytorch-a1cccfa58bed

          如果覺(jué)得有用,就請(qǐng)分享到朋友圈吧!

          △點(diǎn)擊卡片關(guān)注極市平臺(tái),獲取最新CV干貨

          公眾號(hào)后臺(tái)回復(fù)“CVPR21檢測(cè)”獲取CVPR2021目標(biāo)檢測(cè)論文下載~


          極市干貨
          神經(jīng)網(wǎng)絡(luò):視覺(jué)神經(jīng)網(wǎng)絡(luò)模型優(yōu)秀開源工作:timm庫(kù)使用方法和最新代碼解讀
          技術(shù)綜述:綜述:神經(jīng)網(wǎng)絡(luò)中 Normalization 的發(fā)展歷程CNN輕量化模型及其設(shè)計(jì)原則綜述
          算法技巧(trick):8點(diǎn)PyTorch提速技巧匯總圖像分類算法優(yōu)化技巧


          #?CV技術(shù)社群邀請(qǐng)函?#

          △長(zhǎng)按添加極市小助手
          添加極市小助手微信(ID : cvmart4)

          備注:姓名-學(xué)校/公司-研究方向-城市(如:小極-北大-目標(biāo)檢測(cè)-深圳)


          即可申請(qǐng)加入極市目標(biāo)檢測(cè)/圖像分割/工業(yè)檢測(cè)/人臉/醫(yī)學(xué)影像/3D/SLAM/自動(dòng)駕駛/超分辨率/姿態(tài)估計(jì)/ReID/GAN/圖像增強(qiáng)/OCR/視頻理解等技術(shù)交流群


          每月大咖直播分享、真實(shí)項(xiàng)目需求對(duì)接、求職內(nèi)推、算法競(jìng)賽、干貨資訊匯總、與?10000+來(lái)自港科大、北大、清華、中科院、CMU、騰訊、百度等名校名企視覺(jué)開發(fā)者互動(dòng)交流~



          覺(jué)得有用麻煩給個(gè)在看啦~??


          瀏覽 153
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  人妻视频导航 | 精品传媒一区二区三区 | 依依成人精品 | 青青草久草 | 婷婷亚洲综合_久久精品男人 |