OpenCV中轮廓之间的仿射变换

时间:2013-04-12 19:54:19

标签: python 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帧中最长的轮廓。

确定蓝色和绿色轮廓之间的旋转和偏移的最佳方法是什么?

我只想在步骤周围的某些区域使用轮廓的右侧,例如箭头之间的区域。

当然,如果有更好的方式来注册这些图像,我很乐意听到它。我已经在原始图像上尝试了标准的特征匹配方法,但它不能很好地工作。

4 个答案:

答案 0 :(得分:7)

按照Shambool建议的方法,这就是我想出的。我使用Ramer-Douglas-Peucker算法来简化感兴趣区域的轮廓并确定两个转折点。我打算使用两个转折点来获得我的三个未知数(xoffset,yoffset和旋转角度),但是第二个转折点有点偏向右边,因为RDP简化了该区域中更平滑的曲线。因此,我使用了通向第一个转折点的线段的角度。在image1和image2之间区分这个角度给出了旋转角度。我仍然不满意这个解决方案。它对这两个图像效果很好,但我不确定它是否能在整个图像序列上很好地工作。走着瞧。

将轮廓拟合到黑色边框的已知形状会更好。

# 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

答案 1 :(得分:3)

好问题。

一种方法是将轮廓表示为2d点云,然后进行注册。 更多simple and clear code in Matlab可以给你仿射变换。

包含python和matlab包装的more complex C++ code(using VXL lib)。 或者您可以使用一些修改后的ICP(迭代最近点)算法,该算法对噪声具有鲁棒性并且可以处理仿射变换。

此外,您的轮廓似乎不是很准确,因此可能会出现问题。

另一种方法是使用某种使用像素值的注册。 Matlab code(我认为它使用了某种最小化器+互相关指标) 也许可能存在某种用于医学成像的光流注册(或其他类型)。

您也可以将点要素用作SIFT(SURF)。

您可以在FIJI(ImageJ)快速尝试 这也是link

  1. 打开2张图片
  2. 插件 - &gt;特征提取 - &gt;筛选(或其他)
  3. 将预期转化设为仿射
  4. 查看ImageJ日志中的估计变换模型[3,3]单应矩阵。 如果它运行良好,那么你可以使用OpenCV在python中实现它,或者可以在ImageJ中使用Jython。
  5. 如果发布原始图像并描述所有条件(似乎图像在帧之间变化)会更好

答案 2 :(得分:2)

您可以使用各自的省略号表示这些轮廓。这些椭圆以轮廓的质心为中心,并且朝向主密度轴。您可以比较质心和方向角。

1)填充轮廓=&gt; drawContours的厚度= CV_FILLED

2)寻找时刻=&gt; cvMoments()

3)和use them

质心:{x,y} = {M10 / M00,M01 / M00}

方向(theta):  enter image description here

编辑:我为您的案例定制了旧版(enteredblobdetection.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示例检查this

答案 3 :(得分:1)

如果您不想在两个图像之间找到单应性并想要找到仿射变换,则您有三个未知数,旋转角度(R)和x和y(X,Y)中的位移。因此,需要最少两个点(每个点有两个已知值)来找到未知数。两个图像或两条线之间应匹配两个点,每个点有两个已知值,即截距和斜率。如果采用点匹配方法,点之间的距离越远,找到的转换为噪声就越稳健(如果你记得错误传播规则,这很简单)。

在两点匹配方法中:

  1. 在第一个图像I1中找到两个点(A和B),在第二个图像I2中找到它们对应的点(A',B')
  2. 找到A和B之间的中间点:C,以及A'和B'之间的中间点:C'
  3. 差异C和C'(C-C')给出了图像之间的平移(X和Y)
  4. 使用C-A和C'-A'的点积你可以找到旋转角度(R)
  5. 为了检测稳健点,我会找到你找到的具有最高绝对值的二阶导数(Hessian)的计数器侧面的点,然后尝试匹配它们。既然你提到这是一个录像片段,你可以很容易地假设每两帧之间的转换很小,以拒绝异常值。