Search code examples
qttransformtranslationcropsubpixel

How to translate/crop a QImage with subpixel accuracy?


The use case is a 2D map with a vehicle at the origin. The map shall also be translated in case the vehicle moves e.g. 0.5 pixels. I believe this should be feasible using bilinear interpolation or similar.

If there is no simple solution using Qt, I would appreciate hints to non-Qt-solutions.

Minimal example:

#include <QtWidgets/QApplication>
#include <QtGui/QImage>
#include <QLabel>

int main(int argc, char *argv[])
{

    QApplication app(argc, argv);

    // Parameters
    QString PATH_IMG_IN = "../img_test_rect.jpg";
    QString PATH_IMG_OUT = "../img_out.png";
    float TRANSLATE_IN_PX = 0.5;

    // load image
    QImage img;
    img.load(PATH_IMG_IN);

    // rotate image.
    QTransform trans;
    trans.translate(0,TRANSLATE_IN_PX);
    QImage img_new = img.transformed(trans, Qt::SmoothTransformation);

    // save image
    img_new.save(PATH_IMG_OUT, nullptr, 100);

    // optional: Get info about true transformation matrix
    QTransform trans_true = QImage::trueMatrix(trans, img.width(), img.height());

    return app.exec();
}

Given an input image with a sharp border (see below), I would expect the output image to have a blurred border. This is not the case:

enter image description here

How to fix that?


Solution

  • I tested openCV and its function cv::warpAffine allows translation with sub-pixel precision (see MWE below).

    After founding some old, unanswered threads on qtcentre.org, it seems to me that Qt simply does not allow translation with sub-pixel precision. Please correct me if I am wrong.

    For Qt, I only found workarounds to scale the image first, translate with pixel accuracy and scale down again. Unfortunately, this approach is too computationally expensive for my use case.

    MWE with opencv:

    #include "opencv2/opencv.hpp"
    #include "opencv2/highgui/highgui.hpp"
    
    int main(int argc, char** argv) {
    
    // parameters
    std::string PATH_IMG_IN = "../img_test_rect.jpg";
    std::string PATH_IMG_OUT = "../img_out.jpg";
    
    // load image
    cv::Mat img = cv::imread(PATH_IMG_IN, CV_LOAD_IMAGE_GRAYSCALE);
    if (!img.data)                              // Check for invalid input
    {
        std::cout << "Could not open or find the image" << std::endl;
        return -1;
    }
    
    // rotate image
    cv::Mat img_new = cv::Mat::ones(img.size(), img.type()) * 0.5; // another type = CV_8U
    cv::Mat mat_transform = (cv::Mat_<float>(2, 3) << 1, 0, 0.5, 0, 1, 0);
    cv::warpAffine(img, img_new, mat_transform, img_new.size());
    
    // show image
    cv::imshow("Display window", img_new);
    
    // save image
    cv::imwrite(PATH_IMG_OUT, img_new);
    
    // wait for the user to press any key:
    cv::waitKey(0);
    
    return 0;
    }