Related
Let's say I have a QPixmap that has the dimensions of (20 x 100). How can I create a copy of this QPixmap that's rotated a specific amount and also has new dimensions to allocate the new dimensions of the rotated pixmap?
I've found multiple examples on how to rotate using QPainter and QTransform, but none seem to provide a proper manner to keep the QPixmap from cutting off.
The best example I've found so far is:
// original = Original QPixmap
QSize size = original.size();
QPixmap newPixmap(size);
newPixmap.fill(QColor::fromRgb(0, 0, 0, 0));
QPainter p(&newPixmap);
p.translate(size.height() / 2, size.height() / 2);
p.rotate(35); // Any rotation, for this example 35 degrees
p.translate(size.height() / -2, size.height() / -2);
p.drawPixmap(0, 0, original);
p.end();
This rotates a QPixmap, and places it on a new QPixmap of the same dimensions. However, I am at a loss on how to modify this to work with new dimensions.
I've even tried simply modifying the initial size of the new pixmap, but that just causes the image to be off center (and still cut off for some reason?)
Any support would be appreciated!
One way to do this would be to calculate the minimum bounding rect for your rotated image and to create a new pixmap with these dimensions onto which you can render your rotated image which is now guarenteed to fit. To do this you could take each corner point of your image rectangle and rotate them around the center. The resulting points can then be used to calculate your minimum bounding rectangle by looking at each point and finding both the minimum and maximum x and y values.
For example in the following hypothetical example we have a 100x100 rectangle. If we use a simple algorithm to rotate each corner point of the rectangle around the center by our angle (in this case 45 degrees) we get the four new corner points (50, -20), (-20, 50), (120, 120) and (50, 120). From these points we can see the minimum x value is -20, the minimum y value is -20, the maximum x value is 120 and the maximum y value is 120, so the minimum bounding rect can be described by topLeft:(-20, -20) and bottomRight:(120, 120).
To help you with this here is a function taken from another stackoverflow post for rotating a point around another point:
QPointF getRotatedPoint( QPointF p, QPointF center, qreal angleRads )
{
qreal x = p.x();
qreal y = p.y();
float s = qSin( angleRads );
float c = qCos( angleRads );
// translate point back to origin:
x -= center.x();
y -= center.y();
// rotate point
float xnew = x * c - y * s;
float ynew = x * s + y * c;
// translate point back:
x = xnew + center.x();
y = ynew + center.y();
return QPointF( x, y );
}
And here is a function I wrote that uses it to calculate the minimum bounding rect for some rectangle rotated by some angle...
QRectF getMinimumBoundingRect( QRect r, qreal angleRads )
{
QPointF topLeft = getRotatedPoint( r.topLeft(), r.center(), angleRads );
QPointF bottomRight = getRotatedPoint( r.bottomRight(), r.center(), angleRads );
QPointF topRight = getRotatedPoint( r.topRight(), r.center(), angleRads );
QPointF bottomLeft = getRotatedPoint( r.bottomLeft(), r.center(), angleRads );
// getMin and getMax just return the min / max of their arguments
qreal minX = getMin( topLeft.x(), bottomRight.x(), topRight.x(), bottomLeft.x() );
qreal minY = getMin( topLeft.y(), bottomRight.y(), topRight.y(), bottomLeft.y() );
qreal maxX = getMax( topLeft.x(), bottomRight.x(), topRight.x(), bottomLeft.x() );
qreal maxY = getMax( topLeft.y(), bottomRight.y(), topRight.y(), bottomLeft.y() );
return QRectF( QPointF( minX, minY ), QPointF( maxX, maxY ) );
}
So now we have the minimum bounding rectangle for our rotated image we can create a new pixmap with its width and height and render our rotated image to it at the center. This is tricky because of the transformation involved which makes it a bit more confusing as to what your source and target rects might be. It's actually not as hard as it might seem. You perform your translation / rotation to rotate the paint device around the center, then you can simply render your source image onto your destination image exactly as you would if you were rendering the source to the center of the destination.
For example:
QPixmap originalPixmap; // Load this from somewhere
QRectF minimumBoundingRect = getMinimumBoundingRect( originalPixmap.rect(), angleRads);
QPixmap rotatedPixmap( minimumBoundingRect.width(), minimumBoundingRect.height() );
QPainter p( &rotatedPixmap );
p.save();
// Rotate the rotated pixmap paint device around the center...
p.translate( 0.5 * rotatedPixmap.width(), 0.5 * rotatedPixmap.height() );
p.rotate( angleDegrees );
p.translate( -0.5 * rotatedPixmap.width(), -0.5 * rotatedPixmap.height() );
// The render rectangle is simply the originalPixmap rectangle as it would be if placed at the center of the rotatedPixmap rectangle...
QRectF renderRect( 0.5 * rotatedRect.width() - 0.5 * originalPixmap.width(),
0.5 * rotatedRect.height() - 0.5 * originalPixmap.height(),
originalPixmap.width(),
originalPixmap.height() );
p.drawPixmap( renderRect, originalPixmap, originalPixmap.rect() );
p.restore();
And voila, a nicely rotated image with no corners chopped off.
I'm trying to get orientation image of a fingerprint using the method proposed in this paper.
I tried implementing the steps described in Section 3.1.1 of the paper, but I don't get the desired result.
Here are my OpenCV code:
Mat calculate_orientation(Mat img, Mat &coherence) {
Mat image = img.clone();
Mat orient_im = Mat::zeros(image.size(), image.type());
Mat grad_x, grad_y;
Sobel(image, grad_x, CV_32F, 1, 0, 3, 1, 0, BORDER_DEFAULT );
Sobel(image, grad_y, CV_32F, 0, 1, 3, 1, 0, BORDER_DEFAULT );
//Iterate per BLOCKSIZE and use BLOCKSIZE/2 as the center
for (int i=BLOCKSIZE/2 ; i<=image.rows-BLOCKSIZE/2 ; i+=BLOCKSIZE) {
for (int j=BLOCKSIZE/2 ; j<=image.cols-BLOCKSIZE/2 ; j+=BLOCKSIZE) {
//Iterate each pixel in the block
float vx = 0.0f, vy = 0.0f, angle;
//Coherence
float gx = 0.0f, gy = 0.0f, gxy = 0.0f;
for (int u=i-BLOCKSIZE/2 ; u<i+BLOCKSIZE/2 ; u++) {
for (int v=j-BLOCKSIZE/2 ; v<j+BLOCKSIZE/2 ; v++) {
gx = 2* grad_x.at<float>(u,v) * grad_y.at<float>(u,v);
gy = pow(grad_x.at<float>(u,v), 2) - pow(grad_y.at<float>(u,v), 2);
vx += gx;
vy += gy;
gxy += sqrt(pow(gx,2)+pow(gy,2));
}
}
if (vy == 0) {
angle = 90;
} else {
angle = 0.5 * atan(vx/vy) * 180.0f/CV_PI;
}
//The angle above is the angle perpendicular to ridge direction
orient_im.at<float>(i,j) = angle + 90;
//Coherence
float coh = sqrt(pow(vx,2)+pow(vy,2))/gxy;
coherence.at<float>(i,j) = coh;
}
}
return orient_im;
}
This is the input image.
And this is the result. The blue lines are orientation with coherence value of more than 0.5, and the red lines are orientation with coherence value of less than 0.5.
Only around half of the orientation seems right.
I know there are already a few questions about this, but I still haven't gotten the correct results, so pardon me for asking. Any help would be appreciated.
I want fill circle with gradient color, like I show on bottom. I can't figure out easy way, how to do that.
I can make more circles, but transitions are visible.
cv::circle(img, center, circle_radius * 1.5, cv::Scalar(1.0, 1.0, 0.3), CV_FILLED);
cv::circle(img, center, circle_radius * 1.2, cv::Scalar(1.0, 1.0, 0.6), CV_FILLED);
cv::circle(img, center, circle_radius, cv::Scalar(1.0, 1.0, 1.0), CV_FILLED);
All you need to do is create a function which takes in a central point and a new point, calculates the distance, and returns a grayscale value for that point. Alternatively you could just return the distance, store the distance at that point, and then scale the whole thing later with cv::normalize().
So let's say you have the central point as (50, 50) in a (100, 100) image. Here's pseudocode for what you'd want to do:
function euclideanDistance(center, point) # returns a float
return sqrt( (center.x - point.x)^2 + (center.y - point.y)^2 )
center = (50, 50)
rows = 100
cols = 100
gradient = new Mat(rows, cols) # should be of type float
for row < rows:
for col < cols:
point = (col, row)
gradient[row, col] = euclideanDistance(center, point)
normalize(gradient, 0, 255, NORM_MINMAX, uint8)
gradient = 255 - gradient
Note the steps here:
Create the Euclidean distance function to calculate distance
Create a floating point matrix to hold the distance values
Loop through all rows and columns and assign a distance value
Normalize to the range you want (you could stick with a float here instead of casting to uint8, but you do you)
Flip the binary gradient, since distances farther away will be brighter---but you want the opposite.
Now for your exact example image, there's a gradient in a circle, whereas this method just creates the whole image as a gradient. In your case, if you want a specific radius, just modify the function which calculates the Euclidean distance, and if it's beyond some distance, set it to 0 (the value at the center of the circle, which will be flipped eventually to white):
function euclideanDistance(center, point, radius) # returns a float
distance = sqrt( (center.x - point.x)^2 + (center.y - point.y)^2 )
if distance > radius:
return 0
else
return distance
Here is the above in actual C++ code:
#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <cmath>
float euclidean_distance(cv::Point center, cv::Point point, int radius){
float distance = std::sqrt(
std::pow(center.x - point.x, 2) + std::pow(center.y - point.y, 2));
if (distance > radius) return 0;
return distance;
}
int main(){
int h = 400;
int w = 400;
int radius = 100;
cv::Mat gradient = cv::Mat::zeros(h, w, CV_32F);
cv::Point center(150, 200);
cv::Point point;
for(int row=0; row<h; ++row){
for(int col=0; col<w; ++col){
point.x = col;
point.y = row;
gradient.at<float>(row, col) = euclidean_distance(center, point, radius);
}
}
cv::normalize(gradient, gradient, 0, 255, cv::NORM_MINMAX, CV_8U);
cv::bitwise_not(gradient, gradient);
cv::imshow("gradient", gradient);
cv::waitKey();
}
A completely different method (though doing the same thing) would be to use the distanceTransform(). This function maps the distance from the center of a white blob to the nearest black value to a grayscale value, like we were doing above. This code is more concise and does the same thing. However, it can work on arbitrary shapes, not just circles, so that's cool.
#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>
int main(){
int h = 400;
int w = 400;
int radius = 100;
cv::Point center(150, 200);
cv::Mat gradient = cv::Mat::zeros(h, w, CV_8U);
cv::rectangle(gradient, cv::Point(115, 100), cv::Point(270, 350), cv::Scalar(255), -1, 8 );
cv::Mat gradient_padding;
cv::bitwise_not(gradient, gradient_padding);
cv::distanceTransform(gradient, gradient, CV_DIST_L2, CV_DIST_MASK_PRECISE);
cv::normalize(gradient, gradient, 0, 255, cv::NORM_MINMAX, CV_8U);
cv::bitwise_or(gradient, gradient_padding, gradient);
cv::imshow("gradient-distxform.png", gradient);
cv::waitKey();
}
You have to draw many circles. Color of each circle depends on distance from center. Here is some simple example:
void printGradient(cv::Mat &_input,const cv::Point &_center, const double radius)
{
cv::circle(_input, _center, radius, cv::Scalar(0, 0, 0), -1);
for(double i=1; i<radius; i=i++)
{
const int color = 255-int(i/radius * 255); //or some another color calculation
cv::circle(_input,_center,i,cv::Scalar(color, color, color),2);
}
}
And result:
Another approach not mentioned yet is to precompute a circle gradient image (with one of the mentioned approaches like the accepted solution) and use affine warping with linear interpolation to create other such circles (different sizes). This can be faster, if warping and interpolation are optimized and maybe accelerated by hardware.
Result might be a bit worse than perfect.
I once used this to create a single individual vignetting mask circle for each frame innendoscopic imaging. Was faster than to compute the distances "manually".
I would like to populate an ellipse shape in OpenCV in such a way that the value it takes is the normalized distance from its center.
Typically in OpenCV, I can fill an image with an elliptical shape as follows:
cv::ellipse(image, e, cv::Scalar(255), CV_FILLED);
However, this gives the ellipse a constant scalar value of 1 and I would like to vary this value based on its distance from the center.
I guess one way would be to go through the points and manually compute this. I am quite new to OpenCV and having trouble doing this with this Mat object.
Here is the sample code snippet to Find distance Transform of a Ellipse.
You can simply create a mask of that Ellipse region & Find Distance transform for that mask.
Mat mEllipse_Bgr(Size(640,480),CV_8UC3,Scalar(0));
Mat mEllipseMask(mEllipse_Bgr.size(),CV_8UC1,Scalar(0));
// Draw a ellipse
ellipse( mEllipse_Bgr, Point( 200, 200 ), Size( 100.0, 160.0 ), 45, 0, 360, Scalar( 255, 0, 0 ), 1, 8 );
ellipse( mEllipseMask, Point( 200, 200 ), Size( 100.0, 160.0 ), 45, 0, 360, Scalar( 255), -1, 8 );
imshow("Ellipse Image",mEllipse_Bgr);
imshow("Ellipse Mask",mEllipseMask);
// Perform the distance transform algorithm
Mat mDist;
distanceTransform(mEllipseMask, mDist, CV_DIST_L2, 3);
// Normalize the distance Transform image for range = {0.0, 1.0} to view it
normalize(mDist, mDist, 0, 1., NORM_MINMAX);
imshow("Distance Transform Image", mDist);
assume that I have a detected circle with coordinate of (center.x and center.y) detected by using this circle function:
GaussianBlur( dis, dis, Size(3, 3), 2, 2 );
vector<Vec3f> circles;
HoughCircles( dis, circles, CV_HOUGH_GRADIENT, 1, dis.rows/8, 200, 100);
for( size_t i = 0; i < circles.size(); i++ ){
Point center(cvRound(circles[i][0]), cvRound(circles[i][1]));
cout << "center" << center.x << ", " << center.y << endl;
// coordinates of center points
V.push_back(std::make_pair(center.x,center.y));
int radius = cvRound(circles[i][2]);
// circle center
circle( dis, center, 3, 1, -1, 8, 0 );
// circle outline
circle( dis, center, radius, 1, 3, 8, 0 );
}
how do I draw a rectangle around this circle which the center of the circle locates in middle of the rectangle and the distance between the center and each side is "radius + x" ?
I am completely new in image processing, sorry for the simple question.
I would appreciate any help..
...............Edit the code..................
cv::rectangle( diatence, cvPoint((center.x)-(radius+10),(center.y)-(radius+10)), cvPoint((center.x)+(radius+10),(center.y)+(radius+10)), 1, 1, 8 );
assuming the centre is at x,y you need to draw a rectangle with the following specifications:
top left corner : x-(radius+a),y-(radius+a)
bottom right corner : x+(radius+a),y+(radius+a)
where a is an arbitrary value that you want to add to the radius.
More generally:
given a centre point x,y and a known size LxH of a rectangle, you can draw the rectangle by specifiying the top-left point as x-(L/2),y-(H/2) and the bottom-right point as x+(L/2),y+(H/2)