Мы начали наше путешествие с первой главы Глава 1 », которая была основным упражнением. В этой главе мы перейдем к чему-то более сложному, но перед этим немного теории.

В конце этой статьи вы найдете ссылку на проект на Github и видео с результатами.

Урок 1: Калибровка камеры

Искажение

Искажение изображения происходит, когда камера смотрит на трехмерные объекты в реальном мире и преобразует их в двухмерное изображение; это преобразование несовершенно. Искажение фактически меняет форму и размер этих трехмерных объектов. Итак, первым шагом в анализе изображений с камеры является устранение этого искажения, чтобы вы могли получить из них правильную и полезную информацию.

Почему важно исправить искажение изображения?

  • Искажение может изменить видимый размер объекта на изображении.
  • Искажение может изменить видимую форму объекта на изображении.
  • Искажение может привести к изменению внешнего вида объекта в зависимости от того, где он находится в поле зрения.
  • Искажение может сделать объекты ближе или дальше, чем они есть на самом деле.

Типы искажений

В реальных камерах для формирования изображения используются изогнутые линзы, и световые лучи часто слишком сильно или слишком мало изгибаются по краям этих линз. Это создает эффект, искажающий края изображений, так что линии или объекты выглядят более или менее изогнутыми, чем они есть на самом деле. Это называется радиальным искажением и является наиболее распространенным типом искажения.

Другой тип искажения - это тангенциальное искажение. Это происходит, когда объектив камеры не выровнен идеально параллельно плоскости изображения, где находится пленка или датчик камеры. Это заставляет изображение выглядеть наклонным, так что некоторые объекты кажутся дальше или ближе, чем они есть на самом деле.

Коэффициенты искажения и коррекция

Для коррекции радиального искажения необходимы три коэффициента: k1, k2 и k3. Чтобы исправить появление на изображении радиально искаженных точек, можно использовать формулу коррекции.

В следующих уравнениях (x, y) - точка на искаженном изображении. Чтобы неискажать эти точки, OpenCV вычисляет r, которое представляет собой известное расстояние между точкой на неискаженном (скорректированном) изображении (Xcorrected, Ycorrected) и центром искажения изображения, который часто является центром этого изображения (Xc, Yc). . Эту центральную точку (xc, yc) иногда называют центром искажения. Эти точки изображены ниже.

Итак, мы знаем, что искажения изменяют размер и форму объектов на изображении, но как нам для этого откалибровать? Мы можем выбрать любую форму для калибровки камеры, и мы будем использовать шахматную доску.

Поиск углов

Мы будем использовать функции OpenCV findChessboardCorners () и drawChessboardCorners (), чтобы автоматически находить и рисовать углы в изображении рисунка шахматной доски.

Чтобы узнать больше об обеих этих функциях, вы можете ознакомиться с документацией OpenCV здесь: cv2.findChessboardCorners () и cv2.drawChessboardCorners ().

Применяя эти две функции к образцу изображения, вы получите следующий результат:

Калибровка камеры

Примечание относительно угловых координат

Поскольку исходный угол равен (0,0,0), последний угол находится (6,4,0) относительно этого угла, а не (7,5,0).

Примеры полезного кода

Преобразование изображения, импортированного cv2 или glob API, в оттенки серого:

gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

Примечание. Если вы читаете изображение с помощью mpimg.imread (), оно будет считываться в изображении RGB, и вам следует преобразовать его в оттенки серого с помощью cv2.COLOR_RGB2GRAY , но если вы используете cv2.imread () или glob API, как это происходит в этом видео-примере, это будет читаться в изображении BGR, и вы должны преобразовать его в оттенки серого с помощью cv2.COLOR_BGR2GRAY. Мы узнаем больше о преобразовании цветов позже в этом уроке, но помните об этом, когда будете писать собственный код и просматривать примеры кода.

Нахождение углов шахматной доски (для доски 8х6):

ret, corners = cv2.findChessboardCorners(gray, (8,6), None)

Рисование обнаруженных углов на изображении:

img = cv2.drawChessboardCorners(img, (8,6), corners, ret)

Калибровка камеры, заданные точки объекта, точки изображения и форма изображения в градациях серого:

ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)

Отмена искажения тестового изображения:

dst = cv2.undistort(img, mtx, dist, None, mtx)

Примечание о форме изображения

Форма изображения, которая передается в функцию calibrateCamera, - это просто высота и ширина изображения. Один из способов получить эти значения - получить их из массива формы серого изображения gray.shape [:: - 1]. Это возвращает ширину и высоту изображения в пикселях, например (1280, 960).

Другой способ получить форму изображения - получить их непосредственно из изображения color, извлекая первые два значения в массиве форм цветного изображения с помощью img.shape [1 :: - 1]. Этот фрагмент кода запрашивает только первые два значения в массиве фигур и меняет их местами. Обратите внимание, что в нашем случае мы работаем с изображением в оттенках серого, поэтому у нас есть только два измерения (у цветных изображений три, высота, ширина и глубина), поэтому в этом нет необходимости.

Важно использовать всю фигуру изображения в градациях серого или первые два значения формы цветного изображения. Это связано с тем, что вся форма цветного изображения будет включать третье значение - количество цветовых каналов - в дополнение к высоте и ширине изображения. Например, массив формы цветного изображения может быть (960, 1280, 3), который представляет собой высоту и ширину изображения в пикселях (960, 1280), и третье значение (3), которое представляет три цветовых канала в цвете. изображение, о котором вы узнаете больше позже, и если вы попытаетесь передать эти три значения в функцию calibrateCamera, вы получите сообщение об ошибке.

Исправление искажений

Этот процесс состоит из двух основных шагов: используйте изображения шахматной доски для получения точек изображения и точек объекта, а затем используйте функции OpenCV cv2.calibrateCamera () и cv2.undistort () для вычисления калибровки и неискажения.

Пример кода:

import pickle
import cv2
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.image as mpimg


# Read in the saved objpoints and imgpoints
dist_pickle = pickle.load( open( "wide_dist_pickle.p", "rb" ) )
objpoints = dist_pickle["objpoints"]
imgpoints = dist_pickle["imgpoints"]


# Read in an image
img = cv2.imread('test_image.png')
img_size = (img.shape[1], img.shape[0])
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Find the chessboard corners
ret, corners = cv2.findChessboardCorners(gray, (8,6), None)


    # If found, add object points, image points
if ret == True:
    objpoints.append(objp)
    imgpoints.append(corners)


    # Draw and display the corners
    cv2.drawChessboardCorners(img, (8,6), corners, ret)
    
def cal_undistort(img, objpoints, imgpoints):
    ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, img_size,None,None)
    undist = cv2.undistort(img, mtx, dist, None, mtx)
    return undist


undistorted = cal_undistort(img, objpoints, imgpoints)


f, (ax1, ax2) = plt.subplots(1, 2, figsize=(24, 9))
f.tight_layout()
ax1.imshow(img)
ax1.set_title('Original Image', fontsize=50)
ax2.imshow(undistorted)

ax2.set_title('Undistorted Image', fontsize=50)
plt.subplots_adjust(left=0., right=1, top=0.9, bottom=0.)

Расчет кривизны полосы движения

Беспилотным автомобилям необходимо указать правильный угол поворота рулевого колеса влево или вправо. Вы можете рассчитать этот угол, если знаете кое-что о скорости и динамике автомобиля, а также о том, насколько сильно изгибается полоса движения.

Один из способов вычислить кривизну линии полосы движения - это подогнать полином 2-й степени к этой линии, и из этого вы можете легко извлечь полезную информацию.

Для линии полосы движения, близкой к вертикали, вы можете разместить линию, используя следующую формулу:

f (y) = Ay² + By + C, где A, B и C - коэффициенты.

A дает вам кривизну линии полосы движения, B дает вам направление или направление, на которое указывает линия, а C дает вам положение линии в зависимости от того, как далеко она находится от самого левого края изображения (y = 0 ).

Перспективное преобразование

Перспективное преобразование сопоставляет точки данного изображения с разными желаемыми точками изображения с новой перспективой. Перспективное преобразование, которое вас больше всего заинтересует, - это преобразование вида с высоты птичьего полета, которое позволяет нам рассматривать полосу сверху; это будет полезно для расчета кривизны полосы движения позже. Помимо создания представления изображения с высоты птичьего полета, преобразование перспективы также может использоваться для всех видов различных точек обзора.

Преобразование знака остановки

Примеры полезного кода

Вычислите перспективное преобразование M, учитывая исходную и конечную точки:

M = cv2.getPerspectiveTransform(src, dst)

Вычислите обратное перспективное преобразование:

Minv = cv2.getPerspectiveTransform(dst, src)

Деформируйте изображение, используя перспективное преобразование, M:

warped = cv2.warpPerspective(img, M, img_size, flags=cv2.INTER_LINEAR)

Небольшое резюме:

Отменить искажение и преобразовать перспективу

Теперь мы увидели, как находить углы, откалибровать камеру, отменить искажение изображения и применить перспективное преобразование.

Чтобы применить это, нам нужно написать функцию, которая принимает ваше искаженное изображение в качестве входных данных и выполняет следующие шаги:

  • Отмените искажение изображения с помощью cv2.undistort () с mtx и dist
  • Преобразовать в оттенки серого
  • Найдите углы шахматной доски
  • Нарисуйте углы
  • Определите 4 исходные точки (внешние 4 угла, обнаруженные в шаблоне шахматной доски)
  • Определите 4 точки назначения (должны быть указаны в том же порядке, что и точки src!)
  • Используйте cv2.getPerspectiveTransform (), чтобы получить M, матрицу преобразования
  • используйте cv2.warpPerspective (), чтобы применить M и преобразовать изображение в вид сверху вниз

Пример кода

import pickle
import cv2
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.image as mpimg


# Read in the saved camera matrix and distortion coefficients
# These are the arrays you calculated using cv2.calibrateCamera()
dist_pickle = pickle.load( open( "wide_dist_pickle.p", "rb" ) )
mtx = dist_pickle["mtx"]
dist = dist_pickle["dist"]


# Read in an image
img = cv2.imread('test_image2.png')
nx = 8 # the number of inside corners in x
ny = 6 # the number of inside corners in y



def corners_unwarp(img, nx, ny, mtx, dist):
    # Use the OpenCV undistort() function to remove distortion
    undist = cv2.undistort(img, mtx, dist, None, mtx)
    # Convert undistorted image to grayscale
    gray = cv2.cvtColor(undist, cv2.COLOR_BGR2GRAY)
    # Search for corners in the grayscaled image
    ret, corners = cv2.findChessboardCorners(gray, (nx, ny), None)


    if ret == True:
        # If we found corners, draw them! (just for fun)
        cv2.drawChessboardCorners(undist, (nx, ny), corners, ret)
        # Choose offset from image corners to plot detected corners
        # This should be chosen to present the result at the proper aspect ratio
        # My choice of 100 pixels is not exact, but close enough for our purpose here
        offset = 100 # offset for dst points
        # Grab the image shape
        img_size = (gray.shape[1], gray.shape[0])


        # For source points I'm grabbing the outer four detected corners
        src = np.float32([corners[0], corners[nx-1], corners[-1], corners[-nx]])
        # For destination points, I'm arbitrarily choosing some points to be
        # a nice fit for displaying our warped result 
        # again, not exact, but close enough for our purposes
        dst = np.float32([[offset, offset], [img_size[0]-offset, offset], 
                                     [img_size[0]-offset, img_size[1]-offset], 
                                     [offset, img_size[1]-offset]])
        # Given src and dst points, calculate the perspective transform matrix
        M = cv2.getPerspectiveTransform(src, dst)
        # Warp the image using OpenCV warpPerspective()
        warped = cv2.warpPerspective(undist, M, img_size)


    # Return the resulting image and matrix
    return warped, M




top_down, perspective_M = corners_unwarp(img, nx, ny, mtx, dist)
f, (ax1, ax2) = plt.subplots(1, 2, figsize=(24, 9))
f.tight_layout()
ax1.imshow(img)
ax1.set_title('Original Image', fontsize=50)
ax2.imshow(top_down)
ax2.set_title('Undistorted and Warped Image', fontsize=50)
plt.subplots_adjust(left=0., right=1, top=0.9, bottom=0.)

Урок 2: Градиенты и цветовые пространства

Здесь мы узнаем, как использовать пороги градиента и различные цветовые пространства, чтобы легче идентифицировать разметку полос на дороге.

Это примеры операторов Собеля с размером ядра 3 (в каждом случае подразумевается оператор 3 x 3). Это минимальный размер, но размер ядра может быть любым нечетным числом. Более крупное ядро ​​подразумевает использование градиента по большей области изображения или, другими словами, более плавный градиент.

Чтобы понять, как эти операторы берут производную, вы можете подумать о наложении одного из них на область изображения 3 x 3. Если изображение в этой области является плоским (т.е. есть небольшое изменение значений в данной области), то результат (суммирование поэлементного произведения оператора и соответствующих пикселей изображения) будет равен нулю.

В этом случае сумма этой матрицы равна 0, что подразумевает плоский градиент (в направлении x в этом вычислении, хотя направление y также равно нулю в этом примере).

Если вместо этого, например, вы примените оператор Sx к области изображения, где значения растут слева направо, то результат будет положительным, что подразумевает положительную производную.

Визуальный пример

Если мы применим к этому изображению операторы Sobel x и y:

А потом берем абсолютное значение, получаем результат:

x vs. y

На изображениях выше вы можете видеть, что градиенты, снятые как по оси x, так и по оси y, обнаруживают линии полос движения и захватывают другие края. Использование градиента в направлении x подчеркивает края, расположенные ближе к вертикали. В качестве альтернативы, если взять градиент в направлении y, края будут ближе к горизонтали.

В следующих упражнениях вы напишете функции, принимающие различные пороговые значения градиентов x и y. Вот код, который может быть полезен:

Примеры полезного кода

Вам необходимо передать один цветовой канал функции cv2.Sobel (), поэтому сначала преобразуйте его в оттенки серого:

gray = cv2.cvtColor(im, cv2.COLOR_RGB2GRAY)

Вычислите производную по направлению x (1, 0 в конце обозначает направление x):

sobelx = cv2.Sobel(gray, cv2.CV_64F, 1, 0)

Вычислите производную по оси y (0, 1 в конце обозначает направление y):

sobely = cv2.Sobel(gray, cv2.CV_64F, 0, 1)

Вычислить абсолютное значение производной x:

abs_sobelx = np.absolute(sobelx)

Преобразуйте изображение абсолютного значения в 8-битное:

scaled_sobel = np.uint8(255*abs_sobelx/np.max(abs_sobelx))

Примечание. и вы хотите, чтобы он работал одинаково с входными изображениями разного масштаба, такими как jpg и png. Вы также можете выбрать другой стандартный диапазон значений, например от 0 до 1 и т. Д.

Создайте бинарный порог для выбора пикселей на основе силы градиента:

thresh_min = 20 thresh_max = 100 sxbinary = np.zeros_like(scaled_sobel) sxbinary[(scaled_sobel >= thresh_min) & (scaled_sobel <= thresh_max)] = 1 plt.imshow(sxbinary, cmap='gray')

Результат

Применение SOBEL

import numpy as np
import cv2
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import pickle




# Read in an image and grayscale it
image = mpimg.imread('signs_vehicles_xygrad.png')


# Define a function that applies Sobel x or y, 
# then takes an absolute value and applies a threshold.
# Note: calling your function with orient='x', thresh_min=20, thresh_max=100
# should produce output like the example image shown above this quiz.
def abs_sobel_thresh(img, orient='x', thresh_min=0, thresh_max=255):
    # Convert to grayscale
    gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
    # Apply x or y gradient with the OpenCV Sobel() function
    # and take the absolute value
    if orient == 'x':
        abs_sobel = np.absolute(cv2.Sobel(gray, cv2.CV_64F, 1, 0))
    if orient == 'y':
        abs_sobel = np.absolute(cv2.Sobel(gray, cv2.CV_64F, 0, 1))
    # Rescale back to 8 bit integer
    scaled_sobel = np.uint8(255*abs_sobel/np.max(abs_sobel))
    # Create a copy and apply the threshold
    binary_output = np.zeros_like(scaled_sobel)
    # Here I'm using inclusive (>=, <=) thresholds, but exclusive is ok too
    binary_output[(scaled_sobel >= thresh_min) & (scaled_sobel <= thresh_max)] = 1


    # Return the result
    return binary_output
    
# Run the function
grad_binary = abs_sobel_thresh(image, orient='x', thresh_min=20, thresh_max=100)
# Plot the result
f, (ax1, ax2) = plt.subplots(1, 2, figsize=(24, 9))
f.tight_layout()
ax1.imshow(image)
ax1.set_title('Original Image', fontsize=50)
ax2.imshow(grad_binary, cmap='gray')
ax2.set_title('Thresholded Gradient', fontsize=50)
plt.subplots_adjust(left=0., right=1, top=0.9, bottom=0.)

Ссылка проекта и видео результата

Я не хотел вдаваться в подробности и давать вам больше теории, однако я предоставил в ваше распоряжение ссылку на проект с деталями кода, вы найдете все, что вам нужно, в файле writeup.md, а также в полном конвейере файл.

Ссылка на проект на Github

Мой конвейер довольно надежен, он правильно определяет полосы даже при прохождении сквозь тени и изменении цвета асфальта.

Источник: Udacity

Первоначально опубликовано на https://www.linkedin.com.