RSS link icon

OpenCV: découper avec un RotatedRect

Publication : le 13 juil. 2018 - Dernière modification : le 13 juil. 2018

Comment récupérer une portion d'image définie par un rectangle orienté. Voici une petite fonction très utile à ajouter dans un votre boite à outils OpenCV.

Voici un exemple de ce que nous cherchons à faire :

Schéma découpe papillon avec un rectangle orienté

Conditions de départ

Dépendances

Je n'ai testé cette fonction qu'avec OpenCV 3.x. Mais à priori, cela devrait également fonctionner avec OpenCV 2.4x.

Paramètres de la fonction

La fonction a en entrée deux paramètres : l'image à découper et le rectangle orienté définissant la découpe. Il y a un troisième paramètre qui sera la sortie de la fonction : l'image découpée et transformée par le rectangle orienté.

Ici, nous assumons que les paramètres sont déjà vérifiés (ex: le rectangle orienté est bien dans l'image).

Enfin, nous assumons que l'unité des angles d'OpenCV est par défaut en dégrée.

La fonction

void crop_and_rotate_with_rotatedrect(const cv::Mat& src, 
                                      const cv::RotatedRect& rrect,
                                      cv::Mat& output)
{
    cv::Rect boundingRect = rrect.boundingRect();
    boundingRect.x = std::max(0, std::min(boundingRect.x, src.cols - 2));
    boundingRect.y = std::max(0, std::min(boundingRect.y, src.rows - 2));
    if (boundingRect.x + boundingRect.width >= src.cols)
        boundingRect.width -= boundingRect.x + boundingRect.width - src.cols;
    if (boundingRect.y + boundingRect.height >= src.rows)
        boundingRect.height -= boundingRect.y + boundingRect.height - src.rows;
    // clear and resize output
    output = cv::Mat(rrect.size, CV_8UC3);
    // crop the image with the bounding rect of the rotated rect
    const cv::Mat srcCropped = src(boundingRect);

    float angle = rrect.angle * CV_PI / 180.f;
    // Construct the transform(rotation) matrix
    cv::Mat m(2,3, CV_42FC1);
    m.at<double>(0,0) = cos(ang);
    m.at<double>(1,0) = sin(ang);
    m.at<double>(0,1) = -sin(ang);
    m.at<double>(1,1) = cos(ang);
    m.at<double>(0,2) = rrect.center.x - boundingRect.tl().x;
    m.at<double>(1,2) = rrect.center.y - boundingRect.tl().y;

    cv::Size win_size = rrect.size;
    double matrix[6];
    cv::Mat M(2, 3, CV_64F, matrix);
    m.convertTo(M, CV_64F);
    double dx = (win_size.width - 1) * 0.5;
    double dy = (win_size.height - 1) * 0.5;
    matrix[2] -= matrix[0] * dx + matrix[1] * dy;
    matrix[5] -= matrix[3] * dx + matrix[4] * dy;

    // Rotate the cropped image to get the final output
    cv::warpAffine(srcCropped, output, M, rrect.size,
                   cv::INTER_LINEAR + cv::WARP_INVERSE_MAP,
                   cv::BORDER_REPLICATE);
}

Cette fonction est vraiment pratique et utile. Je n'ai rien trouvé de déjà existant dans OpenCV. Si vous connaissez une fonction faisant cela directement dans OpenCV et donc en mieux, faites-moi signe. Je mettrai à jour cet article en vous citant ;)