我有从电影中扫描的需要注册的海底图像的历史时间序列。
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]);
蓝色是第1帧中最长的轮廓,绿色是第2帧中最长的轮廓。
确定蓝色和绿色轮廓之间的旋转和偏移的最佳方法是什么?
我只想在步骤周围的某些区域使用轮廓的右侧,例如箭头之间的区域。
当然,如果有更好的方式来注册这些图像,我很乐意听到它。我已经在原始图像上尝试了标准的特征匹配方法,但它不能很好地工作。
答案 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])
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');
答案 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。
如果发布原始图像并描述所有条件(似乎图像在帧之间变化)会更好
答案 2 :(得分:2)
您可以使用各自的省略号表示这些轮廓。这些椭圆以轮廓的质心为中心,并且朝向主密度轴。您可以比较质心和方向角。
1)填充轮廓=&gt; drawContours的厚度= CV_FILLED
2)寻找时刻=&gt; cvMoments()
3)和use them。
质心:{x,y} = {M10 / M00,M01 / M00}
方向(theta):
编辑:我为您的案例定制了旧版(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)中的位移。因此,需要最少两个点(每个点有两个已知值)来找到未知数。两个图像或两条线之间应匹配两个点,每个点有两个已知值,即截距和斜率。如果采用点匹配方法,点之间的距离越远,找到的转换为噪声就越稳健(如果你记得错误传播规则,这很简单)。
在两点匹配方法中:
为了检测稳健点,我会找到你找到的具有最高绝对值的二阶导数(Hessian)的计数器侧面的点,然后尝试匹配它们。既然你提到这是一个录像片段,你可以很容易地假设每两帧之间的转换很小,以拒绝异常值。