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 
32 #include "ui/movie_grabber_widget.h"
33 
34 #include "base/pose.h"
35 #include "base/projection.h"
36 #include "ui/model_viewer_widget.h"
37 
38 namespace colmap {
39 
MovieGrabberWidget(QWidget * parent,ModelViewerWidget * model_viewer_widget)40 MovieGrabberWidget::MovieGrabberWidget(QWidget* parent,
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 
Add()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 
Delete()137 void MovieGrabberWidget::Delete() {
138   QModelIndexList selection = table_->selectionModel()->selectedIndexes();
139   foreach (QModelIndex index, selection) { table_->removeRow(index.row()); }
140   UpdateViews();
141   model_viewer_widget_->UpdateMovieGrabber();
142 }
143 
Clear()144 void MovieGrabberWidget::Clear() {
145   view_data_.clear();
146   while (table_->rowCount() > 0) {
147     table_->removeRow(0);
148   }
149   views.clear();
150   model_viewer_widget_->UpdateMovieGrabber();
151 }
152 
Assemble()153 void MovieGrabberWidget::Assemble() {
154   if (table_->rowCount() < 2) {
155     QMessageBox::critical(this, tr("Error"),
156                           tr("You must add at least two control views."));
157     return;
158   }
159 
160   if (model_viewer_widget_->GetProjectionType() !=
161       RenderOptions::ProjectionType::PERSPECTIVE) {
162     QMessageBox::critical(this, tr("Error"),
163                           tr("You must use perspective projection."));
164     return;
165   }
166 
167   const QString path = QFileDialog::getExistingDirectory(
168       this, tr("Choose destination..."), "", QFileDialog::ShowDirsOnly);
169 
170   // File dialog cancelled?
171   if (path == "") {
172     return;
173   }
174 
175   const QDir dir = QDir(path);
176 
177   const QMatrix4x4 model_view_matrix_cached =
178       model_viewer_widget_->ModelViewMatrix();
179   const float point_size_cached = model_viewer_widget_->PointSize();
180   const float image_size_cached = model_viewer_widget_->ImageSize();
181   const std::vector<Image> views_cached = views;
182 
183   // Make sure we do not render movie grabber path.
184   views.clear();
185   model_viewer_widget_->UpdateMovieGrabber();
186   model_viewer_widget_->DisableCoordinateGrid();
187 
188   const float frame_rate = frame_rate_sb_->value();
189   const float frame_time = 1.0f / frame_rate;
190   size_t frame_number = 0;
191 
192   // Data of first view.
193   const Eigen::Matrix4d prev_model_view_matrix =
194       QMatrixToEigen(view_data_[table_->item(0, 0)].model_view_matrix)
195           .cast<double>();
196   const Eigen::Matrix3x4d prev_view_model_matrix =
197       InvertProjectionMatrix(prev_model_view_matrix.topLeftCorner<3, 4>());
198   Eigen::Vector4d prev_qvec =
199       RotationMatrixToQuaternion(prev_view_model_matrix.block<3, 3>(0, 0));
200   Eigen::Vector3d prev_tvec = prev_view_model_matrix.block<3, 1>(0, 3);
201 
202   for (int row = 1; row < table_->rowCount(); ++row) {
203     const auto logical_idx = table_->verticalHeader()->logicalIndex(row);
204     QTableWidgetItem* prev_table_item = table_->item(logical_idx - 1, 0);
205     QTableWidgetItem* table_item = table_->item(logical_idx, 0);
206 
207     const ViewData& prev_view_data = view_data_.at(prev_table_item);
208     const ViewData& view_data = view_data_.at(table_item);
209 
210     // Data of next view.
211     const Eigen::Matrix4d curr_model_view_matrix =
212         QMatrixToEigen(view_data.model_view_matrix).cast<double>();
213     const Eigen::Matrix3x4d curr_view_model_matrix =
214         InvertProjectionMatrix(curr_model_view_matrix.topLeftCorner<3, 4>());
215     const Eigen::Vector4d curr_qvec =
216         RotationMatrixToQuaternion(curr_view_model_matrix.block<3, 3>(0, 0));
217     const Eigen::Vector3d curr_tvec = curr_view_model_matrix.block<3, 1>(0, 3);
218 
219     // Time difference between previous and current view.
220     const float dt = std::abs(table_item->text().toFloat() -
221                               prev_table_item->text().toFloat());
222 
223     // Point size differences between previous and current view.
224     const float dpoint_size = view_data.point_size - prev_view_data.point_size;
225     const float dimage_size = view_data.image_size - prev_view_data.image_size;
226 
227     const auto num_frames = dt * frame_rate;
228     for (size_t i = 0; i < num_frames; ++i) {
229       const float t = i * frame_time;
230       float tt = t / dt;
231 
232       if (smooth_cb_->isChecked()) {
233         tt = ScaleSigmoid(tt, static_cast<float>(smoothness_sb_->value()));
234       }
235 
236       // Compute current model-view matrix.
237       Eigen::Vector4d interp_qvec;
238       Eigen::Vector3d interp_tvec;
239       InterpolatePose(prev_qvec, prev_tvec, curr_qvec, curr_tvec, tt,
240                       &interp_qvec, &interp_tvec);
241 
242       Eigen::Matrix4d frame_model_view_matrix = Eigen::Matrix4d::Identity();
243       frame_model_view_matrix.topLeftCorner<3, 4>() = InvertProjectionMatrix(
244           ComposeProjectionMatrix(interp_qvec, interp_tvec));
245 
246       model_viewer_widget_->SetModelViewMatrix(
247           EigenToQMatrix(frame_model_view_matrix.cast<float>()));
248 
249       // Set point and image sizes.
250       model_viewer_widget_->SetPointSize(prev_view_data.point_size +
251                                          dpoint_size * tt);
252       model_viewer_widget_->SetImageSize(prev_view_data.image_size +
253                                          dimage_size * tt);
254 
255       QImage image = model_viewer_widget_->GrabImage();
256       image.save(dir.filePath(
257           "frame" + QString().sprintf("%06zu", frame_number) + ".png"));
258       frame_number += 1;
259     }
260 
261     prev_qvec = curr_qvec;
262     prev_tvec = curr_tvec;
263   }
264 
265   views = views_cached;
266   model_viewer_widget_->SetPointSize(point_size_cached);
267   model_viewer_widget_->SetImageSize(image_size_cached);
268   model_viewer_widget_->UpdateMovieGrabber();
269   model_viewer_widget_->EnableCoordinateGrid();
270   model_viewer_widget_->SetModelViewMatrix(model_view_matrix_cached);
271 }
272 
TimeChanged(QTableWidgetItem * item)273 void MovieGrabberWidget::TimeChanged(QTableWidgetItem* item) {
274   table_->sortItems(0, Qt::AscendingOrder);
275   UpdateViews();
276   model_viewer_widget_->UpdateMovieGrabber();
277 }
278 
SelectionChanged(const QItemSelection & selected,const QItemSelection & deselected)279 void MovieGrabberWidget::SelectionChanged(const QItemSelection& selected,
280                                           const QItemSelection& deselected) {
281   foreach (QModelIndex index, table_->selectionModel()->selectedIndexes()) {
282     model_viewer_widget_->SelectMoviewGrabberView(index.row());
283   }
284 }
285 
UpdateViews()286 void MovieGrabberWidget::UpdateViews() {
287   views.clear();
288   for (int row = 0; row < table_->rowCount(); ++row) {
289     const auto logical_idx = table_->verticalHeader()->logicalIndex(row);
290     QTableWidgetItem* item = table_->item(logical_idx, 0);
291 
292     const Eigen::Matrix4d model_view_matrix =
293         QMatrixToEigen(view_data_.at(item).model_view_matrix).cast<double>();
294     Image image;
295     image.Qvec() =
296         RotationMatrixToQuaternion(model_view_matrix.block<3, 3>(0, 0));
297     image.Tvec() = model_view_matrix.block<3, 1>(0, 3);
298     views.push_back(image);
299   }
300 }
301 
302 }  // namespace colmap
303