Skip to content

Commit

Permalink
Add command to export to openMVS
Browse files Browse the repository at this point in the history
  • Loading branch information
paulinus committed Jul 28, 2016
1 parent eea5e0b commit d3362ce
Show file tree
Hide file tree
Showing 7 changed files with 573 additions and 0 deletions.
2 changes: 2 additions & 0 deletions opensfm/commands/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import reconstruct
import mesh
import export_ply
import export_openmvs

opensfm_commands = [
extract_metadata,
Expand All @@ -15,4 +16,5 @@
reconstruct,
mesh,
export_ply,
export_openmvs,
]
83 changes: 83 additions & 0 deletions opensfm/commands/export_openmvs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import logging
import os

import cv2
import numpy as np

from opensfm import csfm
from opensfm import dataset

logger = logging.getLogger(__name__)


class Command:
name = 'export_openmvs'
help = "Export reconstruction to openMVS format"

def add_arguments(self, parser):
parser.add_argument('dataset', help='dataset to process')

def run(self, args):
data = dataset.DataSet(args.dataset)
reconstructions = data.load_reconstruction()
graph = data.load_tracks_graph()

if reconstructions:
self.undistort_images(reconstructions[0], data)
self.export(reconstructions[0], graph, data)

def export(self, reconstruction, graph, data):
exporter = csfm.OpenMVSExporter()
for camera in reconstruction.cameras.values():
if camera.projection_type == 'perspective':
w, h = camera.width, camera.height
K = np.array([
[camera.focal, 0, (w - 1.0) / 2 / max(w, h)],
[0, camera.focal, (h - 1.0) / 2 / max(w, h)],
[0, 0, 1],
])
exporter.add_camera(str(camera.id), K)

for shot in reconstruction.shots.values():
if shot.camera.projection_type == 'perspective':
image_path = data._undistorted_image_file(shot.id)
exporter.add_shot(
str(os.path.abspath(image_path)),
str(shot.id),
str(shot.camera.id),
shot.pose.get_rotation_matrix(),
shot.pose.get_origin())

for point in reconstruction.points.values():
shots = graph[point.id].keys()
coordinates = np.array(point.coordinates, dtype=np.float64)
exporter.add_point(coordinates, shots)

exporter.export(data.data_path + '/openmvs_scene.mvs')

def undistort_images(self, reconstruction, data):
for shot in reconstruction.shots.values():
if shot.camera.projection_type == 'perspective':
image = data.image_as_array(shot.id)
undistorted = undistort_image(image, shot)
data.save_undistorted_image(shot.id, undistorted)


def undistort_image(image, shot):
"""Remove radial distortion from a perspective image."""
camera = shot.camera
height, width = image.shape[:2]
K = opencv_calibration_matrix(width, height, camera.focal)
distortion = np.array([camera.k1, camera.k2, 0, 0])
return cv2.undistort(image, K, distortion)


def opencv_calibration_matrix(width, height, focal):
"""Calibration matrix as used by OpenCV and PMVS.
Duplicated with bin.export_openmvs.opencv_calibration_matrix
"""
f = focal * max(width, height)
return np.matrix([[f, 0, 0.5 * (width - 1)],
[0, f, 0.5 * (height - 1)],
[0, 0, 1.0]])
16 changes: 16 additions & 0 deletions opensfm/dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,22 @@ def image_as_array(self, image):
IMREAD_COLOR = cv2.IMREAD_COLOR if context.OPENCV3 else cv2.CV_LOAD_IMAGE_COLOR
return cv2.imread(self.__image_file(image), IMREAD_COLOR)[:,:,::-1] # Turn BGR to RGB

def _undistorted_image_path(self):
return os.path.join(self.data_path, 'undistorted')

def _undistorted_image_file(self, image):
"""Path of undistorted version of an image."""
return os.path.join(self._undistorted_image_path(), image)

def undistorted_image_as_array(self, image):
"""Undistorted image pixels as 3-dimensional numpy array (R G B order)"""
IMREAD_COLOR = cv2.IMREAD_COLOR if context.OPENCV3 else cv2.CV_LOAD_IMAGE_COLOR
return cv2.imread(self._undistorted_image_file(image), IMREAD_COLOR)[:,:,::-1] # Turn BGR to RGB

def save_undistorted_image(self, image, array):
io.mkdir_p(self._undistorted_image_path())
cv2.imwrite(self._undistorted_image_file(image), array[:, :, ::-1])

@staticmethod
def __is_image_file(filename):
return filename.split('.')[-1].lower() in {'jpg', 'jpeg', 'png', 'tif', 'tiff', 'pgm', 'pnm', 'gif'}
Expand Down
8 changes: 8 additions & 0 deletions opensfm/src/csfm.cc
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "multiview.cc"
#include "akaze.cc"
#include "bundle.h"
#include "openmvs_exporter.h"

#if (PY_VERSION_HEX < 0x03000000)
static void numpy_import_array_wrapper()
Expand Down Expand Up @@ -140,4 +141,11 @@ BOOST_PYTHON_MODULE(csfm) {
.def_readwrite("reprojection_error", &BAPoint::reprojection_error)
.def_readwrite("id", &BAPoint::id)
;

class_<csfm::OpenMVSExporter>("OpenMVSExporter")
.def("add_camera", &csfm::OpenMVSExporter::AddCamera)
.def("add_shot", &csfm::OpenMVSExporter::AddShot)
.def("add_point", &csfm::OpenMVSExporter::AddPoint)
.def("export", &csfm::OpenMVSExporter::Export)
;
}
86 changes: 86 additions & 0 deletions opensfm/src/openmvs_exporter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// Isolating on a namespace to prevent collisions with opencv names
#define _USE_OPENCV
#include "third_party/openmvs/Interface.h"


namespace csfm {

class OpenMVSExporter {
public:
void AddCamera(
const std::string &camera_id,
PyObject *K) {

PyArrayContiguousView<double> K_view((PyArrayObject *)K);

MVS::Interface::Platform platform;
platform.name = camera_id;
MVS::Interface::Platform::Camera camera;
camera.K = cv::Matx33d(K_view.data());
camera.R = cv::Matx33d::eye();
camera.C = cv::Point3_<double>(0, 0, 0);
platform.cameras.push_back(camera);

platform_ids_[camera_id] = scene_.platforms.size();
scene_.platforms.push_back(platform);
}

void AddShot(
const std::string &path,
const std::string &shot_id,
const std::string &camera_id,
PyObject *R,
PyObject *C) {
PyArrayContiguousView<double> R_view((PyArrayObject *)R);
PyArrayContiguousView<double> C_view((PyArrayObject *)C);
const double *C_data = C_view.data();

int platform_id = platform_ids_[camera_id];
MVS::Interface::Platform &platform = scene_.platforms[platform_id];

MVS::Interface::Platform::Pose pose;
pose.R = cv::Matx33d(R_view.data());
pose.C = cv::Point3_<double>(C_data[0], C_data[1], C_data[2]);
int pose_id = platform.poses.size();
platform.poses.push_back(pose);

MVS::Interface::Image image;
image.name = path;
image.platformID = platform_id;
image.cameraID = 0;
image.poseID = pose_id;

image_ids_[shot_id] = scene_.images.size();
scene_.images.push_back(image);
}

void AddPoint(
PyObject *coordinates,
bp::list shot_ids) {
PyArrayContiguousView<double> coordinates_view((PyArrayObject *)coordinates);
const double *x = coordinates_view.data();

MVS::Interface::Vertex vertex;
vertex.X = cv::Point3_<double>(x[0], x[1], x[2]);
for (int i = 0; i < len(shot_ids); ++i) {
std::string shot_id = bp::extract<std::string>(shot_ids[i]);
MVS::Interface::Vertex::View view;
view.imageID = image_ids_[shot_id];
view.confidence = 0;
vertex.views.push_back(view);
}
scene_.vertices.push_back(vertex);
}

void Export(std::string filename) {
ARCHIVE::SerializeSave(scene_, filename);
}

private:
std::map<std::string, int> platform_ids_;
std::map<std::string, int> image_ids_;
MVS::Interface scene_;
};


}
Loading

10 comments on commit d3362ce

@xialang2012
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I intend to achieve it, but you have already completed it. 👍

@paulinus
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Testing would be very appreciated!
I'm sometimes getting SegFaults in openMVS when using the exported scenes.

@pmoulon
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Congrats for the new exporter.
In order to test if it's related to the OpenSfM exporter or due to OpenMVS you can try to process the same scene in OpenMVG+OpenMVS or under OpenSfM+OpenMVS.

You must check if the segfault is due to memory limitation... since large scene can consume a lot of memory, depending of the step you are running.

@paulinus
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, i've tested using the script here https://gist.github.com/paulinus/1eba8e4528529009f033fe407a7f3c03
No segfault, but i get the wrong colors on the pointcloud. I suspect something is wrong using openMVS on the mac. I've managed to get better colors using this patch paulinus/openMVS@f815120 but it seems very unlikely that this patch is correct (why would it work for others without that patch?).

@pmoulon
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the feedback. Happy to hear that already tested it ;-)
You're right there is something strange related to the point projection... We must understand it.

@xialang2012
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am trying to built openMVS, and then I will compare the results between the the exporter with OpenSfM + OpenMVS and OpenMVG + OpenMVS on Windows.

@pmoulon
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(out of the conversation) It would be nice to setup a Docker image with our tools as ready to use ;-)

@paulinus
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pmoulon i've just asked about the projection on openMVS issues: cdcseacave/openMVS#109

yep a docker image would be great. I'm using docker for building the test in travis, but that includes only OpenSfM. Would be great to have one with all tools included.

@pmoulon
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@paulinus For the moment the only Linux distribution dedicated on photogrammetry is NuxSfM https://github.com/pyp22/NuxSFM-2.0
For sure it would be nice to build a Docker system for our projects in order to ease the usage of the users.
And perhaps attract more easily new potential contributors!
It's time to show to people that OpenSource photogrammetry is powerful

@xialang2012
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It works well on Win. But if I try to exporter many images, it seems a liltte slowly, may be multi-thread should be used.

Please sign in to comment.