Last active
January 19, 2018 21:51
-
-
Save thai-ng/19cc39704de5726799166469e586adf8 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <opencv2/core.hpp> | |
#include <opencv2/highgui.hpp> | |
#include <opencv2/imgproc.hpp> | |
#include <opencv2/opencv.hpp> | |
#include <array> | |
#include <string_view> | |
#include <utility> | |
using namespace std::string_view_literals; | |
// Wrapper for setMouseCallback to use capture lambda | |
template <typename F> | |
void setMouseAction(std::string_view windowName, F& func) | |
{ | |
cv::setMouseCallback(windowName.data(), | |
[](int e, int x, int y, int flags, void* data) | |
{ | |
(*static_cast<F*>(data))(e, x, y, flags); | |
}, | |
static_cast<void*>(&func)); | |
} | |
// Display image, take 2 clicks, close image | |
std::pair<cv::Point, cv::Point> get2Points(cv::Mat const& im) | |
{ | |
auto window = "Choose 2 points, left to right"sv; | |
cv::namedWindow(window.data()); | |
cv::imshow(window.data(), im); | |
std::array<cv::Point, 2> clicks; | |
int clickCount = 0; | |
auto closure = [&clicks, &clickCount, window](int e, int x, int y, int flags) | |
{ | |
if (e == cv::MouseEventTypes::EVENT_LBUTTONDOWN) | |
{ | |
clicks[clickCount] = cv::Point(x, y); | |
++clickCount; | |
if (clickCount == 2) | |
{ | |
// Close window to continue | |
cv::destroyWindow(window.data()); | |
} | |
} | |
}; | |
setMouseAction(window.data(), closure); | |
cv::waitKey(); | |
return { clicks[0], clicks[1] }; | |
} | |
inline | |
cv::Point meanPoint(cv::Point p1, cv::Point p2) | |
{ | |
auto meanX = (p1.x + p2.x) / 2.0; | |
auto meanY = (p1.y + p2.y) / 2.0; | |
return cv::Point(static_cast<int>(meanX), static_cast<int>(meanY)); | |
} | |
cv::Mat padImage(cv::Mat im, cv::Point centerPoint) | |
{ | |
auto toTranslateTop = (im.rows / 2) - centerPoint.y; | |
auto topPad = (toTranslateTop > 0) ? toTranslateTop * 2 : 0; | |
auto bottomPad = (toTranslateTop < 0) ? -toTranslateTop * 2 : 0; | |
auto toTranslateLeft = (im.cols / 2) - centerPoint.x; | |
auto leftPad = (toTranslateLeft > 0) ? toTranslateLeft * 2 : 0; | |
auto rightPad = (toTranslateLeft < 0) ? -toTranslateLeft * 2 : 0; | |
cv::copyMakeBorder(im, im, topPad, bottomPad, leftPad, rightPad, cv::BorderTypes::BORDER_CONSTANT, 0); | |
return im; | |
} | |
cv::Mat rotateImage(cv::Mat im, double angle) | |
{ | |
auto rotation = cv::getRotationMatrix2D(cv::Point(im.cols / 2, im.rows / 2), angle, 1); | |
cv::warpAffine(im, im, rotation, im.size()); | |
return im; | |
} | |
cv::Mat rotateAndPadImage(cv::Mat im, cv::Point p1, cv::Point p2) | |
{ | |
auto centerPoint = meanPoint(p1, p2); | |
im = padImage(im, centerPoint); | |
auto angle = std::atan2(p2.y - centerPoint.y, p2.x - centerPoint.x) * 180 / std::acos(-1.0); | |
im = rotateImage(im, angle); | |
return im; | |
} | |
cv::Mat openSingleGrayImage(std::string_view name) | |
{ | |
auto img = cv::imread(name.data(), cv::ImreadModes::IMREAD_UNCHANGED); | |
img.convertTo(img, CV_32F); | |
img *= 1 / 255.0; | |
if (img.channels() == 3) | |
{ | |
cv::cvtColor(img, img, CV_RGB2GRAY); | |
} | |
return img; | |
} | |
cv::Mat scaleImage(cv::Mat im, double scale) | |
{ | |
cv::resize(im, im, cv::Size(static_cast<int>(im.size().width * scale), static_cast<int>(im.size().height * scale))); | |
return im; | |
} | |
std::pair<cv::Mat, cv::Mat> align2Images(cv::Mat im1, cv::Mat im2) | |
{ | |
auto[im1p1, im1p2] = get2Points(im1); | |
im1 = rotateAndPadImage(im1, im1p1, im1p2); | |
auto[im2p1, im2p2] = get2Points(im2); | |
im2 = rotateAndPadImage(im2, im2p1, im2p2); | |
auto firstLength = std::hypot(im1p2.x - im1p1.x, im1p2.y - im1p1.y); | |
auto secondLength = std::hypot(im2p2.x - im2p1.x, im2p2.y - im2p1.y); | |
if (firstLength < secondLength) | |
{ | |
auto scale = firstLength / secondLength; | |
im2 = scaleImage(im2, scale); | |
} | |
else if (firstLength > secondLength) | |
{ | |
auto scale = secondLength/ firstLength; | |
im1 = scaleImage(im1, scale); | |
} | |
auto croppedWidth = std::min(im1.size().width, im2.size().width); | |
auto croppedHeight = std::min(im1.size().height, im2.size().height); | |
auto im1ROI = cv::Rect(im1.cols / 2 - (croppedWidth / 2), im1.rows / 2 - (croppedHeight / 2), croppedWidth, croppedHeight); | |
im1 = im1(im1ROI); | |
auto im2ROI = cv::Rect(im2.cols / 2 - (croppedWidth / 2), im2.rows / 2 - (croppedHeight / 2), croppedWidth, croppedHeight); | |
im2 = im2(im2ROI); | |
return { im1, im2 }; | |
} | |
int main() | |
{ | |
auto im1 = openSingleGrayImage("DerekPicture.jpg"); | |
auto im2 = openSingleGrayImage("nutmeg.jpg"); | |
auto [i1, i2] = align2Images(im1, im2); | |
cv::namedWindow("Image1"); | |
cv::imshow("Image1", i1); | |
cv::namedWindow("Image2"); | |
cv::imshow("Image2", i2); | |
cv::waitKey(); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment