<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>

          在沒有深度學(xué)習(xí)的情況下找到道路

          共 9595字,需瀏覽 20分鐘

           ·

          2021-09-03 14:50

          點(diǎn)擊上方小白學(xué)視覺”,選擇加"星標(biāo)"或“置頂

          重磅干貨,第一時(shí)間送達(dá)

          今天我們將一起參與一個(gè)項(xiàng)目,使用python在圖像和視頻中找到車道。對(duì)于該項(xiàng)目,我們將采用手動(dòng)方法。盡管我們確實(shí)可以使用深度學(xué)習(xí)等技術(shù)獲得更好的結(jié)果,但學(xué)習(xí)概念、工作原理和基礎(chǔ)知識(shí)也很重要,這樣我們?cè)跇?gòu)建高級(jí)模型時(shí)就可以應(yīng)用我們已經(jīng)學(xué)到的知識(shí)。在使用深度學(xué)習(xí)時(shí),可能還需要我們介紹的一些步驟。


          我們將采取的步驟如下:

          • 計(jì)算相機(jī)校準(zhǔn)并解決失真。

          • 應(yīng)用透視變換來(lái)校正二值圖像(“鳥瞰圖”)。

          • 使用顏色變換、漸變等來(lái)創(chuàng)建閾值二值圖像。

          • 檢測(cè)車道像素并擬合以找到車道邊界。

          • 確定車道的曲率和車輛相對(duì)于中心的位置。

          • 將檢測(cè)到的車道邊界變形回原始圖像。

          • 輸出車道邊界的視覺顯示以及車道曲率和車輛位置的數(shù)值估計(jì)。

          所有代碼和解釋都可以在我們的Github 中找到 。


          計(jì)算相機(jī)校準(zhǔn)


          今天的廉價(jià)針孔相機(jī)給圖像帶來(lái)了很多失真,兩種主要的畸變是徑向畸變和切向畸變。


          由于徑向畸變,直線會(huì)顯得彎曲,當(dāng)我們遠(yuǎn)離圖像的中心時(shí),它的影響更大。例如,如下圖所示,棋盤的兩個(gè)邊緣用紅線標(biāo)記,但是我們可以看到邊框不是一條直線,并且與紅線不匹配。所有預(yù)期的直線都凸出來(lái)了。

          相機(jī)失真示例

          為了解決這個(gè)問(wèn)題,我們將使用OpenCV python 庫(kù),并使用目標(biāo)相機(jī)拍攝的示例圖像來(lái)制作棋盤。為什么是棋盤?在棋盤圖像中,我們可以很容易地測(cè)量失真,因?yàn)槲覀冎牢矬w的外觀,我們可以計(jì)算從源點(diǎn)到目標(biāo)點(diǎn)的距離,并使用它們來(lái)計(jì)算失真系數(shù),然后使用這些系數(shù)來(lái)修復(fù)圖像。


          下圖顯示了來(lái)自輸出圖像和未失真結(jié)果圖像的示例

          修復(fù)相機(jī)失真

          (所有這些效果都發(fā)生在lib/camera.py文件中),但它是如何工作的?該過(guò)程包括 3 個(gè)步驟:


          對(duì)圖像進(jìn)行采樣:

          在這一步中,我們識(shí)別定義棋盤格的角點(diǎn),以防我們找不到棋盤,或者棋盤不完整,我們將丟棄樣本圖像。


          # first we convert the  image to grayscalegray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
          # Find the chessboard cornersret, corners = cv2.findChessboardCorners(gray, (9, 6), None)


          校準(zhǔn):

          在這一步中,我們從校準(zhǔn)模式的多個(gè)視圖中找到相機(jī)的內(nèi)在和外在參數(shù),然后我們可以使用它們來(lái)生成結(jié)果圖像。

          img_size = (self._valid_images[0].shape[1], self._valid_images[0].shape[0])ret, self._mtx, self._dist, t, t2 = cv2.calibrateCamera(self._obj_points, self._img_points, img_size, None, None)


          不失真:

          在這最后一步中,我們實(shí)際上通過(guò)根據(jù)校準(zhǔn)步驟中檢測(cè)到的參數(shù)補(bǔ)償鏡頭失真來(lái)生成最終圖像。

          cv2.undistort(img, self._mtx, self._dist, None, self._mtx)


          應(yīng)用透視變換來(lái)校正二值圖像(“鳥瞰圖”)。

          該過(guò)程的下一步是更改圖像的視角,從安裝在汽車前部的常規(guī)攝像頭視圖變?yōu)楦┮晥D,也稱為“鳥瞰圖”。這是它的樣子:

          未扭曲的圖像

          這種轉(zhuǎn)換非常簡(jiǎn)單,我們?cè)谄聊簧先∷膫€(gè)我們知道的點(diǎn),然后將它們轉(zhuǎn)換為屏幕的期望位置。讓我們使用上圖的示例更詳細(xì)地回顧一下,在圖片中,我們看到一個(gè)繪制在頂部的綠色形狀,這個(gè)矩形使用四個(gè)源點(diǎn)作為角,它與相機(jī)的常規(guī)直線道路重疊。矩形圍繞圖像的中心切割,因?yàn)橥敢暿墙志巴ǔ=Y(jié)束的地方,以讓位于天空。現(xiàn)在我們把這些點(diǎn)移到屏幕上我們想要的位置,這就是將綠色區(qū)域轉(zhuǎn)換成一個(gè)矩形,從 0 到圖片的高度,下面是我們將在代碼中使用的源點(diǎn)和目標(biāo)點(diǎn):

          height, width, color = img.shape
          src = np.float32([ [210, height], [1110, height], [580, 460], [700, 460]])
          dst = np.float32([ [210, height], [1110, height], [210, 0], [1110, 0]])
          src, dst = self._calc_warp_points(img)
          if self._M is None: self._M = cv2.getPerspectiveTransform(src, dst) self._M_inv = cv2.getPerspectiveTransform(dst, src)
          return cv2.warpPerspective(img, self._M, (width, height), flags=cv2.INTER_LINEAR)


          使用顏色變換、漸變等來(lái)創(chuàng)建閾值二值圖像。

          現(xiàn)在我們已經(jīng)有了圖像,我們需要開始丟棄所有不相關(guān)的信息,只保留線條。為此,我們將應(yīng)用一系列更改,接下來(lái)將詳細(xì)介紹:


          轉(zhuǎn)換為灰度

          將彩色圖像轉(zhuǎn)換為灰度

          return cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)


          增強(qiáng)圖像

          通過(guò)使用高斯模糊對(duì)圖像進(jìn)行平滑,并將原始圖像加權(quán)到平滑后的圖像中,執(zhí)行一些次要但重要的增強(qiáng)

          dst = cv2.GaussianBlur(img, (0, 0), 3)out = cv2.addWeighted(img, 1.5, dst, -0.5, 0)
          使用 Sobel 設(shè)置水平梯度的閾值。

          計(jì)算 X 軸上顏色變化函數(shù)的導(dǎo)數(shù),并應(yīng)用閾值來(lái)過(guò)濾高強(qiáng)度顏色變化,當(dāng)我們使用灰度時(shí),這將是邊界。

          sobel = cv2.Sobel(img, cv2.CV_64F, True, False)abs_sobel = np.absolute(sobel)scaled_sobel = np.uint8(255 * abs_sobel / np.max(abs_sobel))
          return (scaled_sobel >= 20) & (scaled_sobel <= 220)



          使用 Sobel 設(shè)置梯度方向閾值,以便僅檢測(cè)到更接近垂直的邊緣。

          現(xiàn)在我們計(jì)算新閾值的方向?qū)?shù)

          # Calculate the x and y gradientssobel_x = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=sobel_kernel)sobel_y = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=sobel_kernel)
          # Take the absolute value of the x and y gradientsgradient_direction = np.arctan2(np.absolute(sobel_y), np.absolute(sobel_x))gradient_direction = np.absolute(gradient_direction)
          return (gradient_direction >= np.pi/6) & (gradient_direction <= np.pi*5/6)

          接下來(lái),我們將它們組合成一個(gè)漸變

          # combine the gradient and direction thresholds.gradient_condition = ((sx_condition == 1) & (dir_condition == 1))


          顏色閾值

          此過(guò)濾器適用于原始圖像,我們嘗試僅獲取那些黃色/白色的像素(如道路線)

          r_channel = img[:, :, 0]g_channel = img[:, :, 1]return (r_channel > thresh) & (g_channel > thresh)


          L層和S層的HSL閾值

          對(duì)于此任務(wù),有必要更改顏色空間,特別是,我們將使用 HSL 顏色空間,因?yàn)樗鼘?duì)我們使用的圖像具有有趣的特征。

          def _hls_condition(self, img, channel, thresh=(220, 255)):    channels = {        "h": 0,        "l": 1,        "s": 2    }    hls = cv2.cvtColor(img, cv2.COLOR_RGB2HLS)
          hls = hls[:, :, channels[channel]]
          return (hls > thresh[0]) & (hls <= thresh[1])

              最后我們將所有這些組合成一個(gè)最終圖像:

          grey = self._to_greyscale(img)grey = self._enhance(grey)
          # apply gradient threshold on the horizontal gradientsx_condition = self._sobel_gradient_condition(grey, 'x', 20, 220)
          # apply gradient direction threshold so that only edges closer to vertical are detected.dir_condition = self._directional_condition(grey, thresh=(np.pi/6, np.pi*5/6))
          # combine the gradient and direction thresholds.gradient_condition = ((sx_condition == 1) & (dir_condition == 1))
          # and color thresholdcolor_condition = self._color_condition(img, thresh=200)
          # now let's take the HSL thresholdl_hls_condition = self._hls_condition(img, channel='l', thresh=(120, 255))s_hls_condition = self._hls_condition(img, channel='s', thresh=(100, 255))
          combined_condition = (l_hls_condition | color_condition) & (s_hls_condition | gradient_condition)result = np.zeros_like(color_condition)result[combined_condition] = 1

          我們的新圖像現(xiàn)在如下圖所示:

          你已經(jīng)看到那里形成的線條了嗎?


          檢測(cè)車道像素并擬合以找到車道邊界。

          到目前為止,我們已經(jīng)能夠創(chuàng)建一個(gè)由只包含車道特征的鳥瞰圖組成的圖像(至少在大多數(shù)情況下,我們?nèi)匀挥幸恍┰胍簦S辛诉@張新圖像,我們現(xiàn)在可以開始進(jìn)行一些計(jì)算,將圖像轉(zhuǎn)換為我們可以使用的實(shí)際值,例如車道位置和曲率。


          讓我們首先識(shí)別圖像上的像素并構(gòu)建一個(gè)表示車道函數(shù)的多項(xiàng)式。我們打算怎么做?事實(shí)證明,有一個(gè)非常好的方法,使用圖像下半部分的直方圖。下面是直方圖的示例:

          直方圖

          圖像上的峰值幫助我們識(shí)別車道的左側(cè)和右側(cè),以下是在代碼上構(gòu)建直方圖的方式:

          # Take a histogram of the bottom half of the imagehistogram = np.sum(binary_warped[binary_warped.shape[0] // 2:, :], axis=0)
          # Find the peak of the left and right halves of the histogram# These will be the starting point for the left and right linesmidpoint = np.int(histogram.shape[0] // 2)left_x_base = np.argmax(histogram[:midpoint])right_x_base = np.argmax(histogram[midpoint:]) + midpoint

          但我們可能會(huì)問(wèn),為什么只有下半部分?答案是我們只想關(guān)注緊挨著汽車的路段,因?yàn)檐嚨揽赡軙?huì)形成一條曲線,這會(huì)影響我們的直方圖。一旦我們找到離汽車更近的車道位置,我們就可以使用移動(dòng)窗口方法來(lái)找到其余車道,如下圖所示:

          移動(dòng)窗口處理示例

          這是它在代碼中的樣子:

          # Choose the number of sliding windowsnum_windows = 9# Set the width of the windows +/- marginmargin = 50# Set minimum number of pixels found to recenter windowmin_pix = 100
          # Set height of windows - based on num_windows above and image shapewindow_height = np.int(binary_warped.shape[0] // num_windows)
          # Current positions to be updated later for each window in nwindowsleft_x_current = left_x_baseright_x_current = right_x_base
          # Create empty lists to receive left and right lane pixel indicesleft_lane_inds = []right_lane_inds = []
          # Step through the windows one by onefor window in range(num_windows): # Identify window boundaries in x and y (and right and left) win_y_low = binary_warped.shape[0] - (window + 1) * window_height win_y_high = binary_warped.shape[0] - window * window_height win_x_left_low = left_x_current - margin win_x_left_high = left_x_current + margin win_x_right_low = right_x_current - margin win_x_right_high = right_x_current + margin
          if self._debug: # Draw the windows on the visualization image cv2.rectangle(out_img, (win_x_left_low, win_y_low), (win_x_left_high, win_y_high), (0, 255, 0), 2) cv2.rectangle(out_img, (win_x_right_low, win_y_low), (win_x_right_high, win_y_high), (0, 255, 0), 2)
          # Identify the nonzero pixels in x and y within the window # good_left_inds = ((nonzero_y >= win_y_low) & (nonzero_y < win_y_high) & (nonzero_x >= win_x_left_low) & (nonzero_x < win_x_left_high)).nonzero()[0] good_right_inds = ((nonzero_y >= win_y_low) & (nonzero_y < win_y_high) & (nonzero_x >= win_x_right_low) & (nonzero_x < win_x_right_high)).nonzero()[0]
          # Append these indices to the lists left_lane_inds.append(good_left_inds) right_lane_inds.append(good_right_inds)
          # If you found > min_pix pixels, recenter next window on their mean position if len(good_left_inds) > min_pix: left_x_current = np.int(np.mean(nonzero_x[good_left_inds])) if len(good_right_inds) > min_pix: right_x_current = np.int(np.mean(nonzero_x[good_right_inds]))
          # Concatenate the arrays of indices (previously was a list of lists of pixels)try: left_lane_inds = np.concatenate(left_lane_inds) right_lane_inds = np.concatenate(right_lane_inds)except ValueError: # Avoids an error if the above is not implemented fully pass

              

          這個(gè)過(guò)程非常緊急,所以在處理視頻時(shí),我們可以調(diào)整一些事情,因?yàn)槲覀儾⒉豢偸切枰獜牧汩_始,之前進(jìn)行的計(jì)算為我們提供了下一個(gè)車道的窗口,因此更容易找到. 所有這些都在存儲(chǔ)庫(kù)的最終代碼中實(shí)現(xiàn),請(qǐng)隨意查看。


          一旦我們有了所有的窗口,我們現(xiàn)在可以使用所有確定的點(diǎn)構(gòu)建多項(xiàng)式,每條線(左和右)將獨(dú)立計(jì)算如下:

          left_fit = np.polyfit(left_y, left_x, 2)right_fit = np.polyfit(right_y, right_x, 2)

          數(shù)字 2 表示二階多項(xiàng)式。


          確定車道的曲率和車輛相對(duì)于中心的位置。

          現(xiàn)在我們知道了線條在圖像上的位置,并且我們知道汽車的位置(在相機(jī)的中心)我們可以做一些有趣的計(jì)算來(lái)確定車道的曲率和汽車相對(duì)于中心的位置車道的。


          車道曲率

          車道曲率是一個(gè)簡(jiǎn)單的多項(xiàng)式計(jì)算。

          fit_cr = np.polyfit(self.all_y * self._ym_per_pix, self.all_x * self._xm_per_pix, 2)plot_y = np.linspace(0, 720 - 1, 720)y_eval = np.max(plot_y)
          curve = ((1 + (2 * fit_cr[0] * y_eval * self._ym_per_pix + fit_cr[1]) ** 2) ** 1.5) / np.absolute(2 * fit_cr[0])

          但是有一個(gè)重要的考慮因素,對(duì)于這一步,我們不能在像素上工作,我們需要找到一種將像素轉(zhuǎn)換為米的方法,因此我們引入了 2 個(gè)變量:_ym_per_pix 和 _xm_per_pix,它們是預(yù)定義的值。

          self._xm_per_pix = 3.7 / 1280self._ym_per_pix = 30 / 720


          車輛相對(duì)于中心的位置

          非常簡(jiǎn)單,計(jì)算車道中間的位置,并將其與圖像中心進(jìn)行比較,如下所示

          lane_center = (self.left_lane.best_fit[-1] + self.right_lane.best_fit[-1]) / 2car_center = img.shape[1] / 2dx = (car_center - lane_center) * self._xm_per_pix


          全部完成!

          現(xiàn)在我們擁有了所需的所有信息,以及表示車道的多項(xiàng)式,最終結(jié)果應(yīng)如下所示:






          Github代碼連接:
          https://github.com/bajcmartinez/Finding-Car-Lanes-Without-Deep-Learning


          下載1:OpenCV-Contrib擴(kuò)展模塊中文版教程
          在「小白學(xué)視覺」公眾號(hào)后臺(tái)回復(fù):擴(kuò)展模塊中文教程,即可下載全網(wǎng)第一份OpenCV擴(kuò)展模塊教程中文版,涵蓋擴(kuò)展模塊安裝、SFM算法、立體視覺、目標(biāo)跟蹤、生物視覺、超分辨率處理等二十多章內(nèi)容。

          下載2:Python視覺實(shí)戰(zhàn)項(xiàng)目52講
          小白學(xué)視覺公眾號(hào)后臺(tái)回復(fù):Python視覺實(shí)戰(zhàn)項(xiàng)目即可下載包括圖像分割、口罩檢測(cè)、車道線檢測(cè)、車輛計(jì)數(shù)、添加眼線、車牌識(shí)別、字符識(shí)別、情緒檢測(cè)、文本內(nèi)容提取、面部識(shí)別等31個(gè)視覺實(shí)戰(zhàn)項(xiàng)目,助力快速學(xué)校計(jì)算機(jī)視覺。

          下載3:OpenCV實(shí)戰(zhàn)項(xiàng)目20講
          小白學(xué)視覺公眾號(hào)后臺(tái)回復(fù):OpenCV實(shí)戰(zhàn)項(xiàng)目20講,即可下載含有20個(gè)基于OpenCV實(shí)現(xiàn)20個(gè)實(shí)戰(zhàn)項(xiàng)目,實(shí)現(xiàn)OpenCV學(xué)習(xí)進(jìn)階。

          交流群


          歡迎加入公眾號(hào)讀者群一起和同行交流,目前有SLAM、三維視覺、傳感器、自動(dòng)駕駛、計(jì)算攝影、檢測(cè)、分割、識(shí)別、醫(yī)學(xué)影像、GAN算法競(jìng)賽等微信群(以后會(huì)逐漸細(xì)分),請(qǐng)掃描下面微信號(hào)加群,備注:”昵稱+學(xué)校/公司+研究方向“,例如:”張三 + 上海交大 + 視覺SLAM“。請(qǐng)按照格式備注,否則不予通過(guò)。添加成功后會(huì)根據(jù)研究方向邀請(qǐng)進(jìn)入相關(guān)微信群。請(qǐng)勿在群內(nèi)發(fā)送廣告,否則會(huì)請(qǐng)出群,謝謝理解~


          瀏覽 45
          點(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>
                  豆花av电影在线 豆花视频精品一区 | sese999 | 国产伦精品一区二区三区竹菊视频 | 黑人操亚洲人 | 久久成人免费观看网站 |