图像处理之DBSCAN算法(C++)

news2024/11/15 21:47:23

图像处理之DBSCAN算法(C++)


文章目录

  • 图像处理之DBSCAN算法(C++)
  • 前言
  • 一、DBSCAN算法原理
  • 二、代码实现
  • 总结


前言

DBSCAN聚类算法是一种无监督的数据分类方法,该算法不需要训练数据就可以实现对数据的分类。
DBSCAN概念


一、DBSCAN算法原理

主要概念与参数:

  • ε值:样本与样本之间的距离阈值,如果样本A与样本B的距离小于该阈值,则认为样本A在样本B的邻域内,同时样本B也在样本A的邻域内
  • minPts:每一个样本的邻域内样本数阈值,如果该样本邻域内的样本数大于等于该阈值,则认为该样本是核心点
  • 核心点:即邻域内的样本数大于等于minPts的样本。如下图所示,如果样本A的邻域内(以A为圆心的圆内)样本数达到minPts以上,则认为A为核心点
  • 样本距离:欧式距离与曼哈顿距离是两种很常见的衡量数据样本距离的指标,假设有样本A(a1,a2,…,an)和样本B(b1,b2,…,bn),那么A与B的欧式距离为
    欧氏距离
    曼哈顿距离为:
    曼哈顿距离
  • 样本的访问标记:一开始将所有样本的标记设置为-1,表示所有样本都没有被访问。算法执行过程中,会遍历一遍所有样本,经过遍历的样本则将其标记置1,表示该样本已经被访问过,不用再处理。
  • 样本的类编号:设置一个初始类编号为-1,分类过程中,每新增一个类,类编号加1。每当一个样本被归类到某一个类之后,该样本的类编号则设置为当前新增的类编号。所以可以通过判断该样本的类编号是否为-1来判断其是否已经被归类。

二、代码实现

#include <opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;

/**********DBSCAN算法***************/
//数据结构
//样本点
class dbscanPoint {
public:
	int col;		//纵坐标
	int row;		//横坐标
	int grayValue;	//灰度值
	int cluster;	//簇类的类别,-1表示不属于任何簇类
	int visited;	//访问标志位,1--表示已访问,0--表示未访问
	int pointType;	//1--噪声点;2--边界点;3--核心点
	int pts;		//该点的领域内点的个数
	dbscanPoint():cluster(-1),visited(0),pointType(1),pts(0){ }
};

//邻域内的点
class nepsPoint{
public:
	dbscanPoint p;		//样本点
	int index;			//样本点在原点集中的索引
};

//每一个邻域中的点所属于的核心点
class nepsList {
public:
	int centerIndex;		//该邻域中所属核心点的索引
	int nearIndex;			//该邻域中点的索引
};

//计算曼哈顿距离
float calcuDistance(dbscanPoint& a, dbscanPoint& b)
{
	return abs(a.col - b.col) + abs(a.row - b.row) + abs(a.grayValue - b.grayValue);
}

//将图像Mat数据类型转换成dbscanPoint类型的数组
void mat2dbscanPoint(cv::Mat& src,std::vector<dbscanPoint>& dst)
{
	dbscanPoint tmp;
	for(int i=0;i<src.rows;i++)
		for (int j = 0; j < src.cols; j++)
		{
				tmp.row = i;
				tmp.col = j;
				tmp.grayValue = src.at<uchar>(i, j);
				dst.push_back(tmp);
		}
}

//对每一类的像素进行可视化,赋予不同的颜色
void displayImg(std::vector<std::vector<dbscanPoint>> clusters,cv::Size size,cv::Mat& dst)
{
	cv::Mat tmpImg(size, CV_8UC3);

	//RNG rng = theRNG();
	srand((unsigned)time(NULL));
	for (int i = 0; i < clusters.size(); i++)
	{
		/*int r = rng.uniform(0, 255);
		int b = rng.uniform(0, 255);
		int g = rng.uniform(0, 255);*/
		int r = rand() % (255 + 1);
		int g = rand() % (255 + 1);
		int b = rand() % (255 + 1);
		for (int j = 0; j < clusters[i].size(); j++)
		{
			int row = clusters[i][j].row;
			int col = clusters[i][j].col;
			tmpImg.at<cv::Vec3b>(row, col)[0] = b;
			tmpImg.at<cv::Vec3b>(row, col)[1] = g;
			tmpImg.at<cv::Vec3b>(row, col)[2] = r;
		}
	}
	tmpImg.copyTo(dst);
}

/*
* @param std::vector<dbscanPoint> p	样本点集合(输入)
* @param float eps		样本与样本点之间的距离阈值,即半径
* @param int minPts		集合中点的最小数量
* @param std::vector<std::vector<dbscanPoint>> clusters   分类后的集合(输出)
* @breif DBSCAN算法实现
*/
void DBSCAN(std::vector<dbscanPoint>& p, float eps, int minPts, std::vector<std::vector<dbscanPoint>>& clusters)
{
	// 计算每一个点的邻域点集
	std::vector<std::vector<nepsList>> centerPoints(p.size());
	for(int i=0;i<p.size();i++)
		for (int j = i; j < p.size(); j++)
		{
			if (calcuDistance(p[i], p[j]) < eps)
			{
				p[i].pts++;					//计数每一个点的邻域点集的个数
				nepsList tmpEpsList;
				tmpEpsList.centerIndex = i;
				tmpEpsList.nearIndex = j;
				centerPoints[i].push_back(tmpEpsList);		//将点j加入到点i的邻域点集中

				if (i != j)
				{
					p[j].pts++;
					tmpEpsList.centerIndex = j;
					tmpEpsList.nearIndex = i;
					centerPoints[j].push_back(tmpEpsList);	//将点j加入到点i的邻域点集中v
				}
			}
		}

	//判断是否是核心点,判断的标准是:邻域内点的个数是否达到minPts
	for (int i = 0; i < p.size(); i++)
	{
		if (p[i].pts >= minPts)		
		{
			p[i].pointType = 3;		//标记核心点
		}
	}

	int cluster_num = -1;	//簇号初始化为-1

	for (int i = 0; i < p.size(); i++)
	{
		if (p[i].visited == 1)		//如果当前点已经被访问,则跳过
			continue;
		
		p[i].visited = 1;			//如果当前点未被访问,则标记为已访问

		if (p[i].pointType == 3)		//如果当前点为核心点
		{
			cluster_num++;						//簇编号+1
			std::vector<dbscanPoint> cluster;	//新建一个簇
			cluster.push_back(p[i]);			//将当前的核心点加入到新建的簇中
			p[i].cluster = cluster_num;			//将当前的簇序号赋值给当前点的所属簇编号

			//求当前核心点的邻域点集
			std::vector<nepsPoint> currentPoints;
			for (int k = 0; k < centerPoints[i].size(); k++)
			{
				nepsPoint tmpEpsPoint;
				tmpEpsPoint.p = p[centerPoints[i][k].nearIndex];
				tmpEpsPoint.index = centerPoints[i][k].nearIndex;
				currentPoints.push_back(tmpEpsPoint);
			}

			//遍历当前核心点的所有邻域点集
			for (int j = 0; j < currentPoints.size(); j++)
			{
				if (p[currentPoints[j].index].visited == 0)	//通过index访问当前核心点在原点集中的邻域点,如果未被访问则继续,已被访问则跳过
				{
					p[currentPoints[j].index].visited = 1;	//邻域点在原点集未被访问,则标记为已访问
					currentPoints[j].p.visited = 1;			//邻域点未被访问,则标记为已访问


					if (p[currentPoints[j].index].pointType == 3)
					{
						for (int m = 0; m < centerPoints[currentPoints[j].index].size(); m++)
						{
							nepsPoint tmpEpsPoint;
							tmpEpsPoint.p = p[centerPoints[currentPoints[j].index][m].nearIndex];
							tmpEpsPoint.index = centerPoints[currentPoints[j].index][m].nearIndex;
							currentPoints.push_back(tmpEpsPoint);
						}
					}

					if (p[currentPoints[j].index].cluster == -1)			//如果当前遍历点未加入任何簇
					{
						cluster.push_back(p[currentPoints[j].index]);		//将该点加入新建的簇中
						p[currentPoints[j].index].cluster = cluster_num;	//将当前的核心点加入到新建的簇中
						currentPoints[j].p.cluster = cluster_num;			//将当前的簇序号赋值给当前遍历点的所属簇编号
					}
				}
			}
			clusters.push_back(cluster);
		}
	}
}


int main()
{
	// 读取图片
	string filepath = "F://work_study//algorithm_demo//dbscan (3).jpg";
	cv::Mat src = cv::imread(filepath,cv::IMREAD_GRAYSCALE);
	if (src.empty())
	{
		return -1;
	}
	std::vector<dbscanPoint> points;
	mat2dbscanPoint(src, points);

	
	std::vector<std::vector<dbscanPoint>> clusters;
	DBSCAN(points, 15, 25, clusters);
	
	cv::Mat dst;
	displayImg(clusters, src.size(), dst);



	cv::imwrite("dst.jpg", dst);
	cv::waitKey(0);
	return 0;
}

原图:
原图
结果图:
结果


总结

本文主要介绍了DBSCAN算法的原理以及使用opencv、C++对灰度图的算法实现,大家可以根据自己的需求结合DBSCAN算法使用。

参考资料:
DBSCAN聚类算法的理解与应用

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1701621.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

最最最重要的集群参数配置(上)no.7

我希望通过两期内容把这些重要的配置讲清楚。严格来说这些配置并不单单指Kafka服务器端的配置&#xff0c;其中既有Broker端参数&#xff0c;也有主题&#xff08;后面我用我们更熟悉的Topic表示&#xff09;级别的参数、JVM端参数和操作系统级别的参数。 需要你注意的是&…

家政上门服务小程序,客商紧密连系的作用是什么

家政服务拓展速度很快&#xff0c;大小城市都有不少品牌门店&#xff0c;其涵盖项目多样化&#xff0c;使得部分年轻人和老年人长期消费需要&#xff0c;商家与客户都需要完善的路径进行长期合作。 运用【雨科】平台搭建家政上门服务预约小程序&#xff0c;客户随时预约服务、…

技术前沿:三品PLM系统引领工程变更管理新趋势

引言 在当今快速变化的制造行业&#xff0c;产品生命周期管理&#xff08;PLM&#xff09;系统已成为企业不可或缺的工具之一。PLM系统不仅帮助企业优化产品开发流程&#xff0c;还对工程变更管理&#xff08;ECM&#xff09;起着至关重要的作用。本文将探讨PLM系统在工程变更…

高职高校实训教学实验室管理系统一体化

盛元广通高职高校实训教学实验室管理系统一体化是确保实验教学有序进行的关键环节。通过更加科学 、有效、合理的管理&#xff0c;明确排课原则、收集课程信息、评估实验室资源、制定排课计划、冲突检测与调整、发布排课信息、调课管理以及数据统计与分析等措施。实现了实验室资…

Go语言

文章目录 Go语言特点应用领域Go语言和Java语言的对比JavaGo 总结 Go语言安装配置环境变量Hello World Go语言 Go语言全称Golanguage&#xff0c;Go&#xff08;又称 Golang&#xff09;是 Google 的 Robert Griesemer&#xff0c;Rob Pike 及 Ken Thompson 开发的一种静态强类…

数组Array

数组的基本用法 概念 数组是有序元素序列。如果将若干个数据类型相同的变量的集合命名&#xff0c;那么该命名就是数组名。数组元素的重点是类型相同并且连续在内存中存放的数据。 定义格式 存储类型 数据类型 数组名 [ 元素个数 ] &#xff1b; 例如&#xff1a; &#x…

AHB与APB总线介绍

1 什么是AHB、APB? AHB&#xff08;Advanced High-performance Bus&#xff09;高速总线&#xff0c;接高速master&#xff0c;APB&#xff08;Advanced Peripheral Bus&#xff09;外设总线&#xff0c;用来接低速slave&#xff0c;一个master可以有多个slave&#xff0c;AH…

若依微服务实现分布式事务

一、基本介绍 1、什么是分布式事务 指一次大的操作由不同的小操作组成的&#xff0c;这些小的操作分布在不同的服务器上&#xff0c;分布式事务需要保证这些小操作要么全部成功&#xff0c;要么全部失败。从本质上来说&#xff0c;分布式事务就是为了保证不同数据库的数据一致…

2.1 数据类型-常量-变量(整型-浮点-字符)

目录 1 数据类型 1.1 关键字 2 常量 3 变量 3.1 命名规则 4 整形数据 4.1 符号常量 4.2 整型变量 5 浮点型数据 5.1 浮点型常量 5.2 浮点型变量 6 字符型数据 6.1 字符型常量 转义字符 6.2 字符数据在内存中的存储形式及其使用方法 6.3 ASCII码表 7 字符串型常…

homebrew安装mysql的一些问题

本文目录 一、Homebrew镜像安装二、mac安装mysql2.1、修改mysql密码 本文基于mac环境下进行的安装 一、Homebrew镜像安装 Homebrew国内如何自动安装&#xff0c;运行命令/bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)" 会…

2024年,游戏行业还值得进入吗?

来自知乎问题“2024年&#xff0c;游戏行业还值得进入吗&#xff1f;”的回答。 ——原问题描述&#xff1a;从超小厂执行策划做起&#xff0c;未来有前途吗&#xff1f; 展望2024年&#xff0c;国内外的游戏市场环境或将变得更加复杂&#xff0c;曾经那个水大鱼大的时代过去了…

ArduPilot开源飞控之MAVProxy深入研读系列 - 2蜂群链路

ArduPilot开源飞控之MAVProxy深入研读系列 - 2蜂群链路 1. 源由2. 蜂群链路2.1 拓扑结构2.2 设备标识2.3 命令列表 3. 蜂群应用4. 总结技术进步未来挑战 5. 参考资料 1. 源由 MAVProxy的一个关键功能是它能够通过UDP将来自无人机的消息转发到网络上的多个其他设备上的地面站软…

AI绘画图生图有什么用?

随着AI渗透到我们生活中的各个角落&#xff0c;AI绘画图生图的出现&#xff0c;更是在艺术领域引起了广泛的关注和讨论。那么&#xff0c;AI绘画图生图究竟有什么作用呢? 首先&#xff0c;AI绘画图生图能够极大地提高创作效率。传统的绘画过程需要艺术家们花费大量的时间和精力…

uni-app实现页面之间的跳转传参(八)

界面之间的参数传递在 开发中经常会用到,这节主要将一下uni-app开发应用是的传参情况。如下图所示,我的一级界面将点检分成三类:日点检、周点检和年保养;在点击相应的会导航到相应的功能。 在uni-app中常用的方法有uni.navigateTo(OBJECT)、uni.redirectTo(OBJECT);简单的…

一文带你学会如何部署个人博客到云服务器,并进行域名备案与解析!

哈喽&#xff0c;大家好呀&#xff01;这里是码农后端。之前我给大家介绍了如何快速注册一个自己的域名&#xff0c;并创建一台自己的阿里云ECS云服务器。本篇将介绍如何将个人博客部署到云服务器&#xff0c;并进行域名备案与解析。 1、域名备案 注册了域名并购买了云服务器之…

牛客循环5.27

1006 错误代码 不知道原因&#xff0c;有大佬解答一下吗 ac代码 1007 错误代码 ac代码

AI音乐神器Suno V3.5进化全解析:功能升级吊炸天,让音乐创作更简单!

前言 目前&#xff0c;suno的v3.5版本已经向Pro和Premier会员开放&#xff0c;从更新当天到现在我已经使用了近2000积分&#xff0c;接下来我将v3.5的使用体验和与旧版本v3进行比较&#xff0c;让大家更直观的感受到v3.5的强大。 其中一个最屌的功能&#xff0c;我放在最后介绍…

linux下cp和mv命令显示进度条

1.查看当前系统下coreutils工具包的版本号&#xff1a; [rootk8s-master ~]# rpm -qa | grep -w coreutils coreutils-8.22-24.el7_9.2.x86_64当前版本为8.22。 因为cp 和 mv 命令由 coreutils 软件包提供&#xff0c;所以需要重新下载 coreutils 软件包配置补丁 2.下载core…

创意学习剪辑利器:一键添加动图水印,轻松提升视频专业度与创意新境界!

在数字化时代&#xff0c;视频已成为我们生活中不可或缺的一部分。无论是学习分享、工作展示还是生活记录&#xff0c;视频都以其直观、生动的形式&#xff0c;赢得了广大用户的喜爱。然而&#xff0c;如何在众多的视频中脱颖而出&#xff0c;展现出自己的专业度和创意&#xf…

CI/CD:持续集成/持续部署

1. 安装docker、docker-compose # 安装Docker yum install -y yum-utils device-mapper-persistent-data lvm2 yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo sed -i sdownload.docker.commirrors.aliyun.com/docker-ce /…