OpenCV + CPP 系列(卅四)图像特征提取(亚像素级别角点检测)

亚像素级别角点检测

亚像素:在生成数字图像处理时(拍照等)我们是将物理世界中连续的图像进行了离散化处理。现实世界中颜色为连续的且有无数种类,成像到像素面上每一个像素点只代表其附近的颜色,我们常使用的3通道图像颜色种类(255*255*255)代表,至于“附近”到什么程度?就很困难解释。两个像素之间有5.2微米的距离,在宏观上可以看作是连在一起的。但是在微观上,它们之间还有无限的更小的东西存在。这个更小的东西我们称它为“亚像素”。实际上“亚像素”应该是存在的,只是硬件上没有个细微的传感器把它检测出来。于是软件上把它近似地计算出来。

亚像素级别角点检测,提高检测精准度;由于理论与现实总是不一致的,实际情况下几乎所有的角点不会是一个真正的准确像素点。例如(100, 5) 实际上(100.234, 5.789)。

特别是在:跟踪,三维重建,相机校正方面,为了获取更加精准的角点,这样一来就需要亚像素级别角点检测。

亚像素定位方法

  • 插值方法
  • 基于图像矩计算
  • 曲线拟合方法 -(高斯曲面、多项式、椭圆曲面)

在这里插入图片描述

除了利用 HarrisShi-Tomasi方法进行角点检测 外, 还可以使用cornerEigenValsAndVecs() 函数和 cornerMinEigenVal() 函数自定义角点检测函数。 详情查看点击

如果对角点的精度有更高的要求,可以用 cornerSubPix() 函数将角点定位到子像素,从而取得亚像素级别的角点检测效果。

函数简介
函数goodFeaturesToTrack()函数只能提供简单的像素的整数坐标值,若需要实数坐标值则需要使用cornerSubPix()函数,用于寻找亚像素角点的位置:

void cornerSubPix(

InputArray image,          输入图像;
InputOutputArray corners,    初始化输入角点与精确的输出坐标
Size winSize,         搜索窗口半径。若Size(5,5),表示(5*2+1)*(5*2+1)=11*11大小的搜索窗口。
Size zeroZone,        表示死区的一半尺寸。为不对搜索区的中央位置做求和运算,用来避免自相关矩阵出现的某些可能的奇异性。值为(-1,-1)表示没有死区。
TermCriteria criteria      求角点的迭代过程的终止条件。
);

  • 其中:
    cv::TerCriteria::MAX_ITER :迭代终止条件为达到最大迭代次数终止
    cv::TerCriteria::EPS :   迭代到阈值终止
    cv::TerCriteria::MAX_ITER+cv::TerCriteria::EPS :两者都作为迭代终止条件

头文件 image_feature_all.h:声明类与公共函数

#pragma once
#include <opencv2/opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;

class ImageFeature {
public:
	void subpixel_corner_demo(Mat& image);
	
};

主函数main.cpp调用该类的公共成员函数

#include "image_feature_all.h"



int main(int argc, char** argv) {
	const char* img_path = "D:\\Desktop\\jianzhu.jpg";
	Mat image = imread(img_path);
	if (image.empty()) {
		cout << "图像数据为空,读取文件失败!" << endl;
	}
	ImageFeature imgfeature;
	imgfeature.subpixel_corner_demo(image);

	imshow("image", image);
	waitKey(0);
	destroyAllWindows();
	return 0;
}
演示像素坐标检测

检测出角点,再拟合亚像素级别角点位置

源文件 feature_extract.cpp:实现类与公共函数

static void on_subpixel(int num_corner, void* userdata) {
	Mat image = *((Mat*)userdata);
	Mat gray_src;
	cvtColor(image, gray_src, COLOR_BGR2GRAY);

	if (num_corner < 5) { num_corner = 5; }

	//角点检测Shi-Tomasi
	vector<Point2f> corners;
	double qualityLevel = 0.03;
	double minDistance = 10;
	int blockSize = 3;
	bool userHarris = false;
	double k = 0.04;
	goodFeaturesToTrack(gray_src, corners, num_corner, 
		qualityLevel, minDistance, Mat(), blockSize, userHarris, k);
	cout << "corners.size() = " << corners.size() << endl;

	//可视化
	RNG rng(12345);
	Mat resultImg = gray_src.clone();
	cvtColor(resultImg, resultImg, COLOR_GRAY2BGR);
	for (size_t i = 0; i < corners.size(); i++){
		Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
		circle(resultImg, corners[i], 2, color, 1, 8, 0);
	}
	imshow("subpixel", resultImg);

	//拟合亚像素角点位置并计算亚像素角点位置
	Size winSize = Size(5, 5);
	Size zerozone = Size(-1, -1);
	TermCriteria tc = TermCriteria(TermCriteria::EPS + TermCriteria::MAX_ITER, 40, 0.001);
	cornerSubPix(gray_src, corners, winSize, zerozone, tc);

	//打印精细坐标
	for (size_t i = 0; i < corners.size(); i++){
		cout << (i + 1) << ".point[x,y]\t" << corners[i].x << "," << corners[i].y << endl;
	}
	return;
}


void ImageFeature::subpixel_corner_demo(Mat& image) {
	cv::namedWindow("subpixel", WINDOW_NORMAL);
	int num_corner = 100;
	int max_corner = 500;
	createTrackbar("CorNum", "subpixel", &num_corner, max_corner, on_subpixel, (void*)&image);
	on_subpixel(0, &image);
}

在这里插入图片描述

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
利用Zernike矩阵进行图像亚像素级边缘提取的方法一般分为以下几个步骤: 1. 预处理。首先需要将图像进行灰度化、降噪等预处理操作,以方便后续的计算。 2. 计算Zernike矩阵。利用Zernike矩阵的正交性质,可以将图像分解为一系列的Zernike矩阵,从而得到图像的特征信息。 3. 边缘检测。通过Zernike矩阵的特征信息,可以进行边缘检测操作。一般来说,可以通过计算Zernike矩阵的一阶导数来得到边缘信息。 4. 边缘坐标提取。检测到边缘后,需要从中提取出边缘的坐标信息。可以通过一些阈值处理、插值等操作,实现亚像素级别的边缘坐标提取。 下面是一个示例代码,可以实现利用Zernike矩阵进行图像边缘提取,并显示边缘坐标: ```c++ #include <iostream> #include <opencv2/opencv.hpp> using namespace std; using namespace cv; // 计算Zernike矩阵 Mat zernikeMoment(Mat img, int n, int m) { int width = img.cols; int height = img.rows; Mat zernike = Mat::zeros(n + 1, n + 1, CV_64F); double x, y, r, theta, val; for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { x = (double)j / width * 2 - 1; y = (double)i / height * 2 - 1; r = sqrt(x * x + y * y); theta = atan2(y, x); val = 0; for (int s = 0; s <= (n - m) / 2; s++) { double tmp = 0; for (int p = 0; p <= s; p++) { double coef = pow(-1.0, p) * tgamma(n - s - 2 * p + 1) / (tgamma(p + 1) * tgamma((n - m) / 2 - s + p + 1) * tgamma((n + m) / 2 - s - p + 1)); tmp += coef * pow(r, n - 2 * s - p) * cos((n - 2 * s - 2 * p) * theta); } val += tmp * tgamma(s + 1) * pow(r, 2 * s); } zernike.at<double>(n - m, m) += val * img.at<uchar>(i, j); } } double norm = sqrt((n + 1) / (2 * M_PI) * tgamma(n + 1 + m) / tgamma(n + 1 - m)); return zernike * norm; } // 计算一阶导数 Mat zernikeDerivative(Mat zernike, int n, int m, int k) { Mat der = Mat::zeros(n + 1, n + 1, CV_64F); for (int i = 0; i <= n; i++) { for (int j = 0; j <= n - i; j++) { if (m + k >= 0 && m + k <= n - i - j) { der.at<double>(i + j, m + k) = (n - i - j + 1) * zernike.at<double>(i + 1 + j, m + k); } if (m - k >= 0 && m - k <= n - i - j) { der.at<double>(i + j, m - k) = (n - i - j + 1) * zernike.at<double>(i + 1 + j, m - k); } } } return der; } // 亚像素级别的边缘坐标提取 vector<Point2f> getSubpixelPoints(Mat img, vector<Point> points) { vector<Point2f> subpixelPoints; for (int i = 0; i < points.size(); i++) { Point p = points[i]; if (p.x > 0 && p.x < img.cols - 1 && p.y > 0 && p.y < img.rows - 1) { Mat patch = Mat::zeros(3, 3, CV_32F); for (int y = -1; y <= 1; y++) { for (int x = -1; x <= 1; x++) { patch.at<float>(y + 1, x + 1) = img.at<uchar>(p.y + y, p.x + x); } } float dx = 0.5 * (patch.at<float>(1, 2) - patch.at<float>(1, 0)); float dy = 0.5 * (patch.at<float>(2, 1) - patch.at<float>(0, 1)); if (dx == 0 && dy == 0) { subpixelPoints.push_back(Point2f(p.x, p.y)); } else { subpixelPoints.push_back(Point2f(p.x - dx / (patch.at<float>(1, 1) - patch.at<float>(1, 0)), p.y - dy / (patch.at<float>(1, 1) - patch.at<float>(0, 1)))); } } } return subpixelPoints; } int main() { // 读入图像 Mat img = imread("lena.jpg", IMREAD_GRAYSCALE); if (img.empty()) { cout << "Failed to read image!" << endl; return -1; } // 计算Zernike矩阵 Mat zernike = zernikeMoment(img, 20, 0); // 计算一阶导数 Mat der = zernikeDerivative(zernike, 20, 0, 1); // 找到边缘 vector<Point> edgePoints; for (int i = 0; i < img.rows; i++) { for (int j = 0; j < img.cols; j++) { if (der.at<double>(i, j) > 0) { edgePoints.push_back(Point(j, i)); } } } // 亚像素级别的边缘坐标提取 vector<Point2f> subpixelPoints = getSubpixelPoints(img, edgePoints); // 显示边缘 Mat edgeImg = Mat::zeros(img.size(), CV_8UC1); for (int i = 0; i < subpixelPoints.size(); i++) { circle(edgeImg, subpixelPoints[i], 1, Scalar(255), -1); } imshow("Edge", edgeImg); waitKey(); return 0; } ``` 需要注意的是,Zernike矩阵的计算需要调用tgamma函数,因此需要在编译时链接libm库。在Linux系统下,可以使用以下命令进行编译: ``` g++ -o main main.cpp -lopencv_core -lopencv_highgui -lm ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

SongpingWang

你的鼓励是我创作的最大动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值