diff --git a/DEPENDS.txt b/DEPENDS.txt
index 38d394b0..c7afcfda 100644
--- a/DEPENDS.txt
+++ b/DEPENDS.txt
@@ -17,6 +17,9 @@ functionality is only available with the following installed:
`Open CV `_ (version 2.1).
Required for functions in ``scikits.image.opencv``.
+ Note that, under Ubuntu, due to problems with Atlas + OpenCV,
+ you may have to rebuild your own libcv.
+
`PyQt4 `_
The `qt` plugin that provides `imshow` and `scivy`.
diff --git a/scikits/image/opencv/opencv_constants.py b/scikits/image/opencv/opencv_constants.py
index a7bec83d..b07be700 100644
--- a/scikits/image/opencv/opencv_constants.py
+++ b/scikits/image/opencv/opencv_constants.py
@@ -147,6 +147,14 @@ CV_CALIB_CB_ADAPTIVE_THRESH = 1
CV_CALIB_CB_NORMALIZE_IMAGE = 2
CV_CALIB_CB_FILTER_QUADS = 4
+################################
+# Fundamental Matrix Constants #
+################################
+CV_FM_7POINT = 1
+CV_FM_8POINT = 2
+CV_FM_LMEDS = 4
+CV_FM_RANSAC = 8
+
####################
# cvMat TypeValues #
####################
diff --git a/scikits/image/opencv/opencv_cv.pyx b/scikits/image/opencv/opencv_cv.pyx
index a47819b3..e3ccb74f 100644
--- a/scikits/image/opencv/opencv_cv.pyx
+++ b/scikits/image/opencv/opencv_cv.pyx
@@ -294,6 +294,14 @@ c_cvFindExtrinsicCameraParams2 = \
(
ctypes.addressof(cv.cvFindExtrinsicCameraParams2))[0]
+# cvFindFundamentalMat
+ctypedef int (*cvFindFundamentalMatPtr)(CvMat*, CvMat*, CvMat*, int, double,
+ double, CvMat*)
+cdef cvFindFundamentalMatPtr c_cvFindFundamentalMat
+c_cvFindFundamentalMat = \
+ (
+ ctypes.addressof(cv.cvFindFundamentalMat))[0]
+
# cvDrawChessboardCorners
ctypedef void (*cvDrawChessboardCornersPtr)(IplImage*, CvSize, CvPoint2D32f*,
int, int)
@@ -2474,6 +2482,118 @@ def cvFindExtrinsicCameraParams2(object_points, image_points, intrinsic_matrix,
return (rvec, tvec)
+#---------------------
+# cvFindFundamentalMat
+#---------------------
+
+@cvdoc(package='cv', group='calibration', doc=\
+'''cvFindFundamentalMat(points1, points2, int method=CV_FM_RANSAC,
+ double param1=1, double param2=0.99)
+
+Calculates the fundamental matrix from the corresponding points in two images.
+
+Parameters
+----------
+points1 : ndarray, Nx2 or Nx3, dtype=float
+ Points from the first image.
+points2 : ndarray, Nx3 or Nx3, dtype=float
+ Points from the second image (same length as ``points1``).
+method : integer
+ CV_FM_7POINT - use 7-point algorithm (N = 7)
+ CV_FM_8POINT - use 8-point algorithm (N = 8)
+ CV_FM_RANSAC - use RANSAC algorithm (N >= 8)
+ CV_FM_LMEDS - use LMedS algorithm (N >= 8)
+param1 : float
+ In RANSAC, the maximum distance from point to epipolar lines
+ (in pixels) beyond which the point is considered an outlier.
+param2 : float
+ In RANSAC and LMedS, the level of confidence (probability)
+ that the estimated matrix is correct.
+
+Returns
+-------
+fundamental_matrix : ndarray, 3x3 or 3x3x3
+ Fundamental matrix. The 7-point method may return up to three
+ matrices, stored as a 3x3x3 array. If no matrix could be found,
+ None is returned.
+status : ndarray, length N, dtype=bool
+ Currently, this is only a placeholder for future use.
+
+ In future, should indicate whether a data-point is an inlier
+ (True) or outlier (False). Only used by RANSAC and MLedS; other
+ methods set all True.''')
+def cvFindFundamentalMat(points1, points2, int method=CV_FM_RANSAC,
+ double param1=1, double param2=0.99):
+ validate_array(points1)
+ validate_array(points2)
+
+ assert_ndims(points1, [2])
+ assert_ndims(points2, [2])
+ assert_dtype(points1, [FLOAT32, FLOAT64])
+ assert_dtype(points2, [FLOAT32, FLOAT64])
+
+ if not points1.shape[1] in (2, 3):
+ raise ValueError("Points should be Nx2 or Nx3 arrays")
+
+ if not method in (CV_FM_7POINT, CV_FM_8POINT, CV_FM_RANSAC, CV_FM_LMEDS):
+ raise ValueError("Invalid method specified")
+
+ if not points1.shape[0] == points2.shape[0]:
+ raise ValueError("Points1 and points2 should be of equal length.")
+
+ # allocate the numpy return arrays
+ cdef np.npy_intp fundamental_shape[2]
+
+ if (method == CV_FM_7POINT):
+ fundamental_shape[0] = 9
+ else:
+ fundamental_shape[0] = 3
+ fundamental_shape[1] = 3
+
+ cdef np.ndarray F = new_array(2, fundamental_shape, FLOAT64)
+
+ ## The code snippet below creates the ``status`` matrix
+ ## that may optionally be provided. I could not get this
+ ## to work with OpenCV 2.1.
+
+ cdef np.npy_intp status_shape[2]
+ status_shape[0] = points1.shape[0]
+ status_shape[1] = 1
+ cdef np.ndarray status = new_array(2, status_shape, UINT8)
+ status.fill(0)
+
+ ## cdef IplImage status_img
+ ## populate_iplimage(status, &status_img)
+ ## cdef CvMat* cvstatus = cvmat_ptr_from_iplimage(&status_img)
+
+ # Allocate cv images
+ cdef IplImage points1_img
+ cdef IplImage points2_img
+ cdef IplImage F_img
+
+ populate_iplimage(points1, &points1_img)
+ populate_iplimage(points2, &points2_img)
+ populate_iplimage(F, &F_img)
+
+ # Allocate cv matrices
+ cdef CvMat* cvpoints1 = cvmat_ptr_from_iplimage(&points1_img)
+ cdef CvMat* cvpoints2 = cvmat_ptr_from_iplimage(&points2_img)
+ cdef CvMat* cvF = cvmat_ptr_from_iplimage(&F_img)
+
+ cdef int m = c_cvFindFundamentalMat(cvpoints1, cvpoints2, cvF, method,
+ param1, param2, NULL)
+
+ PyMem_Free(cvpoints1)
+ PyMem_Free(cvpoints2)
+ PyMem_Free(cvF)
+ # PyMem_Free(cvstatus)
+
+ if m == 0:
+ return (None, status)
+ else:
+ return (F.reshape((m, 3, 3)).squeeze(), status)
+
+
#------------------------
# cvFindChessboardCorners
#------------------------
diff --git a/scikits/image/opencv/tests/test_opencv_cv.py b/scikits/image/opencv/tests/test_opencv_cv.py
index 681539d6..31468d5d 100644
--- a/scikits/image/opencv/tests/test_opencv_cv.py
+++ b/scikits/image/opencv/tests/test_opencv_cv.py
@@ -320,7 +320,89 @@ class TestUndistort2(OpenCVTest):
assert_array_almost_equal(undistg, self.lena_GRAY_U8)
+@opencv_skip
+def test_cvFindFundamentalMat():
+ #
+ # c2--->* * = Data Cloud
+ # ^
+ # | ^ z-direction
+ # c1 <--|
+ # x
+ #
+ # Experimental setup: camera 1 at the origin, random cube data set in front,
+ # camera two watching from the side (position [10, 0, 10])
+ # Set up projection matrices
+
+ def build_proj_mat(K, R, C):
+ """
+ Construct a projection matrix.
+
+ Parameters
+ ----------
+ K : ndarray, 3x3
+ Camera matrix, intrinsic parameters.
+ R : ndarray, 3x3
+ Rotation, world to camera.
+ C : ndarray, (3,)
+ Location of camera center in world coordinates.
+
+ """
+ C = np.reshape(C, (3, 1))
+
+ KR = np.dot(K, R)
+ P = np.zeros((3, 4))
+ P[:3, :3] = KR
+ P[:, 3].flat = np.dot(KR, -C)
+
+ return P
+
+ def cross_matrix(v):
+ a = v[0]
+ b = v[1]
+ c = v[2]
+
+ return np.array([[ 0, -c, b],
+ [ c, 0, -a],
+ [-b, a, 0]])
+
+ # Camera one, at origin of world coordinates, looking down the z-axis
+ K = np.array([[100., 0, 100],
+ [0, 100, 100],
+ [0, 0, 1]])
+ R = np.eye(3)
+ C = np.zeros((3,))
+ P = build_proj_mat(K, R, C)
+
+ # Camera two
+ K_ = K
+ R_ = np.array([[0., 0, -1],
+ [0, 1, 0],
+ [1, 0, 0]]) # Rotation of 90 degrees around y-axis
+ C_ = np.array([[10., 0, 10]]).T
+ P_ = build_proj_mat(K_, R_, C_)
+
+ data = np.random.random((100, 4)) * 5 - 2.5
+ data[:, 2] += 10 # Offset data in the z direction
+ data[:, 3] = 1 # 4D homogeneous version of 3D coords
+
+ points1 = np.dot(data, P.T)
+ points2 = np.dot(data, P_.T)
+
+ # See Hartley & Zisserman, Multiple View Geometry (2nd ed), p. 244
+ t = -np.dot(R_, C_)
+ K_t = np.dot(K_, t)
+
+ # Under numpy >= 1.5, this would be:
+ #F = cross_matrix(K_t).dot(K_).dot(R).dot(np.linalg.inv(K))
+
+ F = np.dot(np.dot(np.dot(cross_matrix(K_t), K_), R_), np.linalg.inv(K))
+ F /= F[2, 2]
+
+ F_est, status = cvFindFundamentalMat(points1, points2)
+
+ # Compare
+ assert_array_almost_equal(F, F_est)
if __name__ == '__main__':
run_module_suite()