Here is an approach without cv::HoughLines()
: on the binary image, execute a function to detect the most distant pixel from the center of the image towards a specific direction (left, right, top, bottom). This function returns the following pixels, drawn in the image below in red, green and blue:
Left: [21, 35]
Top: [43, 0]
Right: [63, 35]

From these 3 points you have a couple of options to detect the angle:
- Discard one of the points and compute the angle between the other 2;
- Figure out which one is the center point and compute the angle between all 3 of them;
The source code below figures out which one is closest to the center of the image and computes the angle from all the 3 points using the atan2()
method.
Running the example with the binary image outputs:
Angle: 57.8477 (degrees)
Seems legit!
There's a few improvements you can do from here on to improve the accuracy of the angle. One of them is to skeletonize the binary image and make those lines really thin.
Source code:
#include <opencv2/opencv.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <iostream>
enum imgEdge {
LEFT = 0,
RIGHT = 1,
TOP = 2,
BOTTOM = 3
};
// Find the most distant pixel from the center in a particular direction
cv::Point mostDistantPixel(const cv::Mat& input, const imgEdge& edge)
{
if (edge < 0 || edge > 3)
{
std::cout << "!!! mostDistantPixel() invalid side" << std::endl;
return cv::Point();
}
if (input.channels() > 1)
{
std::cout << "!!! mostDistantPixel() only single channel img is supported" << std::endl;
return cv::Point();
}
cv::Point mostDistant(-1,-1);
for (int r = 0; r < input.rows; r++)
{
for (int c = 0; c < input.cols; c++)
{
// Examine pure white pixels only (row, col)
if (input.at<uchar>(r,c) == 255)
{
switch (edge)
{
case imgEdge::LEFT:
if (c <= mostDistant.x || mostDistant == cv::Point(-1, -1))
mostDistant = cv::Point(c, r);
break;
case imgEdge::RIGHT:
if (c >= mostDistant.x || mostDistant == cv::Point(-1, -1))
mostDistant = cv::Point(c, r);
break;
case imgEdge::TOP:
if (r <= mostDistant.y || mostDistant == cv::Point(-1, -1))
mostDistant = cv::Point(c, r);
break;
case imgEdge::BOTTOM:
if (r >= mostDistant.y || mostDistant == cv::Point(-1, -1))
mostDistant = cv::Point(c, r);
break;
}
}
}
}
return mostDistant;
}
// Eucledian distance between 2 points
unsigned int distance(const cv::Point& a, const cv::Point& b)
{
return std::sqrt(std::pow(b.x-a.x, 2) + std::pow(b.y-a.y, 2));
}
// Compute the angle between 3 points (degrees)
double calcAngle(const cv::Point& center, const cv::Point& point, const cv::Point& base)
{
/* Compute the angle between the center, a point and it's base (starting point for the angle computation)
*
* %
* * * @ = center
* * @ # # = base (origin)
* * * % = point
* * From # to %, there are 90 degrees
*/
double angle = std::atan2(point.y - center.y, point.x - center.x) * 180 / 3.141592;
angle = (angle < 0) ? (360 + angle) : angle;
return (360 - angle);
}
int main()
{
cv::Mat img = cv::imread("corner.png", CV_LOAD_IMAGE_GRAYSCALE);
if (img.empty())
{
std::cout << "!!! imread()" << std::endl;
return -1;
}
// Find the left-most, right-most and top-most pixel
cv::Point leftPix = mostDistantPixel(img, imgEdge::LEFT);
std::cout << "Left: " << leftPix << std::endl;
cv::Point topPix = mostDistantPixel(img, imgEdge::TOP);
std::cout << "Top: " << topPix << std::endl;
cv::Point rightPix = mostDistantPixel(img, imgEdge::RIGHT);
std::cout << "Right: " << rightPix << std::endl;
// Draw pixels for debugging purposes
// cv::Mat colored;
// cv::cvtColor(img, colored, CV_GRAY2BGR);
// cv::circle(colored, leftPix, 2, cv::Scalar(0, 0, 255), cv::FILLED); // red
// cv::circle(colored, topPix, 2, cv::Scalar(0, 255, 0), cv::FILLED); // green
// cv::circle(colored, rightPix, 2, cv::Scalar(255, 0, 0), cv::FILLED); // blue
// cv::imwrite("points.png", colored);
// Find the pivot point: which of them is closest to the center
cv::Point center(img.cols/2, img.rows/2);
unsigned int leftDistance = distance(center, leftPix);
unsigned int rightDistance = distance(center, rightPix);
unsigned int topDistance = distance(center, topPix);
// This part needs a lot more testing and refinement
double angle = 0;
if (leftDistance <= rightDistance && leftDistance <= topDistance)
angle = calcAngle(leftPix, topPix, rightPix); // looks good
else if (rightDistance <= leftDistance && rightDistance <= topDistance)
angle = calcAngle(rightPix, leftPix, topPix); // needs testing
else
angle = calcAngle(topPix, leftPix, rightPix); // needs testing
std::cout << "Angle: " << angle << " (degrees)" << std::endl;
return 0;
}