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