J'essaie de faire pivoter une image 1296x968 par 90 degrés en utilisant l'API C++ d'OpenCV et je suis confronté à quelques problèmes.
Entrée :
Rotation :
Comme vous pouvez le voir, l'image pivotée a quelques problèmes. Tout d'abord, il a la même taille que l'original, même si je crée spécifiquement la destination Mat
avec la taille inversée de l'original. Par conséquent, l'image de destination est rognée.
Je soupçonne que cela se produit car j'appelle warpAffine()
et je passe la taille de l'original Mat
au lieu de la taille de la destination Mat
. Mais je fais cela parce que j'ai suivi cette réponse, mais maintenant je soupçonne que la réponse peut être fausse. C'est donc mon premier doute/problème.
La seconde est que warpAffine()
écrit vers la destination à un certain décalage (probablement pour copier les données pivotées au milieu de l'image) et cette opération laisse une horrible et grande bordure noire autour de l'image.
Comment résoudre ces problèmes?
Je partage le code source ci-dessous:
#include <cv.h>
#include <highgui.h>
#include <iostream>
using namespace cv;
using namespace std;
void rotate(Mat& image, double angle)
{
Point2f src_center(image.cols/2.0F, image.rows/2.0F);
Mat rot_matrix = getRotationMatrix2D(src_center, angle, 1.0);
Mat rotated_img(Size(image.size().height, image.size().width), image.type());
warpAffine(image, rotated_img, rot_matrix, image.size());
imwrite("rotated.jpg", rotated_img);
}
int main(int argc, char* argv[])
{
Mat orig_image = imread(argv[1], 1);
if (orig_image.empty())
{
cout << "!!! Couldn't load " << argv[1] << endl;
return -1;
}
rotate(orig_image, 90);
return 0;
}
J'ai trouvé une solution qui n'implique pas warpAffine()
.
Mais avant cela, je dois déclarer (pour les références futures) que mes soupçons étaient exacts, vous devez transmettre la taille de la destination lors de l'appel warpAffine()
:
warpAffine(image, rotated_img, rot_matrix, rotated_img.size());
Pour autant que je sache, la bordure noire (causée par l'écriture avec un décalage) dessinée par cette fonction semble être son comportement standard. J'ai remarqué cela avec l'interface C et aussi avec l'interface C++ d'OpenCV fonctionnant sur Mac et Linux, en utilisant les versions 2.3.1a et 2.3.0.
La solution que j'ai fini par utiliser est beaucoup plus simple que tout cela chose warp. Vous pouvez utiliser cv::transpose()
et cv::flip()
pour faire pivoter une image de 90 degrés. C'est ici:
Mat src = imread(argv[1], 1);
cv::Mat dst;
cv::transpose(src, dst);
cv::flip(dst, dst, 1);
imwrite("rotated90.jpg", dst);
---- I>
Beaucoup de gens ont eu des problèmes avec la rotation des images ou des morceaux d'images en raison de décalages, etc. calculer une image où tout ira parfaitement.
// ROTATE p by R
/**
* Rotate p according to rotation matrix (from getRotationMatrix2D()) R
* @param R Rotation matrix from getRotationMatrix2D()
* @param p Point2f to rotate
* @return Returns rotated coordinates in a Point2f
*/
Point2f rotPoint(const Mat &R, const Point2f &p)
{
Point2f rp;
rp.x = (float)(R.at<double>(0,0)*p.x + R.at<double>(0,1)*p.y + R.at<double>(0,2));
rp.y = (float)(R.at<double>(1,0)*p.x + R.at<double>(1,1)*p.y + R.at<double>(1,2));
return rp;
}
//COMPUTE THE SIZE NEEDED TO LOSSLESSLY STORE A ROTATED IMAGE
/**
* Return the size needed to contain bounding box bb when rotated by R
* @param R Rotation matrix from getRotationMatrix2D()
* @param bb bounding box rectangle to be rotated by R
* @return Size of image(width,height) that will compleley contain bb when rotated by R
*/
Size rotatedImageBB(const Mat &R, const Rect &bb)
{
//Rotate the rectangle coordinates
vector<Point2f> rp;
rp.Push_back(rotPoint(R,Point2f(bb.x,bb.y)));
rp.Push_back(rotPoint(R,Point2f(bb.x + bb.width,bb.y)));
rp.Push_back(rotPoint(R,Point2f(bb.x + bb.width,bb.y+bb.height)));
rp.Push_back(rotPoint(R,Point2f(bb.x,bb.y+bb.height)));
//Find float bounding box r
float x = rp[0].x;
float y = rp[0].y;
float left = x, right = x, up = y, down = y;
for(int i = 1; i<4; ++i)
{
x = rp[i].x;
y = rp[i].y;
if(left > x) left = x;
if(right < x) right = x;
if(up > y) up = y;
if(down < y) down = y;
}
int w = (int)(right - left + 0.5);
int h = (int)(down - up + 0.5);
return Size(w,h);
}
/**
* Rotate region "fromroi" in image "fromI" a total of "angle" degrees and put it in "toI" if toI exists.
* If toI doesn't exist, create it such that it will hold the entire rotated region. Return toI, rotated imge
* This will put the rotated fromroi piece of fromI into the toI image
*
* @param fromI Input image to be rotated
* @param toI Output image if provided, (else if &toI = 0, it will create a Mat fill it with the rotated image roi, and return it).
* @param fromroi roi region in fromI to be rotated.
* @param angle Angle in degrees to rotate
* @return Rotated image (you can ignore if you passed in toI
*/
Mat rotateImage(const Mat &fromI, Mat *toI, const Rect &fromroi, double angle)
{
//CHECK STUFF
// you should protect against bad parameters here ... omitted ...
//MAKE OR GET THE "toI" MATRIX
Point2f cx((float)fromroi.x + (float)fromroi.width/2.0,fromroi.y +
(float)fromroi.height/2.0);
Mat R = getRotationMatrix2D(cx,angle,1);
Mat rotI;
if(toI)
rotI = *toI;
else
{
Size rs = rotatedImageBB(R, fromroi);
rotI.create(rs,fromI.type());
}
//ADJUST FOR SHIFTS
double wdiff = (double)((cx.x - rotI.cols/2.0));
double hdiff = (double)((cx.y - rotI.rows/2.0));
R.at<double>(0,2) -= wdiff; //Adjust the rotation point to the middle of the dst image
R.at<double>(1,2) -= hdiff;
//ROTATE
warpAffine(fromI, rotI, R, rotI.size(), INTER_CUBIC, BORDER_CONSTANT, Scalar::all(0));
//& OUT
return(rotI);
}
Je me rends compte que vous avez trouvé d'autres solutions plus rapides (la rotation à 90 degrés devrait être très rapide, et n'a pas besoin de toutes les machines de warpAffine), mais je veux aborder le problème de la bordure noire pour tous ceux qui traversent cela.
Que pourrait faire warpAffine? L'image de destination a été spécifiée pour être plus large que haute, et la transformation affine n'a spécifié qu'une rotation (autour du centre de l'image), pas une mise à l'échelle. C'est exactement ce qu'il a fait. Il n'y a aucune information nulle part pour dire à warpAffine ce qui devrait être dessiné dans ces bordures noires, donc il les a laissées noires.
Expérience physique directe: posez une feuille sur la table. Dessinez un contour autour (c'est ce que vous avez fait lorsque vous avez spécifié que vous vouliez que le résultat soit de la même forme/taille que l'original). Faites maintenant pivoter cette feuille de 90 degrés autour de son centre. Regardez la région délimitée par le contour sur la table. S'il s'agissait d'un tableau noir, il ressemblerait exactement à votre résultat.
Peut-être que cela peut aider quelqu'un.
variables sont
img: image originale
angle: degrés
échelle
dst: image de destination
int width = img.size().width,
height = img.size().height;
Mat rot = getRotationMatrix2D(Point2f(0,0), angle, scale)/scale; //scale later
double sinv = rot.at<double>(0,1),
cosv = rot.at<double>(0,0);
rot.at<double>(1,2) = width*sinv; //adjust row offset
Size dstSize(width*cosv + height*sinv, width*sinv + height*cosv);
Mat dst;
warpAffine(img, dst, rot, dstSize);
resize(dst, dst, Size(), scale, scale); //scale now
Un problème que j'ai trouvé, c'est que la taille de l'image de destination pour warpAffine
est définie sur image.size()
au lieu de rotated_img.size()
. Cependant, après la déformation, il est toujours traduit trop loin en x et y ... J'ai essayé exactement la même déformation
[ 6.123031769111886e-17 1 163.9999999999999;
-1 6.123031769111886e-17 1132;
0 0 1]
de getRotationMatrix2D d'OpenCV dans Matlab, et cela a fonctionné parfaitement. Je commence à sentir un bogue possible avec warpAffine
...