记录平时学习的碎片…
快速径向对称变换在原论文中的主要作用是用来作为图像特征点检测(面部特征、感兴趣区域),其依赖于梯度信息,来检测图像中高度径向对称的点。
圆是旋转对称的,可以利用此算法来检测圆环的中心点。圆周上的点到圆心的距离相等,且圆周上的点的梯度方向垂直于切线指向圆心。基于以上两点结合径向对称变换算法内容,对圆周上的每一个点进行遍历生成该半径下的投影图像,找到投影图像中像素较大的值,便可确定圆心的位置。
#include "opencv2\core\core.hpp"
#include "opencv2\highgui\highgui.hpp"
#include "opencv2\imgproc\imgproc.hpp"
#include
#define FRST_MODE_BRIGHT 1
#define FRST_MODE_DARK 2
#define FRST_MODE_BOTH 3
using namespace std;
using namespace cv;
/**
Calculate vertical gradient for the input image
@param input Input 8-bit image
@param output Output gradient image
*/
void grady(const cv::Mat& input, cv::Mat &output)
{
output = cv::Mat::zeros(input.size(), CV_64FC1);
for (int y = 0; y<input.rows; y++)
{
for (int x = 1; x<input.cols - 1; x++)
{
*((double*)output.data + y*output.cols + x) = (double)(*(input.data + y*input.cols + x + 1) - *(input.data + y*input.cols + x - 1)) / 2;
}
}
}
/**
Calculate horizontal gradient for the input image
@param input Input 8-bit image
@param output Output gradient image
*/
void gradx(const cv::Mat& input, cv::Mat &output)
{
output = cv::Mat::zeros(input.size(), CV_64FC1);
for (int y = 1; y<input.rows - 1; y++)
{
for (int x = 0; x<input.cols; x++)
{
*((double*)output.data + y*output.cols + x) = (double)(*(input.data + (y + 1)*input.cols + x) - *(input.data + (y - 1)*input.cols + x)) / 2;
}
}
}
/*
参数1:输入灰度图像
参数2:投票点阈值
参数3:投票模式选择(BRIGHT DARK BOTH)
参数4:起始半径
参数5:终止半径
*/
std::vector<cv::Vec3i> frstCircle(const cv::Mat& inputImage, int VotingThre, const int mode, const int StartRadii, const int EndRadii)
{
int width = inputImage.cols;
int height = inputImage.rows;
//不是为了模糊图像,而是为了使函数连续便于求导
GaussianBlur(inputImage, inputImage, cv::Size::Size_(3, 3), 0, 0);
cv::Mat gx, gy;
gradx(inputImage, gx);
grady(inputImage, gy);
// set dark/bright mode
bool dark = false;
bool bright = false;
if (mode == FRST_MODE_BRIGHT)
bright = true;
else if (mode == FRST_MODE_DARK)
dark = true;
else if (mode == FRST_MODE_BOTH) {
bright = true;
dark = true;
}
else {
throw std::exception("invalid mode!");
}
//保存符合条件的投票数、投票点坐标、和半径
std::vector<cv::Vec4i>Container_Voting_Point_radii;
//遍历每一个半径
for (int Radii = StartRadii; Radii <= EndRadii; ++Radii) {
cv::Mat O_n = cv::Mat::zeros(inputImage.rows + 2 * Radii, inputImage.cols + 2 * Radii, CV_64FC1);
//在该半径下生成该半径的投票图
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
cv::Point p(i, j);
cv::Vec2d g = cv::Vec2d(gx.at<double>(i, j), gy.at<double>(i, j));
// Calculate the gradient value
double gnorm = std::sqrt(g.val[0] * g.val[0] + g.val[1] * g.val[1]);
if (gnorm > 0) {
cv::Vec2i gp;
gp.val[0] = (int)std::round((g.val[0] / gnorm) * Radii);
gp.val[1] = (int)std::round((g.val[1] / gnorm) * Radii);
if (bright) {
cv::Point ppve(p.x + gp.val[0] + Radii, p.y + gp.val[1] + Radii);
O_n.at<double>(ppve.x, ppve.y) = O_n.at<double>(ppve.x, ppve.y) + 1;
}
if (dark) {
cv::Point pnve(p.x - gp.val[0] + Radii, p.y - gp.val[1] + Radii);
O_n.at<double>(pnve.x, pnve.y) = O_n.at<double>(pnve.x, pnve.y) + 1;
}
}
}
}
for (int i = Radii; i < O_n.rows - Radii; i++)
{
for (int j = Radii; j < O_n.cols - Radii; j++)
{
if (O_n.at<double>(i, j) > VotingThre) {
//遍历投影图将像素值大于VotingThre的点保存下来(以及像素值、半径)
cv::Point Maxpoint(j, i);
cv::Vec4i vote = cv::Vec4i(O_n.at<double>(i, j), Maxpoint.x - Radii, Maxpoint.y - Radii, Radii);
Container_Voting_Point_radii.push_back(vote);
}
}
}
}
//要返回的数据依次为:半径、圆心坐标
std::vector<cv::Vec3i>CenterPoints;
//该半径下按投票数降序排序
if (!Container_Voting_Point_radii.empty()) {
for (int i = 0; i < Container_Voting_Point_radii.size(); i++) {
for (int j = i + 1; j < Container_Voting_Point_radii.size(); j++) {
cv::Vec4i Temp = cv::Vec4i(0, 0, 0, 0);
if (Container_Voting_Point_radii[i].val[0] < Container_Voting_Point_radii[j].val[0]) {
Temp = Container_Voting_Point_radii[j];
Container_Voting_Point_radii[j] = Container_Voting_Point_radii[i];
Container_Voting_Point_radii[i] = Temp;
}
}
}
}
//同心圆判断,选择保存内径或外径
if (!Container_Voting_Point_radii.empty()) {
for (int i = 0; i < Container_Voting_Point_radii.size(); i++)
{
//被设置为半径为0的组,表示已被遍历,不进行接下来的运算
if (Container_Voting_Point_radii[i].val[3] == 0)
continue;
//逐元素判断,找同心圆并保存最大的外径
cv::Vec4i GreatPoint = Container_Voting_Point_radii[i];
for (int j = i + 1; j < Container_Voting_Point_radii.size(); j++)
{
//计算两个点之间绝对值距离
int PointDistance = abs(GreatPoint.val[1] - Container_Voting_Point_radii[j].val[1]) +
abs(GreatPoint.val[2] - Container_Voting_Point_radii[j].val[2]);
//距离小于某个值,实施一系列赋值
if (PointDistance < 8) {
//保存投票数多的点的坐标
if (Container_Voting_Point_radii[j].val[0] > GreatPoint.val[0]) {
GreatPoint.val[1] = Container_Voting_Point_radii[j].val[1];
GreatPoint.val[2] = Container_Voting_Point_radii[j].val[2];
}
//保存同心圆外径
if (Container_Voting_Point_radii[j].val[3] > GreatPoint.val[3]) {
GreatPoint.val[3] = Container_Voting_Point_radii[j].val[3];
}
//半径置为0 表示该坐标下的点已被遍历
Container_Voting_Point_radii[j].val[3] = 0;
}
}
cv::Vec3i point = cv::Vec3i(GreatPoint.val[1], GreatPoint.val[2], GreatPoint.val[3]);
CenterPoints.push_back(point);
}
}
//打印所有的圆心和半径
for (int i = 0; i < CenterPoints.size(); ++i)
{
cout << CenterPoints[i] << endl;
}
return CenterPoints;
要求输入的参数较多,在实际使用的过程中,个人认为除非是某些特定的场合,不然半径是多变的,很难确定有效的半径集合,在这里投票数阈值还需要手动输入,这也是不好的,后面有时间改成自适应阈值。
小测试(参数并非通用):
int main()
{
Mat dstImage, SrcImage, ThreImage, grayImage;
string path = "F:\\Image_design\\src2.jpg";
SrcImage = imread(path); //source image
if (!SrcImage.data) {
std::cout << "Could not open or find the image" << std::endl;
return -1;
}
std::vector<cv::Vec3i> Centerpoints;
int VotingThre = 18;
cvtColor(SrcImage, grayImage, COLOR_BGR2GRAY);
Centerpoints = frstCircle(grayImage, VotingThre, FRST_MODE_BOTH, 5, 15);
for (int i = 0; i < Centerpoints.size(); ++i)
{
cv::circle(SrcImage, cv::Point(Centerpoints[i].val[0], Centerpoints[i].val[1]),
Centerpoints[i].val[2], Scalar(0, 255, 0), 2);
}
imshow("Origin", SrcImage); //效果图
waitKey(0);
return 0;
}