ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
MovieGrabberWidget.cpp
Go to the documentation of this file.
1 // ----------------------------------------------------------------------------
2 // - CloudViewer: www.cloudViewer.org -
3 // ----------------------------------------------------------------------------
4 // Copyright (c) 2018-2024 www.cloudViewer.org
5 // SPDX-License-Identifier: MIT
6 // ----------------------------------------------------------------------------
7 
8 #include "MovieGrabberWidget.h"
9 
10 #include <ecv2DViewportObject.h>
11 #include <ecvDisplayTools.h>
12 #include <ecvHObjectCaster.h>
13 
14 #include "ModelViewerWidget.h"
15 #include "base/pose.h"
16 #include "base/projection.h"
18 #include "ui/qt_utils.h"
19 #include "ui/render_options.h"
20 
21 namespace cloudViewer {
22 
23 using namespace colmap;
24 
26  ModelViewerWidget* model_viewer_widget)
27  : QWidget(parent), model_viewer_widget_(model_viewer_widget) {
28  setWindowFlags(Qt::Widget | Qt::WindowStaysOnTopHint | Qt::Tool);
29  setWindowTitle("Grab movie");
30 
31  QGridLayout* grid = new QGridLayout(this);
32  grid->setContentsMargins(0, 5, 0, 5);
33 
34  add_button_ = new QPushButton(tr("Add"), this);
35  connect(add_button_, &QPushButton::released, this,
36  &MovieGrabberWidget::Add);
37  grid->addWidget(add_button_, 0, 0);
38 
39  add_from_selected_button_ = new QPushButton(tr("Add Selected"), this);
40  connect(add_from_selected_button_, &QPushButton::released, this,
41  &MovieGrabberWidget::AddFromSelected);
42  grid->addWidget(add_from_selected_button_, 0, 1);
43 
44  delete_button_ = new QPushButton(tr("Delete"), this);
45  connect(delete_button_, &QPushButton::released, this,
46  &MovieGrabberWidget::Delete);
47  grid->addWidget(delete_button_, 0, 2);
48 
49  clear_button_ = new QPushButton(tr("Clear"), this);
50  connect(clear_button_, &QPushButton::released, this,
51  &MovieGrabberWidget::Clear);
52  grid->addWidget(clear_button_, 0, 3);
53 
54  table_ = new QTableWidget(this);
55  table_->setColumnCount(1);
56  QStringList table_header;
57  table_header << "Time [seconds]";
58  table_->setHorizontalHeaderLabels(table_header);
59  table_->resizeColumnsToContents();
60  table_->setShowGrid(true);
61  table_->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
62  table_->verticalHeader()->setVisible(true);
63  table_->verticalHeader()->setDefaultSectionSize(18);
64  table_->setSelectionMode(QAbstractItemView::SingleSelection);
65  table_->setSelectionBehavior(QAbstractItemView::SelectRows);
66  connect(table_, &QTableWidget::itemChanged, this,
67  &MovieGrabberWidget::TimeChanged);
68  connect(table_->selectionModel(), &QItemSelectionModel::selectionChanged,
69  this, &MovieGrabberWidget::SelectionChanged);
70  grid->addWidget(table_, 1, 0, 1, 3);
71 
72  grid->addWidget(new QLabel(tr("Frame rate"), this), 2, 1);
73  frame_rate_sb_ = new QSpinBox(this);
74  frame_rate_sb_->setMinimum(1);
75  frame_rate_sb_->setMaximum(1000);
76  frame_rate_sb_->setSingleStep(1);
77  frame_rate_sb_->setValue(100);
78  grid->addWidget(frame_rate_sb_, 2, 2);
79 
80  grid->addWidget(new QLabel(tr("Smooth transition"), this), 3, 1);
81  smooth_cb_ = new QCheckBox(this);
82  smooth_cb_->setChecked(true);
83  grid->addWidget(smooth_cb_, 3, 2);
84 
85  grid->addWidget(new QLabel(tr("Smoothness"), this), 4, 1);
86  smoothness_sb_ = new QDoubleSpinBox(this);
87  smoothness_sb_->setMinimum(0);
88  smoothness_sb_->setMaximum(1);
89  smoothness_sb_->setSingleStep(0.01);
90  smoothness_sb_->setValue(0.5);
91  grid->addWidget(smoothness_sb_, 4, 2);
92 
93  assemble_button_ = new QPushButton(tr("Assemble movie"), this);
94  connect(assemble_button_, &QPushButton::released, this,
95  &MovieGrabberWidget::Assemble);
96  grid->addWidget(assemble_button_, 5, 1, 1, 2);
97 }
98 
99 void MovieGrabberWidget::AddFromSelected() {
100  if (!view_data_.empty()) {
101  if (QMessageBox::question(
102  this, tr("Add viewports from selected"),
103  tr("Are you sure you want to remove all history views?"),
104  QMessageBox::Yes, QMessageBox::No) != QMessageBox::Yes) {
105  return;
106  }
107 
108  Clear();
109  }
110 
111  ccHObject::Container objects;
113 
114  std::vector<cc2DViewportObject*> selected_viewports;
115  for (const auto& obj : objects) {
116  if (obj->isA(CV_TYPES::VIEWPORT_2D_OBJECT) && obj->isSelected()) {
117  selected_viewports.push_back(
119  }
120  }
121 
122  CVLog::Print(QString("Found selected viewports: %1")
123  .arg(selected_viewports.size()));
124  for (auto& viewport : selected_viewports) {
125  if (!viewport) {
126  continue;
127  }
128 
129  double time = 0;
130  if (table_->rowCount() > 0) {
131  time = table_->item(table_->rowCount() - 1, 0)->text().toDouble() +
132  1;
133  }
134 
135  QTableWidgetItem* item = new QTableWidgetItem();
136  item->setData(Qt::DisplayRole, time);
137  item->setFlags(Qt::NoItemFlags | Qt::ItemIsEnabled |
138  Qt::ItemIsSelectable | Qt::ItemIsEditable);
139  item->setTextAlignment(Qt::AlignRight);
140 
141  // Save size state of current viewpoint.
142  MovieGrabberWidget::ViewData view_data;
143  view_data.viewportParams = viewport->getParameters();
144  view_data.model_view_matrix =
145  viewport->getParameters().computeViewMatrix();
146  view_data.point_size = model_viewer_widget_->PointSize();
147  view_data.image_size = model_viewer_widget_->ImageSize();
148  view_data_.emplace(item, view_data);
149 
150  table_->insertRow(table_->rowCount());
151  table_->setItem(table_->rowCount() - 1, 0, item);
152  table_->selectRow(table_->rowCount() - 1);
153  }
154 }
155 
156 void MovieGrabberWidget::Add() {
157  const ccGLMatrixd matrix = model_viewer_widget_->ModelViewMatrix();
158 
159  double time = 0;
160  if (table_->rowCount() > 0) {
161  time = table_->item(table_->rowCount() - 1, 0)->text().toDouble() + 1;
162  }
163 
164  QTableWidgetItem* item = new QTableWidgetItem();
165  item->setData(Qt::DisplayRole, time);
166  item->setFlags(Qt::NoItemFlags | Qt::ItemIsEnabled | Qt::ItemIsSelectable |
167  Qt::ItemIsEditable);
168  item->setTextAlignment(Qt::AlignRight);
169 
170  // Save size state of current viewpoint.
171  MovieGrabberWidget::ViewData view_data;
172  view_data.viewportParams = ecvDisplayTools::GetViewportParameters();
173  view_data.model_view_matrix = matrix;
174  view_data.point_size = model_viewer_widget_->PointSize();
175  view_data.image_size = model_viewer_widget_->ImageSize();
176  view_data_.emplace(item, view_data);
177 
178  table_->insertRow(table_->rowCount());
179  table_->setItem(table_->rowCount() - 1, 0, item);
180  table_->selectRow(table_->rowCount() - 1);
181 
182  // Zoom out a little, so that we can see the newly added camera
183  // model_viewer_widget_->ChangeFocusDistance(-50.0f);
184 }
185 
186 void MovieGrabberWidget::Delete() {
187  QModelIndexList selection = table_->selectionModel()->selectedIndexes();
188  std::vector<colmap::camera_t> toBeDeleted;
189  for (const auto& index : selection) {
190  table_->removeRow(index.row());
191  Image& image = views[static_cast<std::size_t>(index.row())];
192  toBeDeleted.push_back(image.CameraId());
193  }
194 
195  std::vector<colmap::camera_t>::iterator it;
196  for (it = camera_ids_.begin(); it != camera_ids_.end();) {
197  if (std::find(toBeDeleted.begin(), toBeDeleted.end(), *it) !=
198  toBeDeleted.end()) {
199  it = camera_ids_.erase(it);
200  } else {
201  ++it;
202  }
203  }
204 
205  UpdateViews();
206  model_viewer_widget_->UpdateMovieGrabber();
207 }
208 
209 void MovieGrabberWidget::Clear() {
210  view_data_.clear();
211  while (table_->rowCount() > 0) {
212  table_->removeRow(0);
213  }
214  views.clear();
215  camera_ids_.clear();
216  model_viewer_widget_->UpdateMovieGrabber();
217 }
218 
219 void MovieGrabberWidget::Assemble() {
220  if (table_->rowCount() < 2) {
221  QMessageBox::critical(this, tr("Error"),
222  tr("You must add at least two control views."));
223  return;
224  }
225 
226  if (model_viewer_widget_->GetProjectionType() !=
227  colmap::RenderOptions::ProjectionType::PERSPECTIVE) {
228  QMessageBox::critical(this, tr("Error"),
229  tr("You must use perspective projection."));
230  return;
231  }
232 
233  const QString path = QFileDialog::getExistingDirectory(
234  this, tr("Choose destination..."), "", QFileDialog::ShowDirsOnly);
235 
236  // File dialog cancelled?
237  if (path == "") {
238  return;
239  }
240 
241  const QDir dir = QDir(path);
242 
243  const ccGLMatrixd model_view_matrix_cached =
244  model_viewer_widget_->ModelViewMatrix();
245  const float point_size_cached = model_viewer_widget_->PointSize();
246  const float image_size_cached = model_viewer_widget_->ImageSize();
247  const std::vector<Image> views_cached = views;
248 
249  // Make sure we do not render movie grabber path.
250  views.clear();
251  model_viewer_widget_->UpdateMovieGrabber();
252  bool coords_changed = false;
253  bool lengend_changed = false;
254  bool coords_shown = ecvDisplayTools::OrientationMarkerShown();
255  bool lengend_shown = ecvDisplayTools::OverlayEntitiesAreDisplayed();
256  if (lengend_shown) {
258  lengend_changed = true;
259  }
260  if (coords_shown) {
262  coords_changed = true;
263  }
264 
265  const float frame_rate = frame_rate_sb_->value();
266  const float frame_time = 1.0f / frame_rate;
267  size_t frame_number = 0;
268 
269  // precompute frame count
270  float totalTime = 0;
271  for (int row = 1; row < table_->rowCount(); ++row) {
272  const auto logical_idx = table_->verticalHeader()->logicalIndex(row);
273  QTableWidgetItem* prev_table_item = table_->item(logical_idx - 1, 0);
274  QTableWidgetItem* table_item = table_->item(logical_idx, 0);
275  // Time difference between previous and current view.
276  const float dt = std::abs(table_item->text().toFloat() -
277  prev_table_item->text().toFloat());
278  totalTime += dt;
279  }
280 
281  int frameCount = static_cast<int>(frame_rate * totalTime);
282 
283  // show progress dialog
284  QProgressDialog progressDialog(QString("Frames: %1").arg(frameCount),
285  "Cancel", 0, frameCount, this);
286  progressDialog.setWindowTitle("Generate movie frames");
287  progressDialog.setModal(true);
288  // progressDialog.setAutoClose(false);
289  progressDialog.show();
290  QApplication::processEvents();
291 
292  // Data of first view.
293  ecvViewportParameters firstViewport =
294  view_data_[table_->item(0, 0)].viewportParams;
295  for (int row = 1; row < table_->rowCount(); ++row) {
296  const auto logical_idx = table_->verticalHeader()->logicalIndex(row);
297  QTableWidgetItem* prev_table_item = table_->item(logical_idx - 1, 0);
298  QTableWidgetItem* table_item = table_->item(logical_idx, 0);
299 
300  const MovieGrabberWidget::ViewData& prev_view_data =
301  view_data_.at(prev_table_item);
302  const MovieGrabberWidget::ViewData& view_data =
303  view_data_.at(table_item);
304 
305  // Time difference between previous and current view.
306  const float dt = std::abs(table_item->text().toFloat() -
307  prev_table_item->text().toFloat());
308 
309  // Point size differences between previous and current view.
310  const float dpoint_size =
311  view_data.point_size - prev_view_data.point_size;
312  const float dimage_size =
313  view_data.image_size - prev_view_data.image_size;
314 
315  const auto num_frames = dt * frame_rate;
316 
317  for (size_t i = 0; i < num_frames; ++i) {
318  const float t = i * frame_time;
319  float tt = t / dt;
320 
321  if (smooth_cb_->isChecked()) {
322  tt = ScaleSigmoid(tt,
323  static_cast<float>(smoothness_sb_->value()));
324  }
325 
326  ViewInterpolate interpolator(prev_view_data.viewportParams,
327  view_data.viewportParams);
328  ecvViewportParameters currentViewport;
329  interpolator.interpolate(currentViewport, static_cast<double>(tt));
330 
331  // Set point and image sizes.
332  model_viewer_widget_->StartRender();
333  model_viewer_widget_->SetPointSize(
334  prev_view_data.point_size + dpoint_size * tt, false);
335  model_viewer_widget_->SetImageSize(
336  prev_view_data.image_size + dimage_size * tt, false);
338  model_viewer_widget_->EndRender();
339  model_viewer_widget_->update();
340 
341  QImage image = model_viewer_widget_->GrabImage();
342  image.save(dir.filePath("frame" +
343  QString().asprintf("%06zu", frame_number) +
344  ".png"));
345  frame_number += 1;
346  progressDialog.setValue(frame_number);
347  QApplication::processEvents();
348  if (progressDialog.wasCanceled()) {
349  firstViewport = currentViewport;
350  break;
351  }
352  }
353  }
354 
355  views = views_cached;
356  model_viewer_widget_->SetPointSize(point_size_cached);
357  model_viewer_widget_->SetImageSize(image_size_cached);
358  model_viewer_widget_->UpdateMovieGrabber();
359  if (lengend_changed) {
361  }
362  if (coords_changed) {
364  }
367 }
368 
369 void MovieGrabberWidget::TimeChanged(QTableWidgetItem* item) {
370  table_->sortItems(0, Qt::AscendingOrder);
371  UpdateViews();
372  model_viewer_widget_->UpdateMovieGrabber();
373 }
374 
375 void MovieGrabberWidget::SelectionChanged(const QItemSelection& selected,
376  const QItemSelection& deselected) {
377  for (const auto& index : table_->selectionModel()->selectedIndexes()) {
378  model_viewer_widget_->SelectMoviewGrabberView(index.row());
379  }
380 }
381 
382 void MovieGrabberWidget::UpdateViews() {
383  views.clear();
384  colmap::camera_t base_id = 1000;
385  std::size_t lastNum = camera_ids_.size();
386  for (int row = 0; row < table_->rowCount(); ++row) {
387  const auto logical_idx = table_->verticalHeader()->logicalIndex(row);
388  QTableWidgetItem* item = table_->item(logical_idx, 0);
389 
390  const Eigen::Matrix4d model_view_matrix = ccGLMatrixd::ToEigenMatrix4(
391  view_data_.at(item).model_view_matrix);
392 
393  Image image;
394  colmap::camera_t camera_id;
395  std::size_t curIndex = static_cast<std::size_t>(row);
396  if (curIndex < lastNum) {
397  camera_id = camera_ids_[curIndex];
398  } else {
399  if (camera_ids_.empty()) {
400  camera_id = base_id + static_cast<colmap::camera_t>(row);
401  } else {
402  camera_id = camera_ids_[camera_ids_.size() - 1] + 1;
403  }
404 
405  camera_ids_.push_back(camera_id);
406  }
407 
408  image.SetImageId(camera_id);
409  image.SetCameraId(camera_id);
410 
411  image.Qvec() =
412  RotationMatrixToQuaternion(model_view_matrix.block<3, 3>(0, 0));
413  image.Tvec() = model_view_matrix.block<3, 1>(0, 3);
414  views.push_back(image);
415  }
416 }
417 
418 } // namespace cloudViewer
std::shared_ptr< core::Tensor > image
static bool Print(const char *format,...)
Prints out a formatted message in console.
Definition: CVLog.cpp:113
The ViewInterpolate class.
static Eigen::Matrix< double, 4, 4 > ToEigenMatrix4(const ccGLMatrixTpl< float > &mat)
Double version of ccGLMatrixTpl.
Definition: ecvGLMatrix.h:56
static cc2DViewportObject * To2DViewportObject(ccHObject *obj)
Converts current object to cc2DViewportObject (if possible)
std::vector< ccHObject * > Container
Standard instances container (for children, etc.)
Definition: ecvHObject.h:337
void SetImageSize(const float image_size, bool autoUpdate=true)
void SetPointSize(const float point_size, bool autoUpdate=true)
void EndRender(bool autoZoom=true)
void SelectMoviewGrabberView(const size_t view_idx)
MovieGrabberWidget(QWidget *parent, ModelViewerWidget *model_viewer_widget)
std::vector< colmap::Image > views
static const ecvViewportParameters & GetViewportParameters()
static void ToggleOrientationMarker(bool state=true)
static void DisplayOverlayEntities(bool state)
static bool OrientationMarkerShown()
static void SetViewportParameters(const ecvViewportParameters &params)
static void FilterByEntityType(ccHObject::Container &labels, CV_CLASS_ENUM type)
static bool OverlayEntitiesAreDisplayed()
static void UpdateScreen()
Standard parameters for GL displays/viewports.
__host__ __device__ int2 abs(int2 v)
Definition: cutil_math.h:1267
@ VIEWPORT_2D_OBJECT
Definition: CVTypes.h:141
static const std::string path
Definition: PointCloud.cpp:59
Generic file read and write utility for python interface.