18

我有从需要注册的胶片扫描的海底图像的历史时间序列。

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

在此处输入图像描述

我想使用每个图像左侧的黑色区域进行配准,因为该区域在相机内部,应该及时修复。所以我只需要计算黑色区域之间的仿射变换。

我通过阈值化和找到最大轮廓来确定这些区域:

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]);

在此处输入图像描述

蓝色是第一帧最长的轮廓,绿色是第二帧最长的轮廓。

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

我只想在台阶周围的某个区域使用轮廓的右侧,比如箭头之间的区域。

当然,如果有更好的方法来注册这些图像,我很想听听。我已经在原始图像上尝试了标准的特征匹配方法,但效果不够好。

4

4 回答 4

9

遵循 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])

在此处输入图像描述

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

在此处输入图像描述

# 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');

在此处输入图像描述

于 2013-04-18T11:30:20.820 回答
3

好问题。

一种方法是将轮廓表示为 2d 点云,然后进行配准。Matlab 中更简单明了的代码,可以为您提供仿射变换。

以及包含 python 和 matlab 包装器的更复杂的 C++ 代码(使用 VXL 库) 。或者您可以使用一些改进的 ICP(迭代最近点)算法,该算法对噪声具有鲁棒性并且可以处理仿射变换。

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

另一种方法是使用某种使用像素值的配准。 Matlab代码(我认为它使用某种最小化器+互相关度量)也可能有某种用于医学成像的光流配准(或其他类型)。

您也可以将点特征用作 SIFT(SURF)。

您也可以在FIJI(ImageJ)中快速尝试 此链接

  1. 打开 2 张图片
  2. 插件->特征提取->筛选(或其他)
  3. 将预期变换设置为仿射
  4. 查看 ImageJ 日志中估计的变换模型 [3,3] 单应矩阵。如果它运行良好,那么您可以使用 OpenCV 在 python 中实现它,或者使用 Jython 和 ImageJ。

如果您发布原始图像并描述所有条件会更好(似乎图像在帧之间发生变化)

于 2013-04-17T07:43:39.893 回答
2

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

1)填充轮廓=> drawContours 厚度=CV_FILLED

2) 寻找时刻 => cvMoments()

3)并使用它们

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

方向(θ): 在此处输入图像描述

编辑:我为您的案例定制了遗留(enteringblobdetection.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 示例进行检查。

于 2013-04-17T07:17:25.293 回答
1

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

在两点匹配法中:

  1. 在第一幅图像 I1 中找到两个点 (A 和 B) 以及在第二幅图像 I2 中找到它们的对应点 (A',B')
  2. 找到A和B之间的中点:C,以及A'和B'之间的中点:C'
  3. C和C'(CC')的差异给出了图像(X和Y)之间的平移
  4. 使用 CA 和 C'-A' 的点积,您可以找到旋转角度 (R)

为了检测稳健点,我会找到您在计数器侧面找到的具有最高二阶导数(Hessian)绝对值的点,然后尝试匹配它们。由于您提到这是一个视频片段,您可以轻松地假设每两帧之间的转换很小以拒绝异常值。

于 2013-04-17T02:13:51.757 回答