I am attempting to determine the image gradient direction using the results from openCV's Sobel method.
I understand this should be a very simple task. I have copied the methods from a number of resources and answers from here but whatever I do the resultant directions are always between 0 - 57 degrees (I would expect the range to be from 0-360).
I believe all the depths are correct. I have tried calculating the direction using the 16S data as well as 8U data.
I just can't see where I'm going wrong? Can anyone spot my mistake?
void getGradients(IplImage* original, cv::Mat* gradArray)
{
cv::Mat original_Mat(original, true);
// Convert it to gray
cv::cvtColor( original_Mat, original_Mat, CV_RGB2GRAY );
//cv::blur(original_Mat, original_Mat, cv::Size(7,7));
/// Generate grad_x and grad_y
cv::Mat grad_x = cv::Mat::zeros(original->height, original->width, CV_16S);
cv::Mat grad_y = cv::Mat::zeros(original->height, original->width, CV_16S);
cv::Mat abs_grad_x = cv::Mat::zeros(original->height, original->width, CV_8U);
cv::Mat abs_grad_y = cv::Mat::zeros(original->height, original->width, CV_8U);;
/// Gradient X
cv::Sobel(original_Mat, grad_x, CV_16S, 1, 0, 3);
cv::convertScaleAbs( grad_x, abs_grad_x );
/// Gradient Y
cv::Sobel(original_Mat, grad_y, CV_16S, 0, 1, 3);
cv::convertScaleAbs( grad_y, abs_grad_y );
uchar* pixelX = abs_grad_x.data;
uchar* pixelY = abs_grad_y.data;
uchar* grad1 = gradArray[0].data;
uchar* grad2 = gradArray[1].data;
uchar* grad3 = gradArray[2].data;
uchar* grad4 = gradArray[3].data;
uchar* grad5 = gradArray[4].data;
uchar* grad6 = gradArray[5].data;
uchar* grad7 = gradArray[6].data;
uchar* grad8 = gradArray[7].data;
int count = 0;
int min = 999999;
int max = 0;
for(int i = 0; i < grad_x.rows * grad_x.cols; i++)
{
int directionRAD = atan2(pixelY[i], pixelX[i]);
int directionDEG = directionRAD / PI * 180;
if(directionDEG < min){min = directionDEG;}
if(directionDEG > max){max = directionDEG;}
if(directionDEG >= 0 && directionDEG <= 45) { grad1[i] = 255; count++;}
if(directionDEG >= 45 && directionDEG <= 90) { grad2[i] = 255; count++;}
if(directionDEG >= 90 && directionDEG <= 135) { grad3[i] = 255; count++;}
if(directionDEG >= 135 && directionDEG <= 190) { grad4[i] = 255; count++;}
if(directionDEG >= 190 && directionDEG <= 225) { grad5[i] = 255; count++;}
if(directionDEG >= 225 && directionDEG <= 270) { grad6[i] = 255; count++;}
if(directionDEG >= 270 && directionDEG <= 315) { grad7[i] = 255; count++;}
if(directionDEG >= 315 && directionDEG <= 360) { grad8[i] = 255; count++;}
if(directionDEG < 0 || directionDEG > 360)
{
cout<<"Weird gradient direction given in method: getGradients.";
}
}
}
You're using integer arithmetic so your calculations for radians and degrees are suffering badly from truncation.
Also atan2 gives a result in the range -PI to +PI, so if you want a value in degrees in the range 0..360 you'll need to add a 180 degree correction:
double directionRAD = atan2(pixelY[i], pixelX[i]);
int directionDEG = (int)(180.0 + directionRAD / M_PI * 180.0);
Note the use of double rather than int for directionRAD.
Pro tip: learn to use a debugger to step through you code, inspecting variables as you go - that will make fixing simple bugs like this a lot easier than waiting for responses on StackOverflow.
You can get the x-derivative dx and y-derivative dy using Sobel operator. Then you can use the formula to calculate the magnitude and direction of the gradient. G=sqrt(dx^2+dy^2), theta=arctan(dy/dx). You can find this is just convert descartes coordinate system(x,y) to polar coordinates(rho, theta)!
There is something wrong in your code that you make absolute value of dx and dy, which makes the direction always in the first quadrant of the Cartesian coordinate system. And the function you used convertScaleAbs converts the result to 8-bit, which results in the truncation error.
I have a demo to calculate the magnitude partly based on your code.
const string imgname = "F:/OpenCV/square.jpg";
Mat img = imread(imgname, CV_LOAD_IMAGE_COLOR);
// 1. convert it to gray value
Mat gray;
cvtColor(img, gray, CV_BGR2GRAY);
// 2. blur the image
blur(gray, gray, Size(7, 7));
// 3. sobel
Mat grad_x, grad_y;
Scharr(gray, grad_x, CV_32FC1, 1, 0);
Scharr(gray, grad_y, CV_32FC1, 0, 1);
// 4. calculate gradient magnitude and direction
Mat magnitude, direction;
bool useDegree = true; // use degree or rad
// the range of the direction is [0,2pi) or [0, 360)
cartToPolar(grad_x, grad_y, magnitude, direction, useDegree);
// test, the histogram of the directions
vector<int> cnt(8, 0); // 0-45, 45-90, ..., 315-360
for(auto iter = direction.begin<float>(); iter != direction.end<float>(); ++iter)
{
int idx = static_cast<int>(*iter) / 45;
++cnt[idx];
}
Mat scaled;
convertScaleAbs(magnitude, scaled);
imshow("magnitude", scaled);
for(auto v : cnt)
cout << v << " ";
You take and absolute value of the gradients, which maps all angles from [-180; 180] to [0;90]. Also you use integer division.
Related
I fitted an ellipse based on edges of extracted red ball. But it's not accurate.
I extracted this red ball based on HSV Color Space, but it always ignores the contour of this ball. (Perhaps because color of contour is much darker).
Any good ideas to let me fit a better ellipse for this ball? I want to find an ellipse which can embrace the red ball as accurate as possible.
It will be better if I can use existing functions of OpenCV.
I have fixed this problem. It is still unstable, but at most of time it works.
source image. All of those images can be detected: https://www.dropbox.com/sh/daerty94kv5k2n7/AABu9Axewe6mL0NdEX2nG5MIa?dl=0
Fit ellipse based on color
Re-fit ellipse based on color and edges
The Video link: https://www.youtube.com/watch?v=q0TQYREm9uA
Here is source code:
#include <iostream>
#include "opencv2/opencv.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
using namespace cv;
using namespace std;
int main(int argc, char** argv)
{
cv::Mat capturedImage = imread(argv[1]);
if( capturedImage.empty() )
{
cout << "Couldn't open image " << argv[1] << "\nUsage: fitellipse <image_name>\n";
return 0;
}
/*============================= Phase 1: Translate Color Space from RGB to HSV =====================================================*/
cv::Mat imgHSV;
cv::cvtColor(capturedImage, imgHSV, cv::COLOR_BGR2HSV); //Convert the captured frame from BGR to HSV
cv::Mat imgGray;
cv::cvtColor(capturedImage, imgGray, CV_RGB2GRAY);
cv::Mat imgThresholded;
cv::inRange(imgHSV, cv::Scalar(160, 80, 70), cv::Scalar(179, 255, 255), imgThresholded); //Threshold the image
//morphological opening
cv::erode(imgThresholded, imgThresholded, cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(7, 7)) );
cv::dilate( imgThresholded, imgThresholded, cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(7, 7)) );
//morphological closing (removes small holes from the foreground)
cv::dilate( imgThresholded, imgThresholded, cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(7, 7)) );
cv::erode(imgThresholded, imgThresholded, cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(7, 7)) );
namedWindow("imgThresholded", WINDOW_NORMAL);
imshow("imgThresholded", imgThresholded);
/*============================= Phase 2: Fit a coarse ellipse based on red color ======================================================*/
vector<vector<cv::Point> > contours;
cv::findContours(imgThresholded, contours, CV_RETR_LIST, CV_CHAIN_APPROX_NONE, cv::Point(0,0));
size_t index = 0;
size_t largestSize = 0;
for(size_t i = 0; i < contours.size(); i++)
{
if (contours[i].size() > largestSize)
{
largestSize = contours[i].size();
index = i;
}
}
if (contours[index].size() < 6)
{
cout << "Do not have enough points" << endl;
return -1;
}
cv::Mat imgContour;
cv::Mat(contours[index]).convertTo(imgContour, CV_32F);
cv::RotatedRect coarseEllipse = cv::fitEllipse(imgContour);
cv::Mat capturedImageClone = capturedImage.clone();
ellipse(capturedImageClone, coarseEllipse.center, coarseEllipse.size*0.5f, coarseEllipse.angle, 0.0, 360.0, cv::Scalar(0,255,255), 3, CV_AA);
namedWindow("capturedImageClone", CV_WINDOW_NORMAL);
imshow("capturedImageClone", capturedImageClone);
/*============================= Phase 3: Re-fit a final ellipse based on combination of color and edge ===============================*/
double cxc = coarseEllipse.center.x;
double cyc = coarseEllipse.center.y;
double ca = coarseEllipse.size.height/2;
double cb = coarseEllipse.size.width/2;
cv::Mat mask(capturedImage.rows, capturedImage.cols, CV_8UC3, cv::Scalar(0,0,0));
cv::circle(mask, cv::Point(coarseEllipse.center.x, coarseEllipse.center.y), coarseEllipse.size.height/2 + 100, cv::Scalar(255,255,255), -1);
cv::Mat imgMask;
cv::Mat edges;
cv::bitwise_and(capturedImage, mask, imgMask);
namedWindow("imgMask", CV_WINDOW_NORMAL);
imshow("imgMask", imgMask);
cv::GaussianBlur(imgMask, edges, cv::Size(5,5), 0);
cv::Canny(edges, edges, 50, 100);
namedWindow("edges", CV_WINDOW_NORMAL);
imshow("edges", edges);
cv::findContours(edges, contours, CV_RETR_LIST, CV_CHAIN_APPROX_NONE, cv::Point(0,0));
index = -1;
double centerDistance = (numeric_limits<double>::max)();
double abRatio = (numeric_limits<double>::max)();
cv::RotatedRect finalEllipse;
for (size_t i = 0; i < contours.size(); i++)
{
if (contours[i].size() < 500 || i == contours.size() - 1 || i == contours.size() - 2)
continue;
cv::Mat(contours[i]).convertTo(imgContour, CV_32F);
cv::RotatedRect tmpEllipse = cv::fitEllipse(imgContour);
double txc = tmpEllipse.center.x;
double tyc = tmpEllipse.center.y;
double ta = tmpEllipse.size.height/2;
double tb = tmpEllipse.size.width/2;
double tmpDis = (cxc - txc) * (cxc - txc) + (cyc - tyc) * (cyc - tyc);
if (tmpDis < centerDistance && fabs(tb/ta - 1) < abRatio && ta > ca && tb > cb)
{
centerDistance = tmpDis;
abRatio = fabs(tb/ta - 1);
index = i;
finalEllipse = tmpEllipse;
}
}
if (index == -1)
finalEllipse = coarseEllipse;
ellipse(capturedImage, finalEllipse.center, finalEllipse.size*0.5f, finalEllipse.angle, 0.0, 360.0, cv::Scalar(0,255,255), 3, CV_AA);
double xc = finalEllipse.center.x; // center x
double yc = finalEllipse.center.y; // center y
double theta = finalEllipse.angle; // rotation angle theta
double a = finalEllipse.size.height / 2; // semi-major axis: a
double b = finalEllipse.size.width / 2; // semi-minor axis: b
double A = a * a * sin(theta) * sin(theta) + b * b * cos(theta) * cos(theta);
double B = 2 * (b * b - a * a) * sin(theta) * cos(theta);
double C = a * a * cos(theta) * cos(theta) + b * b * sin(theta) * sin(theta);
double D = -2 * A * xc - B * yc;
double E = -B * xc - 2 * C * yc;
double F = A * xc * xc + B * xc * yc + C * yc * yc - a * a * b * b;
A = A/F;
B = B/F;
C = C/F;
D = D/F;
E = E/F;
F = F/F;
double ellipseArray[3][3] = {{A, B/2, D/2},
{B/2, C, E/2},
{D/2, E/2, F}};
cv::Mat ellipseMatrix(3,3,CV_64FC1, ellipseArray);
cout << ellipseMatrix << endl;
namedWindow("capturedImage", CV_WINDOW_NORMAL);
imshow("capturedImage", capturedImage);
imwrite(argv[2],capturedImage);
imwrite(argv[3],edges);
imwrite(argv[4],capturedImageClone);
imwrite(argv[5],imgMask);
waitKey(0);
return 0;
}
As an input I have a photo of a simple symbol, e.g.: https://www.dropbox.com/s/nrmsvfd0le0bkke/symbol.jpg
I would like to detect the straight lines in it, like points of start and ends of the lines. In this case, assuming the top left of the symbol is (0,0), the lines would be defined like this:
start end (coordinates of beginning and end of a line)
1. (0,0); (0,10) (vertical line)
2. (0,10); (15, 15)
3. (15,15); (0, 20)
4. (0,20); (0,30)
How can I do it (pereferably using OpenCV)? I though about Hough lines, but they seem to be good for perfect thin straight lines, which is not the case in a drawing. I'll probably work on binarized image, too.
Give a try on this,
Apply thinning algorithm on threshold image.
Find contours.
approxPolyDP for the found contour.
See some reference:
approxpolydp-for-edge-maps
Creating Bounding boxes and circles for contours
maybe you can work on this one.
assume a perfect binarization:
run HoughLinesP
(not implemented) try to group those detected lines
I used this code:
int main()
{
cv::Mat image = cv::imread("HoughLinesP_perfect.png");
cv::Mat gray;
cv::cvtColor(image,gray,CV_BGR2GRAY);
cv::Mat output; image.copyTo(output);
cv::Mat g_thres = gray == 0;
std::vector<cv::Vec4i> lines;
//cv::HoughLinesP( binary, lines, 1, 2*CV_PI/180, 100, 100, 50 );
// cv::HoughLinesP( h_thres, lines, 1, CV_PI/180, 100, image.cols/2, 10 );
cv::HoughLinesP( g_thres, lines, 1, CV_PI/(4*180.0), 50, image.cols/20, 10 );
for( size_t i = 0; i < lines.size(); i++ )
{
cv::line( output, cv::Point(lines[i][0], lines[i][3]),
cv::Point(lines[i][4], lines[i][3]), cv::Scalar(155,255,155), 1, 8 );
}
cv::imshow("g thres", g_thres);
cv::imwrite("HoughLinesP_out.png", output);
cv::resize(output, output, cv::Size(), 0.5,0.5);
cv::namedWindow("output"); cv::imshow("output", output);
cv::waitKey(-1);
std::cout << "finished" << std::endl;
return 0;
}
EDIT:
updated code with simple line clustering (`minimum_distance function taken from SO):
giving this result:
float minimum_distance(cv::Point2f v, cv::Point2f w, cv::Point2f p) {
// Return minimum distance between line segment vw and point p
const float l2 = cv::norm(w-v) * cv::norm(w-v); // i.e. |w-v|^2 - avoid a sqrt
if (l2 == 0.0) return cv::norm(p-v); // v == w case
// Consider the line extending the segment, parameterized as v + t (w - v).
// We find projection of point p onto the line.
// It falls where t = [(p-v) . (w-v)] / |w-v|^2
//const float t = dot(p - v, w - v) / l2;
float t = ((p-v).x * (w-v).x + (p-v).y * (w-v).y)/l2;
if (t < 0.0) return cv::norm(p-v); // Beyond the 'v' end of the segment
else if (t > 1.0) return cv::norm(p-w); // Beyond the 'w' end of the segment
const cv::Point2f projection = v + t * (w - v); // Projection falls on the segment
return cv::norm(p - projection);
}
int main()
{
cv::Mat image = cv::imread("HoughLinesP_perfect.png");
cv::Mat gray;
cv::cvtColor(image,gray,CV_BGR2GRAY);
cv::Mat output; image.copyTo(output);
cv::Mat g_thres = gray == 0;
std::vector<cv::Vec4i> lines;
cv::HoughLinesP( g_thres, lines, 1, CV_PI/(4*180.0), 50, image.cols/20, 10 );
float minDist = 100;
std::vector<cv::Vec4i> lines_filtered;
for( size_t i = 0; i < lines.size(); i++ )
{
bool keep = true;
int overwrite = -1;
cv::Point2f a(lines[i][0], lines[i][6]);
cv::Point2f b(lines[i][7], lines[i][3]);
float lengthAB = cv::norm(a-b);
for( size_t j = 0; j < lines_filtered.size(); j++ )
{
cv::Point2f c(lines_filtered[j][0], lines_filtered[j][8]);
cv::Point2f d(lines_filtered[j][9], lines_filtered[j][3]);
float distCDA = minimum_distance(c,d,a);
float distCDB = minimum_distance(c,d,b);
float lengthCD = cv::norm(c-d);
if((distCDA < minDist) && (distCDB < minDist))
{
if(lengthCD >= lengthAB)
{
keep = false;
}
else
{
overwrite = j;
}
}
}
if(keep)
{
if(overwrite >= 0)
{
lines_filtered[overwrite] = lines[i];
}
else
{
lines_filtered.push_back(lines[i]);
}
}
}
for( size_t i = 0; i < lines_filtered.size(); i++ )
{
cv::line( output, cv::Point(lines_filtered[i][0], lines_filtered[i][10]),
cv::Point(lines_filtered[i][11], lines_filtered[i][3]), cv::Scalar(155,255,155), 2, 8 );
}
cv::imshow("g thres", g_thres);
cv::imwrite("HoughLinesP_out.png", output);
cv::resize(output, output, cv::Size(), 0.5,0.5);
cv::namedWindow("output"); cv::imshow("output", output);
cv::waitKey(-1);
std::cout << "finished" << std::endl;
return 0;
}
You should try the Hough Line Transform. And here is an example from this website
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <iostream>
using namespace cv;
using namespace std;
int main()
{
Mat src = imread("building.jpg", 0);
Mat dst, cdst;
Canny(src, dst, 50, 200, 3);
cvtColor(dst, cdst, CV_GRAY2BGR);
vector<Vec2f> lines;
// detect lines
HoughLines(dst, lines, 1, CV_PI/180, 150, 0, 0 );
// draw lines
for( size_t i = 0; i < lines.size(); i++ )
{
float rho = lines[i][0], theta = lines[i][1];
Point pt1, pt2;
double a = cos(theta), b = sin(theta);
double x0 = a*rho, y0 = b*rho;
pt1.x = cvRound(x0 + 1000*(-b));
pt1.y = cvRound(y0 + 1000*(a));
pt2.x = cvRound(x0 - 1000*(-b));
pt2.y = cvRound(y0 - 1000*(a));
line( cdst, pt1, pt2, Scalar(0,0,255), 3, CV_AA);
}
imshow("source", src);
imshow("detected lines", cdst);
waitKey();
return 0;
}
With this you should be able to tweak and get the proprieties you are looking for (vertices).
How to draw Optical flow images from ocl::PyrLKOpticalFlow::dense() Which actually calculates both horizontal and vertical component of the Optical flow? So I don't know how to draw them. I'm new to opencv . Can anyone help me?
Syntax :
ocl::PyrLKOpticalFlow::dense(oclMat &prevImg, oclMat& nextImg, oclMat& u, oclMat &v,oclMat &err)
A well establische method used in the optical flow community is to display a motion vector field as a color coded image as you can see at one of the various data sets. E.g MPI dataset or the Middlebury dataset.
Therefor you estimate the length and the angle of your motion vector. And use a HSV to RGB colorspace transformation (see OpenCV cvtColor function) to create your color coded image. Use the angle of your motion vector as H (Hue) - channel and the normalized length as the S (Saturation) - channel and set V (Value) to 1. The the color of your image will show you the direction of your motion and the saturation the length ( speed ).
The code will should like this ( Note if use_value == true, the Saturation will be set to 1 and the Value channel is related to the motion vector length):
void FlowToRGB(const cv::Mat & inpFlow,
cv::Mat & rgbFlow,
const float & max_size ,
bool use_value)
{
if(inpFlow.empty()) return;
if( inpFlow.depth() != CV_32F)
throw(std::exception("FlowToRGB: error inpFlow wrong data type ( has be CV_32FC2"));
const float grad2deg = (float)(90/3.141);
cv::Mat pol(inpFlow.size(), CV_32FC2);
float mean_val = 0, min_val = 1000, max_val = 0;
float _dx, _dy;
for(int r = 0; r < inpFlow.rows; r++)
{
for(int c = 0; c < inpFlow.cols; c++)
{
cv::Point2f polar = cvmath::toPolar(inpFlow.at<cv::Point2f>(r,c));
polar.y *= grad2deg;
mean_val +=polar.x;
max_val = MAX(max_val, polar.x);
min_val = MIN(min_val, polar.x);
pol.at<cv::Point2f>(r,c) = cv::Point2f(polar.y,polar.x);
}
}
mean_val /= inpFlow.size().area();
float scale = max_val - min_val;
float shift = -min_val;//-mean_val + scale;
scale = 255.f/scale;
if( max_size > 0)
{
scale = 255.f/max_size;
shift = 0;
}
//calculate the angle, motion value
cv::Mat hsv(inpFlow.size(), CV_8UC3);
uchar * ptrHSV = hsv.ptr<uchar>();
int idx_val = (use_value) ? 2:1;
int idx_sat = (use_value) ? 1:2;
for(int r = 0; r < inpFlow.rows; r++, ptrHSV += hsv.step1())
{
uchar * _ptrHSV = ptrHSV;
for(int c = 0; c < inpFlow.cols; c++, _ptrHSV+=3)
{
cv::Point2f vpol = pol.at<cv::Point2f>(r,c);
_ptrHSV[0] = cv::saturate_cast<uchar>(vpol.x);
_ptrHSV[idx_val] = cv::saturate_cast<uchar>( (vpol.y + shift) * scale);
_ptrHSV[idx_sat] = 255;
}
}
cv::Mat rgbFlow32F;
cv::cvtColor(hsv, rgbFlow32F, CV_HSV2BGR);
rgbFlow32F.convertTo(rgbFlow, CV_8UC3);}
}
Python
Please refer to opt_flow.py#draw_flow
def draw_flow(img, flow, step=16):
h, w = img.shape[:2]
y, x = np.mgrid[step/2:h:step, step/2:w:step].reshape(2,-1).astype(int)
fx, fy = flow[y,x].T
lines = np.vstack([x, y, x+fx, y+fy]).T.reshape(-1, 2, 2)
lines = np.int32(lines + 0.5)
vis = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
cv2.polylines(vis, lines, 0, (0, 255, 0))
for (x1, y1), (x2, y2) in lines:
cv2.circle(vis, (x1, y1), 1, (0, 255, 0), -1)
return vis
C++
Please can refer to tvl1_optical_flow.cpp#drawOpticalFlow
static void drawOpticalFlow(const Mat_<Point2f>& flow, Mat& dst, float maxmotion = -1)
{
dst.create(flow.size(), CV_8UC3);
dst.setTo(Scalar::all(0));
// determine motion range:
float maxrad = maxmotion;
if (maxmotion <= 0)
{
maxrad = 1;
for (int y = 0; y < flow.rows; ++y)
{
for (int x = 0; x < flow.cols; ++x)
{
Point2f u = flow(y, x);
if (!isFlowCorrect(u))
continue;
maxrad = max(maxrad, sqrt(u.x * u.x + u.y * u.y));
}
}
}
for (int y = 0; y < flow.rows; ++y)
{
for (int x = 0; x < flow.cols; ++x)
{
Point2f u = flow(y, x);
if (isFlowCorrect(u))
dst.at<Vec3b>(y, x) = computeColor(u.x / maxrad, u.y / maxrad);
}
}
}
I did something like this in my code, a while ago:
calcOpticalFlowPyrLK(frame_prec,frame_cur,v_corners_prec[i],corners_cur,status, err);
for(int j=0; j<corners_cur.size(); j++){
if(status[j]){
line(frame_cur,v_corners_prec[i][j],corners_cur[j],colors[i]);
}
}
Basically I draw a line between the points tracked by the OF in this iteration and the previous ones, this draws the optical flow lines which represent the flow on the image.
Hope this helps..
I am attempting to determine the image gradient direction using the results from OpenCV's Sobel method.
I understand this should be a very simple task, I think I understand the theory but implementing this has been more challenging than I thought.
I would expect the gradient directions to be between 0-360 degrees, however my code shows all gradients fall between 180 - 270 degrees.
I submitted a previous version of this code which included an integer division issue. I have fixed this but it has not solved the problem of a restricted angle of direction.
I have stepped through all the code but I just can't see where I'm going wrong? Can anyone spot my mistake?
Thanks.
void getGradients(IplImage* original, cv::Mat* gradArray)
{
cv::Mat original_Mat(original, true);
// Convert it to gray
cv::cvtColor( original_Mat, original_Mat, CV_RGB2GRAY );
//cv::blur(original_Mat, original_Mat, cv::Size(7,7));
/// Generate grad_x and grad_y
cv::Mat grad_x = cv::Mat::zeros(original->height, original->width, CV_16S);
cv::Mat grad_y = cv::Mat::zeros(original->height, original->width, CV_16S);
/// Gradient X
cv::Sobel(original_Mat, grad_x, CV_16S, 1, 0, 3);
/// Gradient Y
cv::Sobel(original_Mat, grad_y, CV_16S, 0, 1, 3);
uchar* pixelX = grad_x.data;
uchar* pixelY = grad_y.data;
uchar* grad1 = gradArray[0].data;
uchar* grad2 = gradArray[1].data;
uchar* grad3 = gradArray[2].data;
uchar* grad4 = gradArray[3].data;
uchar* grad5 = gradArray[4].data;
uchar* grad6 = gradArray[5].data;
uchar* grad7 = gradArray[6].data;
uchar* grad8 = gradArray[7].data;
int count = 0;
int min = 999999;
int max = -1;
for(int i = 0; i < grad_x.rows * grad_x.cols; i++)
{
double directionRAD = atan2(pixelY[i], pixelX[i]);
int directionDEG = (int)(180 + directionRAD / M_PI * 180);
if(directionDEG < min){min = directionDEG;}
if(directionDEG > max){max = directionDEG;}
if(directionDEG >= 0 && directionDEG <= 45) { grad1[i] = 255; count++;}
if(directionDEG >= 45 && directionDEG <= 90) { grad2[i] = 255; count++;}
if(directionDEG >= 90 && directionDEG <= 135) { grad3[i] = 255; count++;}
if(directionDEG >= 135 && directionDEG <= 190) { grad4[i] = 255; count++;}
if(directionDEG >= 190 && directionDEG <= 225) { grad5[i] = 255; count++;}
if(directionDEG >= 225 && directionDEG <= 270) { grad6[i] = 255; count++;}
if(directionDEG >= 270 && directionDEG <= 315) { grad7[i] = 255; count++;}
if(directionDEG >= 315 && directionDEG <= 360) { grad8[i] = 255; count++;}
if(directionDEG < 0 || directionDEG > 360)
{
cout<<"Weird gradient direction given in method: getGradients.";
}
}
}
grad_x and grad_y are Mats of type CV_16SC1, that is every pixel in them takes up two bytes.
However you declare pixelX and pixelY to pointers to 8 bit bytes. Therefore pixelX[1] is the second byte of the first gradient, rather than the second gradient.
You need
short* pixelX = grad_x.ptr<short>(0);
short* pixelY = grad_y.ptr<short>(0);
Problem is here
uchar* pixelX = grad_x.data;
uchar* pixelY = grad_y.data;
and here
double directionRAD = atan2(pixelY[i], pixelX[i]);
You don`t take abs(), but use unsigned pointer. That is why you cannot get x or y negative.
Should be:
short* pixelX = (short*) grad_x.data;
short* pixelY = (short*) grad_y.data;
and
double directionRAD = atan2((double)pixelY[i], (double)pixelX[i]);
working on square detection. the problem is on the radiant floor. as you can see pictures.
any idea for solve this problem ?
thank you.
source image :
output :
source code:
void EdgeDetection::find_squares(const cv::Mat& image,
vector >& squares,cv::Mat& outputFrame) {
unsigned long imageSize = (long) (image.rows * image.cols) / 1000;
if (imageSize > 1200) RESIZE = 9;
else if (imageSize > 600) RESIZE = 5;
else if (imageSize > 300) RESIZE = 3;
else RESIZE = 1;
Mat src(Size(image.cols / RESIZE, image.rows / RESIZE),CV_YUV420sp2BGR);
// Resize src to img size
resize(image, src, src.size() ,0.5, 0.5, INTER_LINEAR);
Mat imgeorj=image;
const int N = 10;//11;
Mat pyr, timg, gray0(src.size(), CV_8U), gray;
// down-scale and upscale the image to filter out the noise
pyrDown(src, pyr, Size(src.cols / 2, src.rows / 2));
pyrUp(pyr, timg, src.size());
#ifdef blured
Mat blurred(src);
medianBlur(src, blurred, 15);
#endif
vector<vector<Point> > contours;
// find squares in every color plane of the image
for ( int c = 0; c < 3; ++c) {
int ch[] = {c, 0};
mixChannels(&timg, 1, &gray0, 1, ch, 1);
// try several threshold levels
for ( int l = 0; l < N; ++l) {
// hack: use Canny instead of zero threshold level.
// Canny helps to catch squares with gradient shading
if (l == 0) {
// apply Canny. Take the upper threshold from slider
// and set the lower to 0 (which forces edges merging)
// Canny(gray0, gray, 0, thresh, 5);
// Canny(gray0, gray, (10+l), (10+l)*3, 3);
Canny(gray0, gray,50, 200, 3 );
// dilate canny output to remove potential
// holes between edge segments
dilate(gray, gray, Mat(), Point(-1, -1));
//erode(gray, gray, Mat(), Point(-1, -1), 1);
} else {
// apply threshold if l!=0:
// tgray(x,y) = gray(x,y) < (l+1)*255/N ? 255 : 0
gray = gray0 >= (l + 1) * 255 / N;
}
// find contours and store them all as a list
findContours(gray, contours, CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE);
vector<Point> approx;
// test each contour
for (size_t i = 0; i < contours.size(); ++i) {
// approximate contour with accuracy proportional
// to the contour perimeter
approxPolyDP(Mat(contours[i]), approx, arcLength(Mat(contours[i]), true) * 0.02, true);
if (approx.size() == 4 &&
fabs(contourArea(Mat(approx))) > 5000 &&
isContourConvex(Mat(approx))) {
float maxCosine = 0;
for (register int j = 2; j < 5; ++j) {
// find the maximum cosine of the angle between joint edges
float cosine = fabs(angle(approx[j%4], approx[j-2], approx[j-1]));
maxCosine = MAX(maxCosine, cosine);
}
// if cosines of all angles are small
// (all angles are ~90 degree) then write quandrange
// vertices to resultant sequence
if (maxCosine < 0.3) {
squares.push_back(approx);
}
}
}
}
}
debugSquares(squares, imgeorj,outputFrame);
}
You can try using Hough transform for detecting straight edges and use the those for constructing the square.