Аффинное преобразование между контурами в OpenCV

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

from pylab import *
import cv2
import urllib

urllib.urlretrieve('http://geoport.whoi.edu/images/frame014.png','frame014.png');
urllib.urlretrieve('http://geoport.whoi.edu/images/frame015.png','frame015.png');

gray1=cv2.imread('frame014.png',0)
gray2=cv2.imread('frame015.png',0)
figure(figsize=(14,6))
subplot(121);imshow(gray1,cmap=cm.gray);
subplot(122);imshow(gray2,cmap=cm.gray);

enter image description here

Я хочу использовать черную область слева от каждого изображения, чтобы сделать регистрацию, так как эта область была внутри камеры и должна быть исправлена ​​вовремя. Поэтому мне просто нужно вычислить аффинное преобразование между черными областями.

Я определил эти области по пороговым значениям и нахожу самый большой контур:

def find_biggest_contour(gray,threshold=40):
    # threshold a grayscale image 
    ret,thresh = cv2.threshold(gray,threshold,255,1)
    # find the contours
    contours,h = cv2.findContours(thresh,mode=cv2.RETR_LIST,method=cv2.CHAIN_APPROX_NONE)
    # measure the perimeter
    perim = [cv2.arcLength(cnt,True) for cnt in contours]
    # find contour with largest perimeter
    i=perim.index(max(perim))
    return contours[i]

c1=find_biggest_contour(gray1)
c2=find_biggest_contour(gray2)

x1=c1[:,0,0];y1=c1[:,0,1]
x2=c2[:,0,0];y2=c2[:,0,1]

figure(figsize=(8,8))
imshow(gray1,cmap=cm.gray, alpha=0.5);plot(x1,y1,'b-')
imshow(gray2,cmap=cm.gray, alpha=0.5);plot(x2,y2,'g-')
axis([0,1500,1000,0]);

enter image description here

Синий - самый длинный контур из 1-го кадра, зеленый - самый длинный контур из 2-го кадра.

Каков наилучший способ определения вращения и смещения между синими и зелеными контурами?

Я хочу использовать только правую часть контуров в некоторой области вокруг шага, что-то вроде области между стрелками.

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

Ответ 1

Следуя предложенному Шамбулу подходу, вот что я придумал. Я использовал алгоритм Рамера-Дугласа-Пьюкера для упрощения контура в интересующей области и определил два поворотных момента. Я собирался использовать два поворотных пункта, чтобы получить три моих неизвестных (xoffset, yoffset и угол поворота), но второй поворотный момент немного слишком далеко вправо, поскольку RDP упростил более плавную кривую в этом регионе. Поэтому вместо этого я использовал угол сегмента линии, ведущий к 1-й поворотной точке. Различие этого угла между image1 и изображением2 дает мне угол поворота. Я все еще не совсем доволен этим решением. Он работал достаточно хорошо для этих двух изображений, но я не уверен, что он будет хорошо работать во всей последовательности изображений. Посмотрим.

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

# select region of interest from largest contour 
ind1=where((x1>190.) & (y1>200.) & (y1<900.))[0]
ind2=where((x2>190.) & (y2>200.) & (y2<900.))[0]
figure(figsize=(10,10))
imshow(gray1,cmap=cm.gray, alpha=0.5);plot(x1[ind1],y1[ind1],'b-')
imshow(gray2,cmap=cm.gray, alpha=0.5);plot(x2[ind2],y2[ind2],'g-')
axis([0,1500,1000,0])

enter image description here

def angle(x1,y1):
    #  Returns angle of each segment along an (x,y) track
    return array([math.atan2(y,x) for (y,x) in zip(diff(y1),diff(x1))])

def simplify(x,y, tolerance=40, min_angle = 60.*pi/180.): 
    """
    Use the Ramer-Douglas-Peucker algorithm to simplify the path
    http://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm
    Python implementation: https://github.com/sebleier/RDP/
    """
    from RDP import rdp   
    points=vstack((x,y)).T
    simplified = array(rdp(points.tolist(), tolerance))
    sx, sy = simplified.T

    theta=abs(diff(angle(sx,sy)))
    # Select the index of the points with the greatest theta
    # Large theta is associated with greatest change in direction.
    idx = where(theta>min_angle)[0]+1
    return sx,sy,idx

sx1,sy1,i1 = simplify(x1[ind1],y1[ind1])
sx2,sy2,i2 = simplify(x2[ind2],y2[ind2])
fig = plt.figure(figsize=(10,6))
ax =fig.add_subplot(111)

ax.plot(x1, y1, 'b-', x2, y2, 'g-',label='original path')
ax.plot(sx1, sy1, 'ko-', sx2, sy2, 'ko-',lw=2, label='simplified path')
ax.plot(sx1[i1], sy1[i1], 'ro', sx2[i2], sy2[i2], 'ro', 
    markersize = 10, label='turning points')
ax.invert_yaxis()
plt.legend(loc='best')

enter image description here

# determine x,y offset between 1st turning points, and 
# angle from difference in slopes of line segments approaching 1st turning point
xoff = sx2[i2[0]] - sx1[i1[0]]
yoff = sy2[i2[0]] - sy1[i1[0]]
iseg1 = [i1[0]-1, i1[0]]
iseg2 = [i2[0]-1, i2[0]]
ang1 = angle(sx1[iseg1], sy1[iseg1])
ang2 = angle(sx2[iseg2], sy2[iseg2])
ang = -(ang2[0] - ang1[0])
print xoff, yoff, ang*180.*pi

-28 14 5.07775871644

# 2x3 affine matrix M
M=array([cos(ang),sin(ang),xoff,-sin(ang),cos(ang),yoff]).reshape(2,3)
print M

[[  9.99959685e-01   8.97932821e-03  -2.80000000e+01]
 [ -8.97932821e-03   9.99959685e-01   1.40000000e+01]]

# warp 2nd image into coordinate frame of 1st
Minv = cv2.invertAffineTransform(M)
gray2b = cv2.warpAffine(gray2,Minv,shape(gray2.T))

figure(figsize=(10,10))
imshow(gray1,cmap=cm.gray, alpha=0.5);plot(x1[ind1],y1[ind1],'b-')
imshow(gray2b,cmap=cm.gray, alpha=0.5);
axis([0,1500,1000,0]);
title('image1 and transformed image2 overlain with 50% transparency');

enter image description here

Ответ 2

Хороший вопрос.

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

И более сложный код на С++ (с использованием VXL lib) с включенной оболочкой python и matlab. Или вы можете использовать некоторый модифицированный алгоритм ICP (итерационная ближайшая точка), который устойчив к шуму и может обрабатывать аффинное преобразование.

Также ваши контуры кажутся не очень точными, так что это может быть проблемой.

Другой подход - использовать некоторую регистрацию, в которой используются значения пикселей. Код Matlab (я думаю, что он использует какой-то показатель минимизации + кросскорреляции) Также может быть какая-то регистрация оптического потока (или какой-либо другой вид), который используется в медицинской визуализации.

Также вы можете использовать функции точки как SIFT (SURF).

Вы можете попробовать быстро в FIJI (ImageJ) также ссылка.

  • Открыть 2 изображения
  • Плагины- > извлечение функции- > просеивание (или другое)
  • Установите ожидаемое преобразование в аффинный
  • Посмотрите примерную модель преобразования модели [3,3] в матрице ImageJ log. Если он работает хорошо, вы можете реализовать его в python с помощью OpenCV или, возможно, с помощью Jython с ImageJ.

И будет лучше, если вы разместите оригинальные изображения и опишите все условия (кажется, что изображение меняется между кадрами)

Ответ 3

Вы можете представить эти контуры с соответствующими эллипсами. Эти эллипсы сосредоточены на центре тяжести контура и ориентированы на ось основной плотности. Вы можете сравнить центроиды и угол ориентации.

1) Заполните контуры = > drawContours с толщиной = CV_FILLED

2) Найти моменты = > cvMoments()

3) И использовать их.

Centroid: {x, y} = {M10/M00, M01/M00}

Ориентация (тета):  enter image description here

EDIT: я настроил код примера из наследия (inputblobdetection.cpp) для вашего случая.

            /* Image moments */
            double      M00,X,Y,XX,YY,XY;
            CvMoments   m;
            CvRect      r = ((CvContour*)cnt)->rect;
            CvMat       mat;
            cvMoments( cvGetSubRect(pImgFG,&mat,r), &m, 0 );
            M00 = cvGetSpatialMoment( &m, 0, 0 );
            X = cvGetSpatialMoment( &m, 1, 0 )/M00;
            Y = cvGetSpatialMoment( &m, 0, 1 )/M00;
            XX = (cvGetSpatialMoment( &m, 2, 0 )/M00) - X*X;
            YY = (cvGetSpatialMoment( &m, 0, 2 )/M00) - Y*Y;  
            XY = (cvGetSpatialMoment( &m, 1, 1 )/M00) - X*Y; 

            /* Contour description */
            CvPoint myCentroid(r.x+(float)X,r.y+(float)Y);
            double myTheta =  atan( 2*XY/(XX-YY) );

Кроме того, проверьте этот с примерами OpenCV 2.0.

Ответ 4

Если вы не хотите найти гомографию между двумя изображениями и хотите найти аффинное преобразование, у вас есть три неизвестных, угол поворота (R) и смещение по x и y (X, Y). Поэтому для нахождения неизвестных требуется минимум две точки (с двумя известными значениями для каждого). Две точки должны совпадать между двумя изображениями или двумя линиями, каждая из которых имеет два известных значения, перехват и наклон. Если вы идете с подходом к точечному сопоставлению, чем дальше точки друг от друга, тем более надежным является найденное преобразование в шум (это очень просто, если вы помните правила распространения ошибок).

В методе двухточечного сопоставления:

  • находим две точки (A и B) в первом изображении I1 и их соответствующие точки (A ', B') во втором изображении I2
  • найдите среднюю точку между A и B: C и среднюю точку между A 'и B': C '
  • разность C и C '(C-C') дает перевод между изображениями (X и Y)
  • используя точечный продукт C-A и C'-A ', вы можете найти угол поворота (R)

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