ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
movie_grabber_widget.cc
Go to the documentation of this file.
1 // Copyright (c) 2018, ETH Zurich and UNC Chapel Hill.
2 // All rights reserved.
3 //
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are met:
6 //
7 // * Redistributions of source code must retain the above copyright
8 // notice, this list of conditions and the following disclaimer.
9 //
10 // * Redistributions in binary form must reproduce the above copyright
11 // notice, this list of conditions and the following disclaimer in the
12 // documentation and/or other materials provided with the distribution.
13 //
14 // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of
15 // its contributors may be used to endorse or promote products derived
16 // from this software without specific prior written permission.
17 //
18 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
22 // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23 // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24 // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26 // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27 // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28 // POSSIBILITY OF SUCH DAMAGE.
29 //
30 // Author: Johannes L. Schoenberger (jsch-at-demuc-dot-de)
31 
33 
34 #include "base/pose.h"
35 #include "base/projection.h"
36 #include "ui/model_viewer_widget.h"
37 
38 namespace colmap {
39 
41  ModelViewerWidget* model_viewer_widget)
42  : QWidget(parent), model_viewer_widget_(model_viewer_widget) {
43  setWindowFlags(Qt::Widget | Qt::WindowStaysOnTopHint | Qt::Tool);
44  setWindowTitle("Grab movie");
45 
46  QGridLayout* grid = new QGridLayout(this);
47  grid->setContentsMargins(0, 5, 0, 5);
48 
49  add_button_ = new QPushButton(tr("Add"), this);
50  connect(add_button_, &QPushButton::released, this, &MovieGrabberWidget::Add);
51  grid->addWidget(add_button_, 0, 0);
52 
53  delete_button_ = new QPushButton(tr("Delete"), this);
54  connect(delete_button_, &QPushButton::released, this,
55  &MovieGrabberWidget::Delete);
56  grid->addWidget(delete_button_, 0, 1);
57 
58  clear_button_ = new QPushButton(tr("Clear"), this);
59  connect(clear_button_, &QPushButton::released, this,
60  &MovieGrabberWidget::Clear);
61  grid->addWidget(clear_button_, 0, 2);
62 
63  table_ = new QTableWidget(this);
64  table_->setColumnCount(1);
65  QStringList table_header;
66  table_header << "Time [seconds]";
67  table_->setHorizontalHeaderLabels(table_header);
68  table_->resizeColumnsToContents();
69  table_->setShowGrid(true);
70  table_->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
71  table_->verticalHeader()->setVisible(true);
72  table_->verticalHeader()->setDefaultSectionSize(18);
73  table_->setSelectionMode(QAbstractItemView::SingleSelection);
74  table_->setSelectionBehavior(QAbstractItemView::SelectRows);
75  connect(table_, &QTableWidget::itemChanged, this,
76  &MovieGrabberWidget::TimeChanged);
77  connect(table_->selectionModel(), &QItemSelectionModel::selectionChanged,
78  this, &MovieGrabberWidget::SelectionChanged);
79  grid->addWidget(table_, 1, 0, 1, 3);
80 
81  grid->addWidget(new QLabel(tr("Frame rate"), this), 2, 1);
82  frame_rate_sb_ = new QSpinBox(this);
83  frame_rate_sb_->setMinimum(1);
84  frame_rate_sb_->setMaximum(1000);
85  frame_rate_sb_->setSingleStep(1);
86  frame_rate_sb_->setValue(100);
87  grid->addWidget(frame_rate_sb_, 2, 2);
88 
89  grid->addWidget(new QLabel(tr("Smooth transition"), this), 3, 1);
90  smooth_cb_ = new QCheckBox(this);
91  smooth_cb_->setChecked(true);
92  grid->addWidget(smooth_cb_, 3, 2);
93 
94  grid->addWidget(new QLabel(tr("Smoothness"), this), 4, 1);
95  smoothness_sb_ = new QDoubleSpinBox(this);
96  smoothness_sb_->setMinimum(0);
97  smoothness_sb_->setMaximum(1);
98  smoothness_sb_->setSingleStep(0.01);
99  smoothness_sb_->setValue(0.5);
100  grid->addWidget(smoothness_sb_, 4, 2);
101 
102  assemble_button_ = new QPushButton(tr("Assemble movie"), this);
103  connect(assemble_button_, &QPushButton::released, this,
104  &MovieGrabberWidget::Assemble);
105  grid->addWidget(assemble_button_, 5, 1, 1, 2);
106 }
107 
108 void MovieGrabberWidget::Add() {
109  const QMatrix4x4 matrix = model_viewer_widget_->ModelViewMatrix();
110 
111  double time = 0;
112  if (table_->rowCount() > 0) {
113  time = table_->item(table_->rowCount() - 1, 0)->text().toDouble() + 1;
114  }
115 
116  QTableWidgetItem* item = new QTableWidgetItem();
117  item->setData(Qt::DisplayRole, time);
118  item->setFlags(Qt::NoItemFlags | Qt::ItemIsEnabled | Qt::ItemIsSelectable |
119  Qt::ItemIsEditable);
120  item->setTextAlignment(Qt::AlignRight);
121 
122  // Save size state of current viewpoint.
123  ViewData view_data;
124  view_data.model_view_matrix = matrix;
125  view_data.point_size = model_viewer_widget_->PointSize();
126  view_data.image_size = model_viewer_widget_->ImageSize();
127  view_data_.emplace(item, view_data);
128 
129  table_->insertRow(table_->rowCount());
130  table_->setItem(table_->rowCount() - 1, 0, item);
131  table_->selectRow(table_->rowCount() - 1);
132 
133  // Zoom out a little, so that we can see the newly added camera
134  model_viewer_widget_->ChangeFocusDistance(-5);
135 }
136 
137 void MovieGrabberWidget::Delete() {
138  QModelIndexList selection = table_->selectionModel()->selectedIndexes();
139  for (const auto& index : selection) {
140  table_->removeRow(index.row());
141  }
142  UpdateViews();
143  model_viewer_widget_->UpdateMovieGrabber();
144 }
145 
146 void MovieGrabberWidget::Clear() {
147  view_data_.clear();
148  while (table_->rowCount() > 0) {
149  table_->removeRow(0);
150  }
151  views.clear();
152  model_viewer_widget_->UpdateMovieGrabber();
153 }
154 
155 void MovieGrabberWidget::Assemble() {
156  if (table_->rowCount() < 2) {
157  QMessageBox::critical(this, tr("Error"),
158  tr("You must add at least two control views."));
159  return;
160  }
161 
162  if (model_viewer_widget_->GetProjectionType() !=
163  RenderOptions::ProjectionType::PERSPECTIVE) {
164  QMessageBox::critical(this, tr("Error"),
165  tr("You must use perspective projection."));
166  return;
167  }
168 
169  const QString path = QFileDialog::getExistingDirectory(
170  this, tr("Choose destination..."), "", QFileDialog::ShowDirsOnly);
171 
172  // File dialog cancelled?
173  if (path == "") {
174  return;
175  }
176 
177  const QDir dir = QDir(path);
178 
179  const QMatrix4x4 model_view_matrix_cached =
180  model_viewer_widget_->ModelViewMatrix();
181  const float point_size_cached = model_viewer_widget_->PointSize();
182  const float image_size_cached = model_viewer_widget_->ImageSize();
183  const std::vector<Image> views_cached = views;
184 
185  // Make sure we do not render movie grabber path.
186  views.clear();
187  model_viewer_widget_->UpdateMovieGrabber();
188  model_viewer_widget_->DisableCoordinateGrid();
189 
190  const float frame_rate = frame_rate_sb_->value();
191  const float frame_time = 1.0f / frame_rate;
192  size_t frame_number = 0;
193 
194  // Data of first view.
195  const Eigen::Matrix4d prev_model_view_matrix =
196  QMatrixToEigen(view_data_[table_->item(0, 0)].model_view_matrix)
197  .cast<double>();
198  const Eigen::Matrix3x4d prev_view_model_matrix =
199  InvertProjectionMatrix(prev_model_view_matrix.topLeftCorner<3, 4>());
200  Eigen::Vector4d prev_qvec =
201  RotationMatrixToQuaternion(prev_view_model_matrix.block<3, 3>(0, 0));
202  Eigen::Vector3d prev_tvec = prev_view_model_matrix.block<3, 1>(0, 3);
203 
204  for (int row = 1; row < table_->rowCount(); ++row) {
205  const auto logical_idx = table_->verticalHeader()->logicalIndex(row);
206  QTableWidgetItem* prev_table_item = table_->item(logical_idx - 1, 0);
207  QTableWidgetItem* table_item = table_->item(logical_idx, 0);
208 
209  const ViewData& prev_view_data = view_data_.at(prev_table_item);
210  const ViewData& view_data = view_data_.at(table_item);
211 
212  // Data of next view.
213  const Eigen::Matrix4d curr_model_view_matrix =
214  QMatrixToEigen(view_data.model_view_matrix).cast<double>();
215  const Eigen::Matrix3x4d curr_view_model_matrix =
216  InvertProjectionMatrix(curr_model_view_matrix.topLeftCorner<3, 4>());
217  const Eigen::Vector4d curr_qvec =
218  RotationMatrixToQuaternion(curr_view_model_matrix.block<3, 3>(0, 0));
219  const Eigen::Vector3d curr_tvec = curr_view_model_matrix.block<3, 1>(0, 3);
220 
221  // Time difference between previous and current view.
222  const float dt = std::abs(table_item->text().toFloat() -
223  prev_table_item->text().toFloat());
224 
225  // Point size differences between previous and current view.
226  const float dpoint_size = view_data.point_size - prev_view_data.point_size;
227  const float dimage_size = view_data.image_size - prev_view_data.image_size;
228 
229  const auto num_frames = dt * frame_rate;
230  for (size_t i = 0; i < num_frames; ++i) {
231  const float t = i * frame_time;
232  float tt = t / dt;
233 
234  if (smooth_cb_->isChecked()) {
235  tt = ScaleSigmoid(tt, static_cast<float>(smoothness_sb_->value()));
236  }
237 
238  // Compute current model-view matrix.
239  Eigen::Vector4d interp_qvec;
240  Eigen::Vector3d interp_tvec;
241  InterpolatePose(prev_qvec, prev_tvec, curr_qvec, curr_tvec, tt,
242  &interp_qvec, &interp_tvec);
243 
244  Eigen::Matrix4d frame_model_view_matrix = Eigen::Matrix4d::Identity();
245  frame_model_view_matrix.topLeftCorner<3, 4>() = InvertProjectionMatrix(
246  ComposeProjectionMatrix(interp_qvec, interp_tvec));
247 
248  model_viewer_widget_->SetModelViewMatrix(
249  EigenToQMatrix(frame_model_view_matrix.cast<float>()));
250 
251  // Set point and image sizes.
252  model_viewer_widget_->SetPointSize(prev_view_data.point_size +
253  dpoint_size * tt);
254  model_viewer_widget_->SetImageSize(prev_view_data.image_size +
255  dimage_size * tt);
256 
257  QImage image = model_viewer_widget_->GrabImage();
258 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
259  image.save(dir.filePath(
260  "frame" + QString::asprintf("%06zu", frame_number) + ".png"));
261 #else
262  image.save(dir.filePath(
263  "frame" + QString().sprintf("%06zu", frame_number) + ".png"));
264 #endif
265  frame_number += 1;
266  }
267 
268  prev_qvec = curr_qvec;
269  prev_tvec = curr_tvec;
270  }
271 
272  views = views_cached;
273  model_viewer_widget_->SetPointSize(point_size_cached);
274  model_viewer_widget_->SetImageSize(image_size_cached);
275  model_viewer_widget_->UpdateMovieGrabber();
276  model_viewer_widget_->EnableCoordinateGrid();
277  model_viewer_widget_->SetModelViewMatrix(model_view_matrix_cached);
278 }
279 
280 void MovieGrabberWidget::TimeChanged(QTableWidgetItem* item) {
281  table_->sortItems(0, Qt::AscendingOrder);
282  UpdateViews();
283  model_viewer_widget_->UpdateMovieGrabber();
284 }
285 
286 void MovieGrabberWidget::SelectionChanged(const QItemSelection& selected,
287  const QItemSelection& deselected) {
288  for (const auto& index : table_->selectionModel()->selectedIndexes()) {
289  model_viewer_widget_->SelectMoviewGrabberView(index.row());
290  }
291 }
292 
293 void MovieGrabberWidget::UpdateViews() {
294  views.clear();
295  for (int row = 0; row < table_->rowCount(); ++row) {
296  const auto logical_idx = table_->verticalHeader()->logicalIndex(row);
297  QTableWidgetItem* item = table_->item(logical_idx, 0);
298 
299  const Eigen::Matrix4d model_view_matrix =
300  QMatrixToEigen(view_data_.at(item).model_view_matrix).cast<double>();
301  Image image;
302  image.Qvec() =
303  RotationMatrixToQuaternion(model_view_matrix.block<3, 3>(0, 0));
304  image.Tvec() = model_view_matrix.block<3, 1>(0, 3);
305  views.push_back(image);
306  }
307 }
308 
309 } // namespace colmap
std::shared_ptr< core::Tensor > image
void ChangeFocusDistance(const float delta)
QMatrix4x4 ModelViewMatrix() const
void SetImageSize(const float image_size)
void SetPointSize(const float point_size)
void SelectMoviewGrabberView(const size_t view_idx)
void SetModelViewMatrix(const QMatrix4x4 &matrix)
MovieGrabberWidget(QWidget *parent, ModelViewerWidget *model_viewer_widget)
Matrix< double, 3, 4 > Matrix3x4d
Definition: types.h:39
static const std::string path
Definition: PointCloud.cpp:59
Eigen::Matrix3x4d InvertProjectionMatrix(const Eigen::Matrix3x4d &proj_matrix)
Definition: projection.cc:55
Eigen::Vector4d RotationMatrixToQuaternion(const Eigen::Matrix3d &rot_mat)
Definition: pose.cc:70
T ScaleSigmoid(T x, const T alpha=1, const T x0=10)
Definition: math.h:283
Eigen::Matrix4f QMatrixToEigen(const QMatrix4x4 &matrix)
Definition: qt_utils.cc:39
QMatrix4x4 EigenToQMatrix(const Eigen::Matrix4f &matrix)
Definition: qt_utils.cc:49
Eigen::Matrix3x4d ComposeProjectionMatrix(const Eigen::Vector4d &qvec, const Eigen::Vector3d &tvec)
Definition: projection.cc:39
void InterpolatePose(const Eigen::Vector4d &qvec1, const Eigen::Vector3d &tvec1, const Eigen::Vector4d &qvec2, const Eigen::Vector3d &tvec2, const double t, Eigen::Vector4d *qveci, Eigen::Vector3d *tveci)
Definition: pose.cc:198