├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── image_warping ├── CMakeLists.txt ├── base_image_warping.cpp ├── base_image_warping.h ├── idw_image_warping.cpp ├── idw_image_warping.h ├── rbf_image_warping.cpp ├── rbf_image_warping.h ├── scanline.cpp └── scanline.h ├── image_warping_gui ├── CMakeLists.txt ├── image_widget.cpp ├── image_widget.h ├── main.cpp ├── mainwindow.cpp ├── mainwindow.h ├── mainwindow.qrc ├── mainwindow.ui └── resources │ └── images │ ├── copy.png │ ├── cut.png │ ├── new.png │ ├── open.png │ ├── paste.png │ └── save.png ├── screenshots ├── screenshots_0_origin.png ├── screenshots_0_select_ctrl_points.png └── screenshots_0_warped.png └── test_data ├── MonaLisa.bmp └── warp_test.bmp /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/c++ 2 | # Edit at https://www.gitignore.io/?templates=c++ 3 | 4 | ### C++ ### 5 | # Prerequisites 6 | *.d 7 | 8 | # Compiled Object files 9 | *.slo 10 | *.lo 11 | *.o 12 | *.obj 13 | 14 | # Precompiled Headers 15 | *.gch 16 | *.pch 17 | 18 | # Compiled Dynamic libraries 19 | *.so 20 | *.dylib 21 | *.dll 22 | 23 | # Fortran module files 24 | *.mod 25 | *.smod 26 | 27 | # Compiled Static libraries 28 | *.lai 29 | *.la 30 | *.a 31 | *.lib 32 | 33 | # Executables 34 | *.exe 35 | *.out 36 | *.app 37 | 38 | build/ 39 | # End of https://www.gitignore.io/api/c++ 40 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8.11) 2 | 3 | project(image_warping_demo) 4 | 5 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") 6 | 7 | set(CMAKE_AUTOMOC ON) 8 | set(CMAKE_PREFIX_PATH "/usr/local/Cellar/qt/5.12.3/lib/cmake/Qt5Widgets/") 9 | set(CMAKE_INCLUDE_CURRENT_DIR ON) 10 | 11 | find_package(Qt5Widgets REQUIRED) 12 | find_package(OpenCV REQUIRED) 13 | 14 | set(EIGEN_ROOT_DIR "/usr/local/include/eigen3/") 15 | 16 | include_directories( 17 | ${CMAKE_CURRENT_SOURCE_DIR} 18 | ${OpenCV_INCLUDE_DIRS} 19 | ${EIGEN_ROOT_DIR}) 20 | 21 | add_subdirectory(image_warping) 22 | add_subdirectory(image_warping_gui) 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Yilin Gui 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ImageWarping 2 | 3 | C++ Implementation of some image warping algorithms using control points. 4 | 5 | ## Dependencies 6 | 7 | - Qt 5 8 | - OpenCV 9 | - CMake 10 | - Eigen 11 | 12 | ## Build 13 | 14 | Please change [`CMAKE_PREFIX_PATH`](https://github.com/insaneyilin/ImageWarping/blob/master/CMakeLists.txt#L8) and [`EIGEN_ROOT_DIR`](https://github.com/insaneyilin/ImageWarping/blob/master/CMakeLists.txt#L14) in `CMakeLists.txt` according to your environment. 15 | 16 | ``` 17 | mkdir build 18 | cd build 19 | cmake .. 20 | make 21 | ``` 22 | 23 | ## Screenshots 24 | 25 | | ![Original](https://github.com/insaneyilin/ImageWarping/blob/master/screenshots/screenshots_0_origin.png) | ![Select control points](https://github.com/insaneyilin/ImageWarping/blob/master/screenshots/screenshots_0_select_ctrl_points.png) | ![Warped](https://github.com/insaneyilin/ImageWarping/blob/master/screenshots/screenshots_0_warped.png) | 26 | |:---:|:---:|:---:| 27 | | Original Image | Control points | Warped Image | 28 | 29 | ## Todo 30 | 31 | - [x] Implement Radial Basis Functions warping 32 | - [x] Implement Inverse Distance Weighted warping 33 | - [ ] Add technical report 34 | 35 | --- 36 | 37 | ## Reference 38 | 39 | Detlef Ruprecht and Heinrich Müller. Image warping with scattered data interpolation. IEEE Computer Graphics and Applications, 1995. 40 | 41 | Nur Arad and Daniel Reisfeld. Image Warping Using Few Anchor Points and Radial Functions. Computer Graphics Forum, 14(1): 35-46, 1995. 42 | -------------------------------------------------------------------------------- /image_warping/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(image_warping) 2 | 3 | file(GLOB _SRCS *.cpp) 4 | 5 | add_library(${PROJECT_NAME} ${_SRCS}) 6 | target_link_libraries(${PROJECT_NAME} ${OpenCV_LIBS}) 7 | -------------------------------------------------------------------------------- /image_warping/base_image_warping.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Yilin Gui. All rights reserved. 2 | // 3 | // filename: base_image_warping.cpp 4 | 5 | #include "image_warping/base_image_warping.h" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include "image_warping/scanline.h" 12 | 13 | namespace image_warping { 14 | 15 | BaseImageWarping::BaseImageWarping() { 16 | source_points_.reserve(100); 17 | target_points_.reserve(100); 18 | keypoints_.reserve(10000); 19 | transformed_keypoints_.reserve(10000); 20 | } 21 | 22 | BaseImageWarping::~BaseImageWarping() { 23 | } 24 | 25 | void BaseImageWarping::SetAnchorPoints( 26 | const std::vector &src_pts, 27 | const std::vector &tgt_pts) { 28 | source_points_.clear(); 29 | source_points_.assign(src_pts.begin(), src_pts.end()); 30 | target_points_.clear(); 31 | target_points_.assign(tgt_pts.begin(), tgt_pts.end()); 32 | 33 | SolveTransformations(); 34 | } 35 | 36 | void BaseImageWarping::WarpImage(cv::Mat *image) { 37 | const int width = image->cols; 38 | const int height = image->rows; 39 | 40 | paint_mask_.clear(); 41 | paint_mask_.resize(height, std::vector(width, 0)); 42 | image_mat_backup_ = image->clone(); 43 | image->setTo(cv::Scalar(255, 255, 255)); 44 | 45 | Eigen::Vector2f pt; 46 | Eigen::Vector2f trans_pt; 47 | for (int i = 0; i < height; ++i) { 48 | for (int j = 0; j < width; ++j) { 49 | pt[0] = j; 50 | pt[1] = i; 51 | trans_pt = GetTransformedPoint(pt); 52 | if (!IsValidImagePoint(trans_pt, width, height)) { 53 | continue; 54 | } 55 | int trans_x = static_cast(trans_pt[0]); 56 | int trans_y = static_cast(trans_pt[1]); 57 | image->at(trans_y, trans_x) = 58 | image_mat_backup_.at(i, j); 59 | paint_mask_[trans_y][trans_x] = 1; 60 | } 61 | } 62 | FillHole(image); 63 | } 64 | 65 | void BaseImageWarping::WarpImageWithTriangulation(cv::Mat *image) { 66 | const int width = image->cols; 67 | const int height = image->rows; 68 | 69 | paint_mask_.clear(); 70 | paint_mask_.resize(height, std::vector(width, 0)); 71 | image_mat_backup_ = image->clone(); 72 | image->setTo(cv::Scalar(255, 255, 255)); 73 | 74 | GenerateRandomKeyPointsAndDoTriangulation(width, height); 75 | 76 | // get transformed keypoints 77 | Eigen::Vector2f trans_pt; 78 | transformed_keypoints_.clear(); 79 | for (const auto &pt : keypoints_) { 80 | trans_pt = GetTransformedPoint(pt); 81 | transformed_keypoints_.push_back(trans_pt); 82 | if (!IsValidImagePoint(trans_pt, width, height)) { 83 | continue; 84 | } 85 | int orig_x = static_cast(pt[0]); 86 | int orig_y = static_cast(pt[1]); 87 | int trans_x = static_cast(trans_pt[0]); 88 | int trans_y = static_cast(trans_pt[1]); 89 | image->at(trans_y, trans_x) = 90 | image_mat_backup_.at(orig_y, orig_x); 91 | paint_mask_[trans_y][trans_x] = 1; 92 | } 93 | // warp triangles 94 | std::vector triangle_list; 95 | subdiv_2d_->getTriangleList(triangle_list); 96 | std::vector triangle(3); 97 | std::vector transformed_triangle(3); 98 | for(size_t i = 0; i < triangle_list.size(); ++i) { 99 | cv::Vec6f t = triangle_list[i]; 100 | bool valid_triangle = true; 101 | for (int ii = 0; ii < 3; ++ii) { 102 | triangle[ii][0] = t[ii * 2]; 103 | triangle[ii][1] = t[ii * 2 + 1]; 104 | transformed_triangle[ii] = GetTransformedPoint(triangle[ii]); 105 | if (!IsValidImagePoint(transformed_triangle[ii], width, height)) { 106 | valid_triangle = false; 107 | continue; 108 | } 109 | } 110 | if (!valid_triangle) { 111 | continue; 112 | } 113 | std::vector internal_pts; 114 | ScanLineAlgorithm::GetInternalPointsOfPolygon(transformed_triangle, &internal_pts); 115 | for (size_t k = 0; k < internal_pts.size(); ++k) { 116 | Eigen::Vector2f in_pt = internal_pts[k].cast(); 117 | Eigen::Vector3f bc_coords; 118 | GetBarycentricCoordinates(transformed_triangle[0], 119 | transformed_triangle[1], transformed_triangle[2], in_pt, 120 | &bc_coords); 121 | Eigen::Vector2f orig_interal_pt = 122 | bc_coords[0] * triangle[0] + bc_coords[1] * triangle[1] + 123 | bc_coords[2] * triangle[2]; 124 | if (!IsValidImagePoint(orig_interal_pt, width, height)) { 125 | continue; 126 | } 127 | int orig_x = static_cast(orig_interal_pt[0]); 128 | int orig_y = static_cast(orig_interal_pt[1]); 129 | int trans_x = static_cast(in_pt[0]); 130 | int trans_y = static_cast(in_pt[1]); 131 | image->at(trans_y, trans_x) = 132 | image_mat_backup_.at(orig_y, orig_x); 133 | paint_mask_[trans_y][trans_x] = 1; 134 | } 135 | } 136 | 137 | FillHole(image); 138 | } 139 | 140 | void BaseImageWarping::FillHole(cv::Mat *image) { 141 | const int width = image->cols; 142 | const int height = image->rows; 143 | const int half_ksize = 3; 144 | for (int i = 0; i < height; ++i) { 145 | for (int j = 0; j < width; ++j) { 146 | if (paint_mask_[i][j] == 1) { 147 | continue; 148 | } 149 | int min_dist = std::numeric_limits::max(); 150 | int min_dist_i = -1; 151 | int min_dist_j = -1; 152 | const int start_ii = std::max(0, i - half_ksize); 153 | const int end_ii = std::min(i + half_ksize, height); 154 | const int start_jj = std::max(0, j - half_ksize); 155 | const int end_jj = std::min(j + half_ksize, width); 156 | for (int ii = start_ii; ii < end_ii; ++ii) { 157 | for (int jj = start_jj; jj < end_jj; ++jj) { 158 | if (paint_mask_[ii][jj] == 0) { 159 | continue; 160 | } 161 | int dist = std::hypot(std::abs(ii - i), std::abs(jj - j)); 162 | if (dist < min_dist) { 163 | min_dist = dist; 164 | min_dist_i = ii; 165 | min_dist_j = jj; 166 | } 167 | } 168 | } 169 | if (min_dist_i < 0 || min_dist_j < 0) { 170 | continue; 171 | } 172 | image->at(i, j) = 173 | image->at(min_dist_i, min_dist_j); 174 | } 175 | } 176 | } 177 | 178 | float BaseImageWarping::Distance( 179 | const Eigen::Vector2f &p1, const Eigen::Vector2f &p2) { 180 | float diff_x = p1[0] - p2[0]; 181 | float diff_y = p1[1] - p2[1]; 182 | return std::hypot(std::fabs(diff_x), std::fabs(diff_y)); 183 | } 184 | 185 | void BaseImageWarping::GenerateRandomKeyPointsAndDoTriangulation( 186 | int width, int height) { 187 | subdiv_2d_.reset(new cv::Subdiv2D(cv::Rect(0, 0, width, height))); 188 | keypoints_.clear(); 189 | std::vector cv_pt_vec; 190 | int num_inner_points = static_cast(width * height * 0.001); 191 | Eigen::Vector2f pt; 192 | pt[0] = 0; 193 | pt[1] = 0; 194 | keypoints_.push_back(pt); 195 | cv_pt_vec.push_back(cv::Point2f(pt[0], pt[1])); 196 | pt[0] = width - 1; 197 | pt[1] = 0; 198 | keypoints_.push_back(pt); 199 | cv_pt_vec.push_back(cv::Point2f(pt[0], pt[1])); 200 | pt[0] = width - 1; 201 | pt[1] = height - 1; 202 | keypoints_.push_back(pt); 203 | cv_pt_vec.push_back(cv::Point2f(pt[0], pt[1])); 204 | pt[0] = 0; 205 | pt[1] = height - 1; 206 | keypoints_.push_back(pt); 207 | cv_pt_vec.push_back(cv::Point2f(pt[0], pt[1])); 208 | 209 | int num_pts_on_boundary = static_cast(std::sqrt(num_inner_points)); 210 | for (int i = 0; i < num_pts_on_boundary; ++i) { 211 | pt[0] = static_cast(rand() % width); 212 | pt[1] = 0; 213 | keypoints_.push_back(pt); 214 | cv_pt_vec.push_back(cv::Point2f(pt[0], pt[1])); 215 | 216 | pt[0] = static_cast(rand() % width); 217 | pt[1] = height - 1; 218 | keypoints_.push_back(pt); 219 | cv_pt_vec.push_back(cv::Point2f(pt[0], pt[1])); 220 | 221 | pt[0] = 0; 222 | pt[1] = static_cast(rand() % height); 223 | keypoints_.push_back(pt); 224 | cv_pt_vec.push_back(cv::Point2f(pt[0], pt[1])); 225 | 226 | pt[0] = width - 1; 227 | pt[1] = static_cast(rand() % height); 228 | keypoints_.push_back(pt); 229 | cv_pt_vec.push_back(cv::Point2f(pt[0], pt[1])); 230 | } 231 | for (int i = 0; i < num_inner_points; ++i) { 232 | pt[0] = static_cast(rand() % width); 233 | pt[1] = static_cast(rand() % height); 234 | keypoints_.push_back(pt); 235 | cv_pt_vec.push_back(cv::Point2f(pt[0], pt[1])); 236 | } 237 | subdiv_2d_->insert(cv_pt_vec); 238 | } 239 | 240 | void BaseImageWarping::GetBarycentricCoordinates( 241 | const Eigen::Vector2f &p1, 242 | const Eigen::Vector2f &p2, const Eigen::Vector2f &p3, 243 | const Eigen::Vector2f &p, Eigen::Vector3f *bc_coords) { 244 | const float x1 = p1[0]; 245 | const float y1 = p1[1]; 246 | const float x2 = p2[0]; 247 | const float y2 = p2[1]; 248 | const float x3 = p3[0]; 249 | const float y3 = p3[1]; 250 | const float x = p[0]; 251 | const float y = p[1]; 252 | 253 | const float x_minus_x3 = x - x3; 254 | const float y_minus_y3 = y - y3; 255 | const float x1_minus_x3 = x1 - x3; 256 | const float x3_minus_x2 = x3 - x2; 257 | const float y1_minus_y3 = y1 - y3; 258 | const float y2_minus_y3 = y2 - y3; 259 | const float y3_minus_y1 = y3 - y1; 260 | 261 | (*bc_coords)[0] = (y2_minus_y3 * x_minus_x3 + x3_minus_x2 * y_minus_y3) / 262 | (y2_minus_y3 * x1_minus_x3 + x3_minus_x2 * y1_minus_y3); 263 | (*bc_coords)[1] = (y3_minus_y1 * x_minus_x3 + x1_minus_x3 * y_minus_y3) / 264 | (y2_minus_y3 * x1_minus_x3 + x3_minus_x2 * y1_minus_y3); 265 | (*bc_coords)[2] = 1.f - (*bc_coords)[0] - (*bc_coords)[1]; 266 | } 267 | 268 | } // namespace image_warping 269 | -------------------------------------------------------------------------------- /image_warping/base_image_warping.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Yilin Gui. All rights reserved. 2 | // 3 | // filename: base_image_warping.h 4 | 5 | #ifndef BASE_IMAGE_WARPING_H_ 6 | #define BASE_IMAGE_WARPING_H_ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | namespace image_warping { 15 | 16 | class BaseImageWarping { 17 | public: 18 | BaseImageWarping(); 19 | virtual ~BaseImageWarping(); 20 | 21 | void SetAnchorPoints(const std::vector &src_pts, 22 | const std::vector &tgt_pts); 23 | 24 | void WarpImage(cv::Mat *image); 25 | 26 | void WarpImageWithTriangulation(cv::Mat *image); 27 | 28 | virtual std::string Name() const { 29 | return "BaseImageWarping"; 30 | } 31 | 32 | protected: 33 | virtual Eigen::Vector2f GetTransformedPoint(const Eigen::Vector2f &pt) = 0; 34 | void FillHole(cv::Mat *image); 35 | 36 | float Distance(const Eigen::Vector2f &p1, const Eigen::Vector2f &p2); 37 | 38 | bool IsValidImagePoint(const Eigen::Vector2f &pt, int width, int height) { 39 | int x = static_cast(pt[0]); 40 | int y = static_cast(pt[1]); 41 | return x >= 0 && x < width && y >= 0 && y < height; 42 | } 43 | 44 | virtual void SolveTransformations() = 0; 45 | 46 | void GenerateRandomKeyPointsAndDoTriangulation(int width, int height); 47 | 48 | void GetBarycentricCoordinates(const Eigen::Vector2f &p1, 49 | const Eigen::Vector2f &p2, const Eigen::Vector2f &p3, 50 | const Eigen::Vector2f &p, Eigen::Vector3f *bc_coords); 51 | 52 | protected: 53 | std::vector source_points_; 54 | std::vector target_points_; 55 | // paint_mask_[i][j] = 1 means pixel (i, j) is painted, else in 'hole' 56 | std::vector > paint_mask_; 57 | cv::Mat image_mat_backup_; 58 | 59 | std::vector keypoints_; 60 | std::vector transformed_keypoints_; 61 | std::shared_ptr subdiv_2d_; 62 | }; 63 | 64 | } // namespace image_warping 65 | 66 | #endif // BASE_IMAGE_WARPING_H_ 67 | -------------------------------------------------------------------------------- /image_warping/idw_image_warping.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Yilin Gui. All rights reserved. 2 | // 3 | // filename: idw_image_warping.cpp 4 | 5 | #include "image_warping/idw_image_warping.h" 6 | 7 | #include 8 | #include 9 | 10 | namespace image_warping { 11 | 12 | IDWImageWarping::IDWImageWarping() { 13 | } 14 | 15 | IDWImageWarping::~IDWImageWarping() { 16 | } 17 | 18 | Eigen::Vector2f IDWImageWarping::GetTransformedPoint( 19 | const Eigen::Vector2f &pt) { 20 | const int num_ctrl_pts = source_points_.size(); 21 | Eigen::Vector2f trans_pt; 22 | trans_pt[0] = 0.f; 23 | trans_pt[1] = 0.f; 24 | CalcWeights(pt); 25 | for (int i = 0; i < num_ctrl_pts; ++i) { 26 | trans_pt += weights_[i] * 27 | (target_points_[i] + 28 | local_trans_mat_list_[i] * (pt - source_points_[i])); 29 | } 30 | return trans_pt; 31 | } 32 | 33 | void IDWImageWarping::SolveTransformations() { 34 | SolveOptimalLocalTransformations(); 35 | } 36 | 37 | void IDWImageWarping::CalcWeights(const Eigen::Vector2f &pt) { 38 | const int num_ctrl_pts = source_points_.size(); 39 | float weights_sum = 0.f; 40 | weights_.clear(); 41 | for (int i = 0; i < num_ctrl_pts; ++i) { 42 | float w = std::pow(Distance(pt, source_points_[i]), -2.f); 43 | weights_.push_back(w); 44 | weights_sum += w; 45 | } 46 | const float eps = 1e-5; 47 | for (int i = 0; i < num_ctrl_pts; ++i) { 48 | weights_[i] /= weights_sum; 49 | } 50 | } 51 | 52 | void IDWImageWarping::SolveOptimalLocalTransformations() { 53 | const int num_ctrl_pts = source_points_.size(); 54 | local_trans_mat_list_.clear(); 55 | local_trans_mat_list_.resize(num_ctrl_pts); 56 | for (int i = 0; i < num_ctrl_pts; ++i) { 57 | local_trans_mat_list_[i] = Eigen::Matrix2f::Identity(); 58 | } 59 | if (num_ctrl_pts == 1) { 60 | return; 61 | } else { 62 | for (int i = 0; i < num_ctrl_pts; ++i) { 63 | Eigen::MatrixXf coeff_mat(2, 2); 64 | coeff_mat.setZero(); 65 | Eigen::VectorXf b_vec_1(2); 66 | Eigen::VectorXf b_vec_2(2); 67 | b_vec_1.setZero(); 68 | b_vec_2.setZero(); 69 | for (int j = 0; j < num_ctrl_pts; ++j) { 70 | if (i == j) { 71 | continue; 72 | } 73 | float sigma = std::pow( 74 | Distance(source_points_[i], source_points_[j]), -2.f); 75 | coeff_mat(0, 0) += sigma * (source_points_[j][0] - source_points_[i][0]) * 76 | (source_points_[j][0] - source_points_[i][0]); 77 | coeff_mat(0, 1) += sigma * (source_points_[j][0] - source_points_[i][0]) * 78 | (source_points_[j][1] - source_points_[i][1]); 79 | coeff_mat(1, 0) += sigma * (source_points_[j][1] - source_points_[i][1]) * 80 | (source_points_[j][0] - source_points_[i][0]); 81 | coeff_mat(1, 1) += sigma * (source_points_[j][1] - source_points_[i][1]) * 82 | (source_points_[j][1] - source_points_[i][1]); 83 | 84 | b_vec_1(0) += sigma * (source_points_[j][0] - source_points_[i][0]) * 85 | (target_points_[j][0] - target_points_[i][0]); 86 | b_vec_1(1) += sigma * (source_points_[j][1] - source_points_[i][1]) * 87 | (target_points_[j][0] - target_points_[i][0]); 88 | b_vec_2(0) += sigma * (source_points_[j][0] - source_points_[i][0]) * 89 | (target_points_[j][1] - target_points_[i][1]); 90 | b_vec_2(1) += sigma * (source_points_[j][1] - source_points_[i][1]) * 91 | (target_points_[j][1] - target_points_[i][1]); 92 | } 93 | b_vec_1 = coeff_mat.colPivHouseholderQr().solve(b_vec_1); 94 | b_vec_2 = coeff_mat.colPivHouseholderQr().solve(b_vec_2); 95 | local_trans_mat_list_[i](0, 0) = b_vec_1(0); 96 | local_trans_mat_list_[i](0, 1) = b_vec_1(1); 97 | local_trans_mat_list_[i](1, 0) = b_vec_2(0); 98 | local_trans_mat_list_[i](1, 1) = b_vec_2(1); 99 | } 100 | } 101 | } 102 | 103 | } // namespace image_warping 104 | -------------------------------------------------------------------------------- /image_warping/idw_image_warping.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Yilin Gui. All rights reserved. 2 | // 3 | // filename: idw_image_warping.h 4 | // 5 | // Image warping using Inverse Distance Weighted interpolation 6 | // 7 | // Input a list of control point pairs {(p_i, q_i)}, 8 | // 9 | 10 | #ifndef IDW_IMAGE_WARPING_H_ 11 | #define IDW_IMAGE_WARPING_H_ 12 | 13 | #include "image_warping/base_image_warping.h" 14 | 15 | namespace image_warping { 16 | 17 | class IDWImageWarping : public BaseImageWarping { 18 | public: 19 | IDWImageWarping(); 20 | virtual ~IDWImageWarping(); 21 | 22 | virtual std::string Name() const override { 23 | return "IDWImageWarping"; 24 | } 25 | 26 | protected: 27 | virtual Eigen::Vector2f GetTransformedPoint( 28 | const Eigen::Vector2f &pt) override; 29 | virtual void SolveTransformations() override; 30 | 31 | private: 32 | void CalcWeights(const Eigen::Vector2f &pt); 33 | void SolveOptimalLocalTransformations(); 34 | 35 | private: 36 | std::vector weights_; 37 | std::vector local_trans_mat_list_; 38 | }; 39 | 40 | } // namespace image_warping 41 | 42 | #endif // IDW_IMAGE_WARPING_H_ 43 | -------------------------------------------------------------------------------- /image_warping/rbf_image_warping.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Yilin Gui. All rights reserved. 2 | // 3 | // filename: rbf_image_warping.cpp 4 | 5 | #include "image_warping/rbf_image_warping.h" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | namespace image_warping { 12 | 13 | RBFImageWarping::RBFImageWarping() { 14 | } 15 | 16 | RBFImageWarping::~RBFImageWarping() { 17 | } 18 | 19 | Eigen::Vector2f RBFImageWarping::GetTransformedPoint( 20 | const Eigen::Vector2f &pt) { 21 | const int num_ctrl_pts = source_points_.size(); 22 | Eigen::Vector2f trans_pt; 23 | trans_pt[0] = 0.f; 24 | trans_pt[1] = 0.f; 25 | for (int i = 0; i < num_ctrl_pts; ++i) { 26 | float dist = Distance(source_points_[i], pt); 27 | float g_i = HardyMultiQuadricFunction(dist, i); 28 | trans_pt[0] += g_i * alpha_x_list_[i]; 29 | trans_pt[1] += g_i * alpha_y_list_[i]; 30 | } 31 | trans_pt[0] += pt[0]; 32 | trans_pt[1] += pt[1]; 33 | return trans_pt; 34 | } 35 | 36 | void RBFImageWarping::SolveTransformations() { 37 | // get min radius list 38 | min_radius_list_.clear(); 39 | for (int i = 0; i < source_points_.size(); ++i) { 40 | min_radius_list_.push_back(CalcMinRadius(i)); 41 | } 42 | 43 | // solve linear system to get coeffs 44 | SolveLinearSystem(); 45 | } 46 | 47 | float RBFImageWarping::HardyMultiQuadricFunction(float distance, int i) { 48 | return std::sqrt(distance * distance + 49 | min_radius_list_[i] * min_radius_list_[i]); 50 | } 51 | 52 | float RBFImageWarping::CalcMinRadius(int i) { 53 | float min_dist = std::numeric_limits::max(); 54 | const int num_ctrl_pts = source_points_.size(); 55 | if (source_points_.size() <= 1) { 56 | return 0.f; 57 | } 58 | for (int j = 0; j < num_ctrl_pts; ++j) { 59 | if (i == j) { 60 | continue; 61 | } 62 | float dist = Distance(source_points_[j], target_points_[j]); 63 | if (dist < min_dist) { 64 | min_dist = dist; 65 | } 66 | } 67 | return min_dist; 68 | } 69 | 70 | void RBFImageWarping::SolveLinearSystem() { 71 | const int num_ctrl_pts = source_points_.size(); 72 | if (num_ctrl_pts == 0) { 73 | return; 74 | } 75 | coeff_mat_.resize(num_ctrl_pts, num_ctrl_pts); 76 | alpha_x_vec_.resize(num_ctrl_pts); 77 | alpha_y_vec_.resize(num_ctrl_pts); 78 | diff_x_vec_.resize(num_ctrl_pts); 79 | diff_y_vec_.resize(num_ctrl_pts); 80 | for (int i = 0; i < num_ctrl_pts; ++i) { 81 | for (int j = 0; j < num_ctrl_pts; ++j) { 82 | float dist = Distance(source_points_[i], source_points_[j]); 83 | coeff_mat_(i, j) = HardyMultiQuadricFunction(dist, i); 84 | } 85 | diff_x_vec_(i) = target_points_[i][0] - source_points_[i][0]; 86 | diff_y_vec_(i) = target_points_[i][1] - source_points_[i][1]; 87 | } 88 | alpha_x_vec_ = coeff_mat_.colPivHouseholderQr().solve(diff_x_vec_); 89 | alpha_y_vec_ = coeff_mat_.colPivHouseholderQr().solve(diff_y_vec_); 90 | // special handling if there is only one pair of ctrl points 91 | if (num_ctrl_pts == 1) { 92 | const float coeff = coeff_mat_(0, 0) + 1e-5f; 93 | alpha_x_vec_(0) = diff_x_vec_(0) / coeff; 94 | alpha_y_vec_(0) = diff_y_vec_(0) / coeff; 95 | } 96 | alpha_x_list_.clear(); 97 | alpha_y_list_.clear(); 98 | for (int i = 0; i < num_ctrl_pts; ++i) { 99 | alpha_x_list_.push_back(alpha_x_vec_(i)); 100 | alpha_y_list_.push_back(alpha_y_vec_(i)); 101 | } 102 | } 103 | 104 | } // namespace image_warping 105 | -------------------------------------------------------------------------------- /image_warping/rbf_image_warping.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Yilin Gui. All rights reserved. 2 | // 3 | // filename: rbf_image_warping.h 4 | // 5 | // Image warping using Radial Basis Functions 6 | // 7 | // Input a list of control point pairs {(p_i, q_i)}, 8 | // 9 | 10 | #ifndef RBF_IMAGE_WARPING_H_ 11 | #define RBF_IMAGE_WARPING_H_ 12 | 13 | #include "image_warping/base_image_warping.h" 14 | 15 | namespace image_warping { 16 | 17 | class RBFImageWarping : public BaseImageWarping { 18 | public: 19 | RBFImageWarping(); 20 | virtual ~RBFImageWarping(); 21 | 22 | virtual std::string Name() const override { 23 | return "RBFImageWarping"; 24 | } 25 | 26 | protected: 27 | virtual Eigen::Vector2f GetTransformedPoint( 28 | const Eigen::Vector2f &pt) override; 29 | virtual void SolveTransformations() override; 30 | 31 | private: 32 | float HardyMultiQuadricFunction(float distance, int i); 33 | float CalcMinRadius(int i); 34 | void SolveLinearSystem(); 35 | 36 | private: 37 | Eigen::MatrixXf coeff_mat_; 38 | Eigen::VectorXf alpha_x_vec_; 39 | Eigen::VectorXf alpha_y_vec_; 40 | Eigen::VectorXf diff_x_vec_; 41 | Eigen::VectorXf diff_y_vec_; 42 | 43 | std::vector min_radius_list_; 44 | std::vector alpha_x_list_; 45 | std::vector alpha_y_list_; 46 | }; 47 | 48 | } // namespace image_warping 49 | 50 | #endif // RBF_IMAGE_WARPING_H_ 51 | -------------------------------------------------------------------------------- /image_warping/scanline.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Yilin Gui. All rights reserved. 2 | // 3 | // filename: scanline.cpp 4 | 5 | #include "image_warping/scanline.h" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | using Edge = image_warping::ScanLineAlgorithm::Edge; 12 | 13 | namespace image_warping { 14 | 15 | int ScanLineAlgorithm::s_y_min_ = 1e9; 16 | int ScanLineAlgorithm::s_y_max_ = -1e9; 17 | std::vector > ScanLineAlgorithm::s_et_; 18 | std::vector ScanLineAlgorithm::s_aet_; 19 | 20 | void ScanLineAlgorithm::GetInternalPointsOfPolygon( 21 | const std::vector &polygon, 22 | std::vector *points) { 23 | points->clear(); 24 | s_y_min_ = 1e9; 25 | s_y_max_ = -1e9; 26 | s_et_.clear(); 27 | s_aet_.clear(); 28 | 29 | const int num_poly_pts = polygon.size(); 30 | for (int i = 0; i < num_poly_pts; ++i) { 31 | const auto &pt = polygon[i]; 32 | int x1 = pt[0]; 33 | int y1 = pt[1]; 34 | s_y_min_ = std::min(y1, s_y_min_); 35 | s_y_max_ = std::max(y1, s_y_max_); 36 | } 37 | s_et_.resize(s_y_max_ - s_y_min_ + 1); 38 | for (int i = 0; i < num_poly_pts; ++i) { 39 | const auto &pt = polygon[i]; 40 | int x1 = pt[0]; 41 | int y1 = pt[1]; 42 | const auto &next_pt = polygon[(i + 1) % num_poly_pts]; 43 | int x2 = next_pt[0]; 44 | int y2 = next_pt[1]; 45 | 46 | AddEdge(x1, y1, x2, y2); 47 | } 48 | // scan lines 49 | for (int i = 0; i < s_y_max_ - s_y_min_ + 1; ++i) { 50 | for (int j = 0; j < s_et_[i].size(); ++j) { 51 | s_aet_.push_back(s_et_[i][j]); 52 | } 53 | std::sort(s_aet_.begin(), s_aet_.end(), 54 | [](const Edge &e1, const Edge &e2) { 55 | if (std::fabs(e1.x - e2.x) < 1e-6) { 56 | return e1.k < e2.k; 57 | } else { 58 | return e1.x < e2.x; 59 | } 60 | }); 61 | const int aet_size = s_aet_.size(); 62 | for (int j = 0; j + 1 < aet_size; j += 2) { 63 | int l_start = (s_aet_[j].x - static_cast(s_aet_[j].x > 0.5f) ? 64 | static_cast(s_aet_[j].x) + 1 : static_cast(s_aet_[j].x)); 65 | int l_end = (s_aet_[j + 1].x - static_cast(s_aet_[j + 1].x > 0.5f) ? 66 | static_cast(s_aet_[j + 1].x) + 1 : 67 | static_cast(s_aet_[j + 1].x)); 68 | for (int l = l_start; l <= l_end; ++l) { 69 | Eigen::Vector2i pti; 70 | pti[0] = l; 71 | pti[1] = i + s_y_min_; 72 | points->push_back(pti); 73 | } 74 | } 75 | for (auto itr = s_aet_.begin(); itr != s_aet_.end();) { 76 | if (itr->ymax <= i + s_y_min_) { 77 | s_aet_.erase(itr); 78 | } else { 79 | ++itr; 80 | } 81 | } 82 | // incrementally update 83 | for (auto &e : s_aet_) { 84 | e.x += e.k; 85 | } 86 | } 87 | } 88 | 89 | void ScanLineAlgorithm::AddEdge(int x1, int y1, int x2, int y2) { 90 | Edge edge; 91 | // ignore horizontal lines 92 | if (y1 < y2) { 93 | edge.ymax = y2 - 1; 94 | edge.x = x1; 95 | edge.k = 1.f * (x2 - x1) / (y2 - y1); 96 | s_et_[y1 - s_y_min_].push_back(edge); 97 | } else if (y1 > y2) { 98 | edge.ymax = y1 - 1; 99 | edge.x = x2; 100 | edge.k = 1.f * (x2 - x1) / (y2 - y1); 101 | s_et_[y2 - s_y_min_].push_back(edge); 102 | } 103 | } 104 | 105 | } // namespace image_warping 106 | -------------------------------------------------------------------------------- /image_warping/scanline.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Yilin Gui. All rights reserved. 2 | // 3 | // filename: scanline.h 4 | 5 | #ifndef SCANLINE_H_ 6 | #define SCANLINE_H_ 7 | 8 | #include 9 | #include 10 | 11 | namespace image_warping { 12 | 13 | class ScanLineAlgorithm { 14 | public: 15 | static void GetInternalPointsOfPolygon( 16 | const std::vector &polygon, 17 | std::vector *points); 18 | 19 | struct Edge { 20 | float x; 21 | float k; 22 | int ymax; 23 | }; 24 | 25 | private: 26 | ScanLineAlgorithm() = default; 27 | ~ScanLineAlgorithm() = default; 28 | 29 | static void AddEdge(int x1, int y1, int x2, int y2); 30 | 31 | private: 32 | static int s_y_min_; 33 | static int s_y_max_; 34 | 35 | // edge table 36 | static std::vector > s_et_; 37 | 38 | // active edge table 39 | static std::vector s_aet_; 40 | }; 41 | 42 | } // namespace image_warping 43 | 44 | #endif // SCANLINE_H_ 45 | -------------------------------------------------------------------------------- /image_warping_gui/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(APP_NAME image_warping_demo) 2 | 3 | qt5_wrap_cpp(MAIN_WINDOW_SRCS 4 | mainwindow.h 5 | image_widget.h) 6 | qt5_wrap_ui(MAIN_WINDOW_UI 7 | mainwindow.ui) 8 | qt5_add_resources(MAIN_WINDOW_RCCS mainwindow.qrc) 9 | 10 | file(GLOB _SRCS *.cpp) 11 | 12 | add_executable(${APP_NAME} 13 | ${_SRCS} 14 | ${MAIN_WINDOW_RCCS} 15 | ${MAIN_WINDOW_UI} 16 | ${MAIN_WINDOW_SRCS}) 17 | 18 | target_link_libraries(${APP_NAME} 19 | Qt5::Widgets 20 | ${OpenCV_LIBS} 21 | image_warping) 22 | -------------------------------------------------------------------------------- /image_warping_gui/image_widget.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Yilin Gui. All rights reserved. 2 | // 3 | // filename: image_widget.cpp 4 | 5 | #include "image_widget.h" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | #include 14 | 15 | #include "image_warping/rbf_image_warping.h" 16 | #include "image_warping/idw_image_warping.h" 17 | 18 | ImageWidget::ImageWidget() { 19 | InitWarpingInstanceMap(); 20 | } 21 | 22 | ImageWidget::~ImageWidget() { 23 | } 24 | 25 | void ImageWidget::paintEvent(QPaintEvent *paintevent) { 26 | QPainter painter; 27 | painter.begin(this); 28 | 29 | // Draw background 30 | painter.setBrush(Qt::lightGray); 31 | QRect back_rect(0, 0, width(), height()); 32 | painter.drawRect(back_rect); 33 | 34 | // Draw image 35 | QImage image_show = QImage((unsigned char *)(image_mat_.data), 36 | image_mat_.cols, image_mat_.rows, image_mat_.step, 37 | QImage::Format_RGB888); 38 | QRect rect = QRect(0, 0, image_show.width(), image_show.height()); 39 | painter.drawImage(rect, image_show); 40 | 41 | // Draw control lines 42 | if (select_mode_) { 43 | QPen pen(Qt::red, 4); 44 | painter.setPen(pen); 45 | const int num_ctrl_pts = source_points_.size(); 46 | for (int i = 0; i < num_ctrl_pts; ++i) { 47 | QPoint p1(static_cast(source_points_[i][0]), 48 | static_cast(source_points_[i][1])); 49 | QPoint p2(static_cast(target_points_[i][0]), 50 | static_cast(target_points_[i][1])); 51 | painter.drawLine(p1, p2); 52 | } 53 | if (is_drawing_) { 54 | painter.drawLine(point_start_, point_end_); 55 | } 56 | } 57 | 58 | painter.end(); 59 | } 60 | 61 | void ImageWidget::mousePressEvent(QMouseEvent *mouseevent) { 62 | if (select_mode_ && mouseevent->button() == Qt::LeftButton) { 63 | is_drawing_ = true; 64 | auto pt = mouseevent->pos(); 65 | point_start_ = pt; 66 | point_end_ = pt; 67 | update(); 68 | } 69 | } 70 | 71 | void ImageWidget::mouseMoveEvent(QMouseEvent *mouseevent) { 72 | if (select_mode_ && is_drawing_) { 73 | point_end_ = mouseevent->pos(); 74 | 75 | if (realtime_warping_mode_) { 76 | std::vector src_pts = source_points_; 77 | std::vector tgt_pts = target_points_; 78 | Eigen::Vector2f pt; 79 | pt[0] = point_start_.x(); 80 | pt[1] = point_start_.y(); 81 | src_pts.push_back(pt); 82 | pt[0] = point_end_.x(); 83 | pt[1] = point_end_.y(); 84 | tgt_pts.push_back(pt); 85 | image_warping_ = warping_inst_map_[warping_method_].get(); 86 | image_warping_->SetAnchorPoints(src_pts, tgt_pts); 87 | image_warping_->WarpImage(&image_mat_); 88 | // image_warping_->WarpImageWithTriangulation(&image_mat_); 89 | } 90 | 91 | update(); 92 | } 93 | } 94 | 95 | void ImageWidget::mouseReleaseEvent(QMouseEvent *mouseevent) { 96 | if (select_mode_ && is_drawing_) { 97 | Eigen::Vector2f pt; 98 | pt[0] = point_start_.x(); 99 | pt[1] = point_start_.y(); 100 | source_points_.push_back(pt); 101 | 102 | pt[0] = point_end_.x(); 103 | pt[1] = point_end_.y(); 104 | target_points_.push_back(pt); 105 | 106 | is_drawing_ = false; 107 | update(); 108 | } 109 | } 110 | 111 | void ImageWidget::Open() { 112 | // Open file 113 | QString file_name = QFileDialog::getOpenFileName(this, 114 | tr("Read Image"), ".", tr("Images(*.bmp *.png *.jpg)")); 115 | 116 | // Load file 117 | if (!file_name.isEmpty()) { 118 | image_mat_ = cv::imread(file_name.toLatin1().data()); 119 | cv::cvtColor(image_mat_, image_mat_, CV_BGR2RGB); 120 | image_mat_backup_ = image_mat_.clone(); 121 | } 122 | 123 | update(); 124 | } 125 | 126 | void ImageWidget::Save() { 127 | SaveAs(); 128 | } 129 | 130 | void ImageWidget::SaveAs() { 131 | QString filename = QFileDialog::getSaveFileName(this, 132 | tr("Save Image"), ".", tr("Images(*.bmp *.png *.jpg)")); 133 | if (filename.isNull()) { 134 | return; 135 | } 136 | cv::cvtColor(image_mat_, image_mat_bgr_, CV_RGB2BGR); 137 | cv::imwrite(filename.toLatin1().data(), image_mat_bgr_); 138 | } 139 | 140 | void ImageWidget::Invert() { 141 | cv::MatIterator_ iter; 142 | cv::MatIterator_ iter_end; 143 | for (iter = image_mat_.begin(), 144 | iter_end = image_mat_.end(); iter != iter_end; ++iter) { 145 | (*iter)[0] = 255 - (*iter)[0]; 146 | (*iter)[1] = 255 - (*iter)[1]; 147 | (*iter)[2] = 255 - (*iter)[2]; 148 | } 149 | 150 | update(); 151 | } 152 | 153 | void ImageWidget::Mirror(bool is_horizontal, bool is_vertical) { 154 | cv::Mat image_mat_tmp = image_mat_.clone(); 155 | int width = image_mat_.cols; 156 | int height = image_mat_.rows; 157 | 158 | int mode = -1; 159 | if (!is_horizontal && !is_vertical) { 160 | return; 161 | } else if (is_horizontal && is_vertical) { 162 | mode = 0; 163 | } else if (is_horizontal && !is_vertical) { 164 | mode = 1; 165 | } else if (!is_horizontal && is_vertical) { 166 | mode = 2; 167 | } 168 | 169 | for (int i = 0; i < width; ++i) { 170 | for (int j = 0; j < height; ++j) { 171 | switch (mode) { 172 | case 0: 173 | image_mat_.at(j, i) = 174 | image_mat_tmp.at(height - 1 - j, width - 1 - i); 175 | break; 176 | case 1: 177 | image_mat_.at(j, i) = 178 | image_mat_tmp.at(height - 1 - j, i); 179 | break; 180 | case 2: 181 | image_mat_.at(j, i) = 182 | image_mat_tmp.at(j, width - 1 - i); 183 | break; 184 | default: 185 | break; 186 | } 187 | } 188 | } 189 | 190 | update(); 191 | } 192 | 193 | void ImageWidget::ToGray() { 194 | cv::MatIterator_ iter; 195 | cv::MatIterator_ iter_end; 196 | for (iter = image_mat_.begin(), 197 | iter_end = image_mat_.end(); iter != iter_end; ++iter) { 198 | int gray_val = static_cast( 199 | ((*iter)[0] + (*iter)[1] + (*iter)[2]) / 3.f); 200 | (*iter)[0] = gray_val; 201 | (*iter)[1] = gray_val; 202 | (*iter)[2] = gray_val; 203 | } 204 | 205 | update(); 206 | } 207 | 208 | void ImageWidget::Restore() { 209 | image_mat_ = image_mat_backup_.clone(); 210 | update(); 211 | } 212 | 213 | void ImageWidget::Warp() { 214 | image_warping_ = warping_inst_map_[warping_method_].get(); 215 | image_warping_->SetAnchorPoints(source_points_, target_points_); 216 | image_warping_->WarpImage(&image_mat_); 217 | // image_warping_->WarpImageWithTriangulation(&image_mat_); 218 | 219 | update(); 220 | } 221 | 222 | void ImageWidget::ClearControlPoints() { 223 | source_points_.clear(); 224 | target_points_.clear(); 225 | 226 | update(); 227 | } 228 | 229 | void ImageWidget::UndoSelect() { 230 | if (!source_points_.empty() && !target_points_.empty()) { 231 | source_points_.pop_back(); 232 | target_points_.pop_back(); 233 | } 234 | 235 | update(); 236 | } 237 | 238 | void ImageWidget::SetSelectMode(bool status) { 239 | select_mode_ = status; 240 | if (!select_mode_) { 241 | ClearControlPoints(); 242 | } 243 | 244 | update(); 245 | } 246 | 247 | void ImageWidget::InitWarpingInstanceMap() { 248 | warping_inst_map_.clear(); 249 | warping_inst_map_["IDW"].reset(new image_warping::IDWImageWarping()); 250 | warping_inst_map_["RBF"].reset(new image_warping::RBFImageWarping()); 251 | } 252 | -------------------------------------------------------------------------------- /image_warping_gui/image_widget.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Yilin Gui. All rights reserved. 2 | // 3 | // filename: image_widget.h 4 | 5 | #ifndef IMAGE_WIDGET_H_ 6 | #define IMAGE_WIDGET_H_ 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "image_warping/base_image_warping.h" 18 | 19 | QT_BEGIN_NAMESPACE 20 | class QImage; 21 | class QPainter; 22 | QT_END_NAMESPACE 23 | 24 | class ImageWidget : public QWidget { 25 | Q_OBJECT 26 | 27 | public: 28 | ImageWidget(); 29 | ~ImageWidget(); 30 | 31 | protected: 32 | void paintEvent(QPaintEvent *paintevent); 33 | 34 | void mousePressEvent(QMouseEvent *mouseevent); 35 | void mouseMoveEvent(QMouseEvent *mouseevent); 36 | void mouseReleaseEvent(QMouseEvent *mouseevent); 37 | 38 | public slots: 39 | /// File IO 40 | // Open an image file, support ".bmp, .png, .jpg" format 41 | void Open(); 42 | // Save image to current file 43 | void Save(); 44 | // Save image to another file 45 | void SaveAs(); 46 | 47 | /// Image processing 48 | // Invert pixel value in image 49 | void Invert(); 50 | // Mirror image vertically or horizontally 51 | void Mirror(bool horizontal = false, bool vertical = true); 52 | // convert to grayscale 53 | void ToGray(); 54 | // Restore image to origin 55 | void Restore(); 56 | 57 | /// Image Warping 58 | void Warp(); 59 | 60 | void ClearControlPoints(); 61 | 62 | void UndoSelect(); 63 | 64 | void SetSelectMode(bool status); 65 | 66 | void SetWarpingMethod(const std::string &name) { 67 | warping_method_ = name; 68 | } 69 | 70 | void SetRealTimeWarpingMode(bool status) { 71 | realtime_warping_mode_ = status; 72 | } 73 | 74 | private: 75 | void InitWarpingInstanceMap(); 76 | 77 | private: 78 | cv::Mat image_mat_; // save image in rgb format 79 | cv::Mat image_mat_backup_; 80 | cv::Mat image_mat_bgr_; // save image in bgr format, for writing to file 81 | 82 | bool is_drawing_ = false; 83 | bool select_mode_ = false; 84 | bool realtime_warping_mode_ = false; 85 | 86 | QPoint point_start_; 87 | QPoint point_end_; 88 | 89 | std::vector source_points_; 90 | std::vector target_points_; 91 | 92 | // warping method name ("IDW"/"RBF") -> warping instance 93 | std::unordered_map> warping_inst_map_; 95 | std::string warping_method_ = "IDW"; 96 | image_warping::BaseImageWarping *image_warping_ = nullptr; 97 | }; 98 | 99 | #endif // IMAGE_WIDGET_H_ 100 | -------------------------------------------------------------------------------- /image_warping_gui/main.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Yilin Gui. All rights reserved. 2 | // 3 | // filename: main.cpp 4 | 5 | #include 6 | #include "mainwindow.h" 7 | 8 | int main(int argc, char **argv) { 9 | QApplication a(argc, argv); 10 | MainWindow w; 11 | w.show(); 12 | return a.exec(); 13 | } 14 | -------------------------------------------------------------------------------- /image_warping_gui/mainwindow.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Yilin Gui. All rights reserved. 2 | // 3 | // filename: mainwindow.cpp 4 | 5 | #include "mainwindow.h" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | #include "image_widget.h" 14 | 15 | MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { 16 | ui_.setupUi(this); 17 | 18 | setGeometry(200, 200, 960, 540); 19 | 20 | imagewidget_ = new ImageWidget(); 21 | setCentralWidget(imagewidget_); 22 | 23 | CreateActions(); 24 | CreateMenus(); 25 | CreateToolBars(); 26 | CreateStatusBar(); 27 | } 28 | 29 | MainWindow::~MainWindow() { 30 | // no need to `delete imagewidget_` 31 | // 'Q_OBJECT' will release the memory 32 | } 33 | 34 | void MainWindow::closeEvent(QCloseEvent *e) { 35 | } 36 | 37 | void MainWindow::paintEvent(QPaintEvent* paintevent) { 38 | } 39 | 40 | void MainWindow::CreateActions() { 41 | action_new_ = new QAction(QIcon(":/MainWindow/resources/images/new.png"), 42 | tr("&New"), this); 43 | action_new_->setShortcut(QKeySequence::New); 44 | action_new_->setStatusTip(tr("Create a new file")); 45 | 46 | action_open_ = new QAction(QIcon(":/MainWindow/resources/images/open.png"), 47 | tr("&Open..."), this); 48 | action_open_->setShortcuts(QKeySequence::Open); 49 | action_open_->setStatusTip(tr("Open an existing file")); 50 | connect(action_open_, SIGNAL(triggered()), imagewidget_, SLOT(Open())); 51 | 52 | action_save_ = new QAction(QIcon(":/MainWindow/resources/images/save.png"), 53 | tr("&Save"), this); 54 | action_save_->setShortcuts(QKeySequence::Save); 55 | action_save_->setStatusTip(tr("Save the document to disk")); 56 | 57 | action_saveas_ = new QAction(tr("Save &As..."), this); 58 | action_saveas_->setShortcuts(QKeySequence::SaveAs); 59 | action_saveas_->setStatusTip(tr("Save the document under a new name")); 60 | connect(action_saveas_, SIGNAL(triggered()), imagewidget_, SLOT(SaveAs())); 61 | 62 | action_invert_ = new QAction(tr("Inverse"), this); 63 | action_invert_->setStatusTip(tr("Invert all pixel value in the image")); 64 | connect(action_invert_, SIGNAL(triggered()), imagewidget_, SLOT(Invert())); 65 | 66 | action_mirror_ = new QAction(tr("Mirror"), this); 67 | action_mirror_->setStatusTip(tr("Mirror image vertically or horizontally")); 68 | connect(action_mirror_, SIGNAL(triggered()), imagewidget_, SLOT(Mirror())); 69 | 70 | action_gray_ = new QAction(tr("Grayscale"), this); 71 | action_gray_->setStatusTip(tr("Gray-scale map")); 72 | connect(action_gray_, SIGNAL(triggered()), imagewidget_, SLOT(ToGray())); 73 | 74 | action_restore_ = new QAction(tr("Restore"), this); 75 | action_restore_->setStatusTip(tr("Show origin image")); 76 | connect(action_restore_, SIGNAL(triggered()), imagewidget_, SLOT(Restore())); 77 | 78 | action_select_points_ = new QAction(tr("Select Points"), this); 79 | action_select_points_->setStatusTip(tr("Select Points")); 80 | action_select_points_->setCheckable(true); 81 | action_select_points_->setChecked(false); 82 | connect(action_select_points_, SIGNAL(triggered()), this, 83 | SLOT(ChangeSelectPointsMode())); 84 | 85 | action_undo_select_ = new QAction(tr("Undo Select"), this); 86 | action_undo_select_->setStatusTip(tr("Undo Select")); 87 | connect(action_undo_select_, SIGNAL(triggered()), imagewidget_, SLOT(UndoSelect())); 88 | 89 | action_do_warp_ = new QAction(tr("Warp"), this); 90 | action_do_warp_->setStatusTip(tr("Warp Image")); 91 | connect(action_do_warp_, SIGNAL(triggered()), imagewidget_, SLOT(Warp())); 92 | 93 | action_use_idw_warping_ = new QAction(tr("IDW warping"), this); 94 | action_use_idw_warping_->setStatusTip(tr("Inverse Distance Weighted")); 95 | 96 | action_use_rbf_warping_ = new QAction(tr("RBF warping"), this); 97 | action_use_rbf_warping_->setStatusTip(tr("Radial Basis Function")); 98 | 99 | action_grp_warping_method_ = new QActionGroup(this); 100 | action_grp_warping_method_->addAction(action_use_idw_warping_); 101 | action_grp_warping_method_->addAction(action_use_rbf_warping_); 102 | connect(action_grp_warping_method_, SIGNAL(triggered(QAction*)), 103 | this, SLOT(ChangeWarpingMethod(QAction*))); 104 | 105 | action_use_rbf_warping_->setCheckable(true); 106 | action_use_rbf_warping_->setChecked(true); 107 | imagewidget_->SetWarpingMethod("RBF"); 108 | 109 | action_real_time_warping_mode_ = new QAction(tr("Realtime Warping"), this); 110 | action_real_time_warping_mode_->setStatusTip(tr("Realtime Warping")); 111 | action_real_time_warping_mode_->setCheckable(true); 112 | action_real_time_warping_mode_->setChecked(false); 113 | connect(action_real_time_warping_mode_, SIGNAL(triggered()), this, 114 | SLOT(ChangeRealtimeWarpingMode())); 115 | } 116 | 117 | void MainWindow::CreateMenus() { 118 | menu_file_ = menuBar()->addMenu(tr("&File")); 119 | menu_file_->setStatusTip(tr("File menu")); 120 | menu_file_->addAction(action_new_); 121 | menu_file_->addAction(action_open_); 122 | menu_file_->addAction(action_save_); 123 | menu_file_->addAction(action_saveas_); 124 | 125 | menu_edit_ = menuBar()->addMenu(tr("&Edit")); 126 | menu_edit_->setStatusTip(tr("Edit menu")); 127 | menu_edit_->addAction(action_invert_); 128 | menu_edit_->addAction(action_mirror_); 129 | menu_edit_->addAction(action_gray_); 130 | menu_edit_->addAction(action_restore_); 131 | 132 | menu_image_warping_ = menuBar()->addMenu(tr("Image Warping")); 133 | menu_image_warping_->setStatusTip(tr("Warp image")); 134 | menu_image_warping_->addAction(action_select_points_); 135 | menu_image_warping_->addAction(action_undo_select_); 136 | menu_image_warping_->addAction(action_do_warp_); 137 | menu_image_warping_->addAction(action_real_time_warping_mode_); 138 | submenu_warping_method_ = menu_image_warping_->addMenu(tr("Warping Method")); 139 | submenu_warping_method_->addAction(action_use_idw_warping_); 140 | submenu_warping_method_->addAction(action_use_rbf_warping_); 141 | } 142 | 143 | void MainWindow::CreateToolBars() { 144 | toolbar_file_ = addToolBar(tr("File")); 145 | toolbar_file_->addAction(action_new_); 146 | toolbar_file_->addAction(action_open_); 147 | toolbar_file_->addAction(action_save_); 148 | 149 | toolbar_edit_ = addToolBar(tr("Edit")); 150 | toolbar_edit_->addAction(action_invert_); 151 | toolbar_edit_->addAction(action_mirror_); 152 | toolbar_edit_->addAction(action_gray_); 153 | toolbar_edit_->addAction(action_restore_); 154 | 155 | toolbar_image_warping_ = addToolBar(tr("Image Warping")); 156 | toolbar_image_warping_->addAction(action_select_points_); 157 | toolbar_image_warping_->addAction(action_undo_select_); 158 | toolbar_image_warping_->addAction(action_do_warp_); 159 | toolbar_image_warping_->addAction(action_real_time_warping_mode_); 160 | toolbar_image_warping_->addSeparator(); 161 | toolbar_image_warping_->addAction(action_use_idw_warping_); 162 | toolbar_image_warping_->addAction(action_use_rbf_warping_); 163 | } 164 | 165 | void MainWindow::CreateStatusBar() { 166 | statusBar()->showMessage(tr("Ready")); 167 | } 168 | 169 | void MainWindow::ChangeSelectPointsMode() { 170 | if (action_select_points_) { 171 | bool is_checked = action_select_points_->isChecked(); 172 | imagewidget_->SetSelectMode(is_checked); 173 | } 174 | } 175 | 176 | void MainWindow::ChangeRealtimeWarpingMode() { 177 | if (action_real_time_warping_mode_) { 178 | bool is_checked = action_real_time_warping_mode_->isChecked(); 179 | imagewidget_->SetRealTimeWarpingMode(is_checked); 180 | } 181 | } 182 | 183 | void MainWindow::ChangeWarpingMethod(QAction *a) { 184 | a->setCheckable(true); 185 | a->setChecked(true); 186 | 187 | if (action_use_idw_warping_->isChecked()) { 188 | imagewidget_->SetWarpingMethod("IDW"); 189 | } else if (action_use_rbf_warping_->isChecked()) { 190 | imagewidget_->SetWarpingMethod("RBF"); 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /image_warping_gui/mainwindow.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Yilin Gui. All rights reserved. 2 | // 3 | // filename: mainwindow.h 4 | 5 | #ifndef MAINWINDOW_H_ 6 | #define MAINWINDOW_H_ 7 | 8 | #include 9 | #include "ui_mainwindow.h" 10 | 11 | QT_BEGIN_NAMESPACE 12 | class QAction; 13 | class QActionGroup; 14 | class QMenu; 15 | class ViewWidget; 16 | class QImage; 17 | class QPainter; 18 | class QRect; 19 | class ImageWidget; 20 | QT_END_NAMESPACE 21 | 22 | class MainWindow : public QMainWindow { 23 | Q_OBJECT 24 | 25 | public: 26 | MainWindow(QWidget *parent = 0); 27 | ~MainWindow(); 28 | 29 | protected: 30 | void closeEvent(QCloseEvent *e); 31 | void paintEvent(QPaintEvent *paintevent); 32 | 33 | private slots: 34 | void ChangeSelectPointsMode(); 35 | void ChangeRealtimeWarpingMode(); 36 | void ChangeWarpingMethod(QAction *a); 37 | 38 | private: 39 | void CreateActions(); 40 | void CreateMenus(); 41 | void CreateToolBars(); 42 | void CreateStatusBar(); 43 | 44 | private: 45 | Ui::MainWindowClass ui_; 46 | 47 | QMenu *menu_file_; 48 | QMenu *menu_edit_; 49 | QMenu *menu_help_; 50 | QMenu *menu_image_warping_; 51 | QMenu *submenu_warping_method_; 52 | 53 | QToolBar *toolbar_file_; 54 | QToolBar *toolbar_edit_; 55 | QToolBar *toolbar_image_warping_; 56 | 57 | QAction *action_new_; 58 | QAction *action_open_; 59 | QAction *action_save_; 60 | QAction *action_saveas_; 61 | 62 | QAction *action_invert_; 63 | QAction *action_mirror_; 64 | QAction *action_gray_; 65 | QAction *action_restore_; 66 | 67 | QAction *action_select_points_; 68 | QAction *action_undo_select_; 69 | QAction *action_do_warp_; 70 | 71 | QActionGroup *action_grp_warping_method_; 72 | QAction *action_use_idw_warping_; 73 | QAction *action_use_rbf_warping_; 74 | QAction *action_real_time_warping_mode_; 75 | 76 | ImageWidget *imagewidget_; 77 | }; 78 | 79 | #endif // MAINWINDOW_H_ 80 | -------------------------------------------------------------------------------- /image_warping_gui/mainwindow.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | resources/images/copy.png 4 | resources/images/cut.png 5 | resources/images/new.png 6 | resources/images/open.png 7 | resources/images/paste.png 8 | resources/images/save.png 9 | 10 | 11 | -------------------------------------------------------------------------------- /image_warping_gui/mainwindow.ui: -------------------------------------------------------------------------------- 1 | 2 | MainWindowClass 3 | 4 | 5 | MainWindowClass 6 | 7 | 8 | 9 | 0 10 | 0 11 | 600 12 | 400 13 | 14 | 15 | 16 | MainWindow 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /image_warping_gui/resources/images/copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insaneyilin/ImageWarping/2b574e36facb51800f7d82682b7b30d2134a4dcc/image_warping_gui/resources/images/copy.png -------------------------------------------------------------------------------- /image_warping_gui/resources/images/cut.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insaneyilin/ImageWarping/2b574e36facb51800f7d82682b7b30d2134a4dcc/image_warping_gui/resources/images/cut.png -------------------------------------------------------------------------------- /image_warping_gui/resources/images/new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insaneyilin/ImageWarping/2b574e36facb51800f7d82682b7b30d2134a4dcc/image_warping_gui/resources/images/new.png -------------------------------------------------------------------------------- /image_warping_gui/resources/images/open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insaneyilin/ImageWarping/2b574e36facb51800f7d82682b7b30d2134a4dcc/image_warping_gui/resources/images/open.png -------------------------------------------------------------------------------- /image_warping_gui/resources/images/paste.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insaneyilin/ImageWarping/2b574e36facb51800f7d82682b7b30d2134a4dcc/image_warping_gui/resources/images/paste.png -------------------------------------------------------------------------------- /image_warping_gui/resources/images/save.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insaneyilin/ImageWarping/2b574e36facb51800f7d82682b7b30d2134a4dcc/image_warping_gui/resources/images/save.png -------------------------------------------------------------------------------- /screenshots/screenshots_0_origin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insaneyilin/ImageWarping/2b574e36facb51800f7d82682b7b30d2134a4dcc/screenshots/screenshots_0_origin.png -------------------------------------------------------------------------------- /screenshots/screenshots_0_select_ctrl_points.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insaneyilin/ImageWarping/2b574e36facb51800f7d82682b7b30d2134a4dcc/screenshots/screenshots_0_select_ctrl_points.png -------------------------------------------------------------------------------- /screenshots/screenshots_0_warped.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insaneyilin/ImageWarping/2b574e36facb51800f7d82682b7b30d2134a4dcc/screenshots/screenshots_0_warped.png -------------------------------------------------------------------------------- /test_data/MonaLisa.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insaneyilin/ImageWarping/2b574e36facb51800f7d82682b7b30d2134a4dcc/test_data/MonaLisa.bmp -------------------------------------------------------------------------------- /test_data/warp_test.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insaneyilin/ImageWarping/2b574e36facb51800f7d82682b7b30d2134a4dcc/test_data/warp_test.bmp --------------------------------------------------------------------------------