OpenPano не сшивает текст, так как не может получить достаточное количество характерных точек (или ключевых точек ) для выполнения процесса сшивания.
Для сшивания текста не требуется метод сопоставления, устойчивый к поворотам, а только к переводам. OpenCV удобно предлагает такую функцию. Он называется: Сопоставление с шаблонома>.
Решение, которое я разработаю, основано на этой функции OpenCV.
Трубопровод
Теперь я объясню основные шаги моего решения (для получения дополнительной информации, пожалуйста, взгляните на приведенный ниже код).
Процесс сопоставления
Чтобы сопоставить два последовательных изображения (выполнено в функции matchImages
, см. код ниже):
- Мы создаем изображение template, взяв 45 % (
H_templ_ratio
) от первого изображения, как показано ниже:
Этот шаг выполняется в моем коде функцией genTemplate
.
- Мы добавляем черные поля ко второму изображению (там, где мы хотим найти шаблон). Этот шаг необходим, если текст не выровнен на входных изображениях (хотя это относится и к этим образцам изображений). Вот как выглядит изображение после обработки полей. Как видите, поля нужны только ниже и выше изображения:
Изображение template теоретически можно найти в любом месте этого изображения с полями. Этот процесс выполняется в функции addBlackMargins
.
- Мы применяем хитрый фильтр к обоим template изображение и изображение, где мы хотим его найти (выполняется внутри функции
Mat2Edges
). Это повысит надежность процесса сопоставления. Вот пример:
- Мы сопоставляем шаблон с изображением, используя
matchTemplate
и мы получаем местоположение наилучшего соответствия с помощью minMaxLoc
функция.
Вычисление окончательного размера изображения
Этот шаг состоит в вычислении размера окончательной матрицы, в которой мы будем соединять все изображения вместе. Это особенно необходимо, если все входные изображения не имеют одинаковой высоты.
Этот шаг выполняется внутри функции calcFinalImgSize
. Я не буду вдаваться в подробности здесь, потому что, хотя это выглядит немного сложно (по крайней мере, для меня), это всего лишь простая математика (сложение, вычитание, умножение). Возьмите ручку и бумагу, если хотите понять формулы.
Процесс сшивания
Как только у нас есть соответствующие местоположения для каждого входного изображения, нам нужно только выполнить простые математические действия, чтобы скопировать исходные изображения в правильном месте финальное изображение. Опять же, я рекомендую вам проверить код для деталей реализации (см. функцию stitchImages
).
Результаты
Вот результат с вашими входными изображениями:
Как видите, результат не идеальный по пикселям, но он должен быть достаточно хорошим для распознавание.
А вот еще один результат с входными изображениями разной высоты:
Код (Питон)
Моя программа написана на Python и использует модули cv2
(OpenCV) и numpy
. Однако его можно (легко) портировать на другие языки, такие как C++, Java и C#.
import numpy as np
import cv2
def genTemplate(img):
global H_templ_ratio
# we get the image's width and height
h, w = img.shape[:2]
# we compute the template's bounds
x1 = int(float(w)*(1-H_templ_ratio))
y1 = 0
x2 = w
y2 = h
return(img[y1:y2,x1:x2]) # and crop the input image
def mat2Edges(img): # applies a Canny filter to get the edges
edged = cv2.Canny(img, 100, 200)
return(edged)
def addBlackMargins(img, top, bottom, left, right): # top, bottom, left, right: margins width in pixels
h, w = img.shape[:2]
result = np.zeros((h+top+bottom, w+left+right, 3), np.uint8)
result[top:top+h,left:left+w] = img
return(result)
# return the y_offset of the first image to stitch and the final image size needed
def calcFinalImgSize(imgs, loc):
global V_templ_ratio, H_templ_ratio
y_offset = 0
max_margin_top = 0; max_margin_bottom = 0 # maximum margins that will be needed above and bellow the first image in order to stitch all the images into one mat
current_margin_top = 0; current_margin_bottom = 0
h_init, w_init = imgs[0].shape[:2]
w_final = w_init
for i in range(0,len(loc)):
h, w = imgs[i].shape[:2]
h2, w2 = imgs[i+1].shape[:2]
# we compute the max top/bottom margins that will be needed (relatively to the first input image) in order to stitch all the images
current_margin_top += loc[i][1] # here, we assume that the template top-left corner Y-coordinate is 0 (relatively to its original image)
current_margin_bottom += (h2 - loc[i][1]) - h
if(current_margin_top > max_margin_top): max_margin_top = current_margin_top
if(current_margin_bottom > max_margin_bottom): max_margin_bottom = current_margin_bottom
# we compute the width needed for the final result
x_templ = int(float(w)*H_templ_ratio) # x-coordinate of the template relatively to its original image
w_final += (w2 - x_templ - loc[i][0]) # width needed to stitch all the images into one mat
h_final = h_init + max_margin_top + max_margin_bottom
return (max_margin_top, h_final, w_final)
# match each input image with its following image (1->2, 2->3)
def matchImages(imgs, templates_loc):
for i in range(0,len(imgs)-1):
template = genTemplate(imgs[i])
template = mat2Edges(template)
h_templ, w_templ = template.shape[:2]
# Apply template Matching
margin_top = margin_bottom = h_templ; margin_left = margin_right = 0
img = addBlackMargins(imgs[i+1],margin_top, margin_bottom, margin_left, margin_right) # we need to enlarge the input image prior to call matchTemplate (template needs to be strictly smaller than the input image)
img = mat2Edges(img)
res = cv2.matchTemplate(img,template,cv2.TM_CCOEFF) # matching function
_, _, _, templ_pos = cv2.minMaxLoc(res) # minMaxLoc gets the best match position
# as we added margins to the input image we need to subtract the margins width to get the template position relatively to the initial input image (without the black margins)
rectified_templ_pos = (templ_pos[0]-margin_left, templ_pos[1]-margin_top)
templates_loc.append(rectified_templ_pos)
print("max_loc", rectified_templ_pos)
def stitchImages(imgs, templates_loc):
y_offset, h_final, w_final = calcFinalImgSize(imgs, templates_loc) # we calculate the "surface" needed to stitch all the images into one mat (and y_offset, the Y offset of the first image to be stitched)
result = np.zeros((h_final, w_final, 3), np.uint8)
#initial stitch
h_init, w_init = imgs[0].shape[:2]
result[y_offset:y_offset+h_init, 0:w_init] = imgs[0]
origin = (y_offset, 0) # top-left corner of the last stitched image (y,x)
# stitching loop
for j in range(0,len(templates_loc)):
h, w = imgs[j].shape[:2]
h2, w2 = imgs[j+1].shape[:2]
# we compute the coordinates where to stitch imgs[j+1]
y1 = origin[0] - templates_loc[j][1]
y2 = origin[0] - templates_loc[j][1] + h2
x_templ = int(float(w)*(1-H_templ_ratio)) # x-coordinate of the template relatively to its original image's right side
x1 = origin[1] + x_templ - templates_loc[j][0]
x2 = origin[1] + x_templ - templates_loc[j][0] + w2
result[y1:y2, x1:x2] = imgs[j+1] # we copy the input image into the result mat
origin = (y1,x1) # we update the origin point with the last stitched image
return(result)
if __name__ == '__main__':
# input images
part1 = cv2.imread('part1.jpg')
part2 = cv2.imread('part2.jpg')
part3 = cv2.imread('part3.jpg')
imgs = [part1, part2, part3]
H_templ_ratio = 0.45 # H_templ_ratio: horizontal ratio of the input that we will keep to create a template
templates_loc = [] # templates location
matchImages(imgs, templates_loc)
result = stitchImages(imgs, templates_loc)
cv2.imshow("result", result)
person
Elouarn Laine
schedule
11.08.2017