《数字图像处理》 第11章 表示和描述 学习笔记附部分例子代码(c++opencv)

表示和描述

    • 0. 前言
    • 1. 表示
      • 1.1 边界追踪
      • 1.2 链码
      • 1.3 使用最小周长多边形的多边形近似
    • 2. 边界描绘子
      • 2.1 一些简单的描绘子![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/45dddc76217e4fde93a11e2631b2a71a.png#pic_center =500x)
      • 2.2 形状数
      • 2.3 傅里叶描绘子
      • 2.4 统计矩
    • 3. 区域描绘子
      • 3.1 一些简单的描绘子
      • 3.2 拓扑描绘子
      • 3.3 纹理
    • Opencv补充:

0. 前言

本章只学习了前三节……

VS安装Image watch插件请查看官网OpenCV: Image Watch: viewing in-memory images in the Visual Studio debugger

第三版教材中图片下载地址: book images downloads

vs2019配置opencv可以查看:VS2019 & Opencv4.5.4配置教程

前情回顾:
《数字图像处理》第三章 灰度变换和空间滤波 学习笔记附部分例子代码
《数字图像处理》第四章 频率域滤波 学习笔记附部分例子代码
数字图像处理第五章 图像复原和重建(内容较简单,就没有详细记录笔记)
《数字图像处理》第六章 彩色图像处理 学习笔记附部分例子代码
《数字图像处理》第七章 小波域多分辨率处理 学习笔记附部分例子代码
数字图像处理第八章 图像压缩 非重点
《数字图像处理》第九章 形态学图像处理 学习笔记附部分例子代码
《数字图像处理》第十章 图像分割 学习笔记附部分例子代码

1. 表示

1.1 边界追踪

处理的是二值图像,其目标和背景点分别标为1和0,Moore边界追踪算法的步骤如下:

  1. 找到图像左上角为1的点b0为边界起始点。b0左边的点为c0,从c0开始按顺时针方向考察b0的8邻域,找到的第一个值1的点为b1,令扫描到b1前的点为c1。

  2. 赋值b=b1,c=c1。

  3. 从c开始顺时针方向行进,找到第一个值1的点nk,其之前的点均为背景点。

  4. 赋值b=nk,c=nk-1。

  5. 重复step3和step4,直到b=b0且下一个边界点为b1。

1.2 链码

链码用于表示由顺次连接的具有指定长度和方向的直线段组成的边界

使用了opencv的findContours()drawContours(),具体形参的表示可以看补充

void ch11_test01(string path) {
    Mat image = imread(path, IMREAD_GRAYSCALE);
    if (image.empty()) {
        cout << "Unable to load the image\n";
        return;
    }
    // 创建一个纯黑画布
    Mat black(image.size(), CV_8U, Scalar(0));

    Mat filtered;
    blur(image, filtered, Size(9, 9));

    Mat result = Otsu(filtered);//该函数的实现请看第十章笔记的3.2节

    // 寻找轮廓
    vector<vector<Point>> contours;
    findContours(result, contours, RETR_EXTERNAL, CHAIN_APPROX_NONE);

    vector<Point> contour = contours[0];

    vector<int> freemaCode;
    Point currentPoint = contour[0];
    Point nextPoint;

    for (size_t i = 1; i < contour.size(); i++) {
        nextPoint = contour[i];

        // 计算方向
        int dx = nextPoint.x - currentPoint.x;
        int dy = nextPoint.y - currentPoint.y;
        // 转换为佛雷曼链码
        int code = -1;
        if (dx == 0 && dy == -1)      code = 0;
        else if (dx == 1 && dy == -1) code = 1;
        else if (dx == 1 && dy == 0)  code = 2;
        else if (dx == 1 && dy == 1)  code = 3;
        else if (dx == 0 && dy == 1)  code = 4;
        else if (dx == -1 && dy == 1) code = 5;
        else if (dx == -1 && dy == 0) code = 6;
        else if (dx == -1 && dy == -1)code = 7;
        // 添加到链码序列
        freemaCode.push_back(code);
        // 更新当前点
        currentPoint = nextPoint;
    }
    // 输出佛雷曼链码
    cout << "Freeman Chain Code: ";
    for (int code : freemaCode) {
        cout << code << " ";
    }
    cout << endl;

    // 在黑布上画出轮廓
    drawContours(black, contours, -1, Scalar(128), 2);

    displayImg(image, "原图01");
    displayImg(black, "轮廓");
    waitKey(0);
}

在这里插入图片描述

1.3 使用最小周长多边形的多边形近似

数字边界可以用多边形以任意精度来近似。对于一条闭合边界,当多边形的边数等于边界上的点数时,这种近似会变得很精确,此时,每对相邻的点定义了多边形的一条边。多边形近似的目的是使用尽可能少的线段数来获取给定边界的基本形状。

2. 边界描绘子

2.1 一些简单的描绘子在这里插入图片描述

边界的长度是其最简单的描绘子之一。边界B的直径定义为

D i a m ( B ) = m a x [ D ( p i , p j ) ] Diam(B)=max[D(p_i, p_j)] Diam(B)=max[D(pi,pj)]

D是距离度量,pi和pj是边界上的两点。该直线段称为边界的长轴,边界的短边定义为与长轴垂直的直线,长轴与短轴之比称为边界的偏心率,偏心率同样是一个描述子。有时使用相邻边界线段的斜率差作为两条线段交点处曲率的描述子。

2.2 形状数

链码边界的一次差分取决于起始点。形状数的阶n定义为该链码的数字个数,形状编码不知道怎么来的,差分可看下图(方向按逆时针排序):

2.3 傅里叶描绘子

边界由坐标序列s(k)=[x(k), y(k)]表示,每个坐标对可以当做一个复数来处理

s ( k ) = x ( k ) + j y ( k ) s(k) = x(k) + jy(k) s(k)=x(k)+jy(k)

对sk进行离散傅里叶变换,使用前P个傅里叶系数进行逆变换得到

s ^ ( k ) = 1 K ∑ u = 0 P − 1 a ( u ) e j 2 π u k / P \hat s(k)=\frac{1}{K}\sum_{u=0}^{P-1}a(u)e^{j2\pi uk/P} s^(k)=K1u=0P1a(u)ej2πuk/P

由第四章得知,高频分量说明精细细节,而低频分量决定全局形状,因此,P越小,边界丢失的细节就越多。傅里叶描绘子对起始点不敏感,对平移、旋转和尺度变化不敏感。

这里教材的例子,代码复现一直有问题555555,所以就放弃放代码了

2.4 统计矩

对于一段边界,连接两个端点连接起来,然后旋转至水平得到g(r)

在这里插入图片描述

看成一个幅度直方图,纵坐标p(v)是v出现的概率估计,所以关于其均值的v的第n阶矩阵为

μ n ( v ) = ∑ i = 0 A − 1 ( v i − m ) n p ( v i ) \mu _n(v)=\sum_{i=0}^{A-1}(v_i-m)^{n}p(v_i) μn(v)=i=0A1(vim)np(vi)

式中, m = ∑ i = 0 A − 1 v i p ( v i ) m= \sum_{i=0}^{A-1}v_i p(v_i) m=i=0A1vip(vi)

m为v的均值或平均值, μ 2 \mu_2 μ2为v的方差

3. 区域描绘子

3.1 一些简单的描绘子

一个区域的面积定义为该区域中像素的数量,区域的周长是其边界的长度。致密性描绘子圆度率有下式表示:

R c = 4 π A P 2 R_c =\frac{4\pi A}{P^2} Rc=P24πA

A为讨论区域的面积,P是其周长

3.2 拓扑描绘子

拓扑学是研究未受任何变形影响的图形的性质,前提是该图形为被撕裂或粘连。区域描述有两点

  • 区域内的孔洞数量H

  • 连通分量的数量C

可以定义欧拉数E=C-H.

当描述有直线线段表示的区域非常简单,V表示顶点数,Q表示边数,F表示面数,那么欧拉公式如下:

V − Q + F = C − H V-Q+F=C-H VQ+F=CH

opencv可以使用connectedComponents()connectedComponentsWithStats()两个函数,后者会得到stats这一5列矩阵,该矩阵的每一行对应一个连通区域的标签,包含有连通区域左上角的坐标x, y,以及外接矩形的宽高和面积。

void ch11_test03(string path) {
	Mat image = imread(path, IMREAD_GRAYSCALE);
	if (image.empty()) {
		cout << "Unable to load the image\n";
		return;
	}

	Mat binaryImage;
	threshold(image, binaryImage, 78, 255, THRESH_BINARY); //阈值选择78
	binaryImage = ~binaryImage;

	//Mat labeledImg;
	//int numLabels = connectedComponents(binaryImg, labeledImg, 8);
	//numLabels--;
	Mat labeledImage;
	Mat stats, centroids;
	int numLabels = connectedComponentsWithStats(binaryImage, labeledImage, stats, centroids, 8);
	// 减去背景区域
	numLabels--;

	cout << "连通个数:" << numLabels << endl;

	//displayImg(labeledImage, "标记结果");
	displayImg(image, "原图");
	displayImg(binaryImage, "阈值处理结果");
	waitKey(0);
}


在这里插入图片描述

Stats矩阵和centroids矩阵介绍

3.3 纹理

统计方法: 一个区域的灰度级直方图的统计矩,如同3.1。二阶矩在纹理描述中特别重要,度量:

R ( z ) = 1 − 1 1 − σ 2 ( z ) R(z)=1-\frac{1}{1-\sigma ^2(z)} R(z)=11σ2(z)1

可以提现图像的平滑程度。

而三阶矩:

μ 3 ( v ) = ∑ i = 0 A − 1 ( v i − m ) 3 p ( v i ) \mu _3(v)=\sum_{i=0}^{A-1}(v_i-m)^{3}p(v_i) μ3(v)=i=0A1(vim)3p(vi)

是直方图偏斜度的度量。

“一致性”度量:

U ( z ) = ∑ L − 1 i = 0 p 2 ( z i ) U(z)=\sum_{L-1}^{i=0}p^2(z_i) U(z)=L1i=0p2(zi)

平均熵度量:

e ( z ) = ∑ i = 0 L − 1 p ( z i ) l o g 2 p ( z i ) e(z)=\sum_{i=0}^{L-1}p(z_i)log_2p(z_i) e(z)=i=0L1p(zi)log2p(zi)

令 Q 是定义两个像素彼此相对位置的一个算子,并考虑一幅具有L个可能灰度级的图像f。 令 G 为一个矩阵,其元素gij是灰度为zi和zj的像素对出现在f中由Q所指定的位置处的次数。按这种方法形成的矩阵称为灰度级共生矩阵。

![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/c86cbd9e6b3b4c56ba0e03ab332f1c45.png#pic_center = 500x)

图像中可能的灰度级决定了G的大小。

ch11_test04("..\\Images_CH11\\Fig1130(a)(uniform_noise).tif", "a"); //图a显示时需要归一化到0-255
ch11_test04("..\\Images_CH11\\Fig1130(b)(sinusoidal).tif", "b"); //显示时归一化到0-1
ch11_test04("..\\Images_CH11\\Fig1130(c)(cktboard_section).tif", "c");//显示时归一化到0-255

//计算共生矩阵,像素对(水平方向一格)
Mat calGLCM(Mat input, int level) {
	Mat result = Mat::zeros(Size(level, level), CV_32F);
	//遍历水平像素对
	for (int x = 0; x + 1 < input.cols; x++) {
		for (int y = 0; y < input.rows; y++) {
			int scale1 = input.at<uchar>(y, x);		//at.(row, col)
			int scale2 = input.at<uchar>(y, x + 1);
			result.at<float>(scale1, scale2)++;
		}
	}
	return result;
}


void ch11_test04(string path, string info) {
	Mat image = imread(path, IMREAD_GRAYSCALE);
	if (image.empty()) {
		cout << "Unable to load the image\n";
		return;
	}

	Mat GLCM = calGLCM(image, 256);
	//normalize(GLCM, GLCM, 0, 1, NORM_MINMAX);
	displayImg(image, "原图04" + info);
	displayImg(GLCM, "共生矩阵" + info);
	waitKey(0);
}


在这里插入图片描述

在这里插入图片描述

Opencv补充:

  1. void findContours(InputArray image, OutputArrayOfArrays contours, OutputArray hierarchy, int mode, int method)

    • image: 输入的二值化图像,通常是通过阈值处理得到的,要求是8位单通道图像。

    • contours: 输出参数,包含检测到的轮廓的容器,通常是 vector<vector<Point>> 类型。

    • hierarchy: 输出参数,轮廓的层次结构,通常是 vector<Vec4i> 类型。可以为可选参数,如果不需要层次结构,可以设置为 noArray()

    • mode: 轮廓检索模式,可以是 RETR_EXTERNAL(仅检测外部轮廓)、RETR_LIST(检测所有轮廓,不建立层次关系)、RETR_CCOMP(检测所有轮廓,建立两层层次关系)、RETR_TREE(检测所有轮廓,建立层次树结构)。

    • method: 轮廓逼近方法,可以是 CHAIN_APPROX_NONE(保存所有的轮廓点)、CHAIN_APPROX_SIMPLE(压缩水平、垂直、对角方向的元素,只保留其端点)

  2. void drawContours(InputOutputArray image, InputArrayOfArrays contours, int contourIdx, const Scalar& color, int thickness = 1, int lineType = LINE_8);

    • image: 要绘制轮廓的图像。

    • contours: 保存轮廓点的容器,通常是vector<vector<Point>>类型

    • contourIdx: 要绘制的轮廓的索引。如果是负数,表示绘制所有轮廓。

    • color: 绘制轮廓的颜色,通常使用Scalar类表示,例如Scalar(0, 255, 0)表示绿色。

    • thickness: 绘制轮廓线的粗细,如果是负数表示填充轮廓内部。默认值是1。

    • lineType: 绘制轮廓的线型,可以是LINE_8LINE_4LINE_AA
      );`

    • image: 要绘制轮廓的图像。

    • contours: 保存轮廓点的容器,通常是vector<vector<Point>>类型

    • contourIdx: 要绘制的轮廓的索引。如果是负数,表示绘制所有轮廓。

    • color: 绘制轮廓的颜色,通常使用Scalar类表示,例如Scalar(0, 255, 0)表示绿色。

    • thickness: 绘制轮廓线的粗细,如果是负数表示填充轮廓内部。默认值是1。

    • lineType: 绘制轮廓的线型,可以是LINE_8LINE_4LINE_AA

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

智慧地球(AI•Earth)社区成立一周年啦!独家福利与惊喜彩蛋等你来拿!

原文&#xff1a;智慧地球&#xff08;AI•Earth&#xff09;社区成立一周年啦&#xff01; 智慧地球社区 一周年庆典&#x1f38a; 独家福利&#x1f381;与惊喜彩蛋&#x1f389;等你来拿&#xff01; 智慧地球&#xff08;AI•Earth&#xff09;社区自2023年1月11日建立以…

软件测试|Docker Kill/Pause/Unpause命令详细使用指南

简介 Docker是一种流行的容器化平台&#xff0c;提供了各种命令和功能来管理和操作容器。本文将详细介绍Docker中的三个重要命令&#xff1a;kill、pause和unpause。我们将深入了解它们的作用、用法和示例&#xff0c;帮助您更好地理解和使用这些命令。 什么是Docker Kill/Pa…

C++异常处理机制

文章目录 C语言传统的处理错误的方式C异常概念异常的使用自定义异常体系C标准库的异常体系异常的优缺点 正文开始前给大家推荐个网站&#xff0c;前些天发现了一个巨牛的 人工智能学习网站&#xff0c; 通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。 点击…

Mysql为什么只能支持2000w左右的数据量?

首先说明一下&#xff1a; MySQL并没有硬性规定只能支持到2000万左右的数据量。 其实&#xff0c;MySQL能够处理的数据量远远超过这个数字。无论是开源社区版还是商业版&#xff0c; MySQL在适当的硬件和配置下&#xff0c;都能够支持非常大的数据集。 通常所说的“MySQL只能…

延迟加载:提升性能的隐形利器

引言 想象一下&#xff0c;你正在玩一款大型电子游戏。如果游戏在启动的时候就加载了所有的关卡、角色和道具&#xff0c;那玩家可能需要等待很长时间才能开始游戏&#xff0c;而且大部分内容可能在游戏的初期都不会被用到。显然&#xff0c;这样的做法既低效又耗时。 而延迟加…

科研学习|论文解读——超准确性反馈:使用眼动追踪来检测阅读过程中的可理解性和兴趣

摘要&#xff1a; 了解用户想要什么信息是信息科学和技术面临的最大挑战。隐式反馈是解决这一挑战的关键&#xff0c;因为它允许信息系统了解用户的需求和偏好。然而&#xff0c;可用的反馈往往是有限的&#xff0c;而且其解释也很困难。为了应对这一挑战&#xff0c;我们提出了…

仿真炫酷烟花+背景音乐-H5代码实现_可直接运行【附完整源码】

文章目录 背景效果实现源码代码解析完整源码下载总结寄语 背景 烟花仿真是一项具有创意和娱乐性质的项目&#xff0c;旨在通过H5技术实现炫酷的烟花效果&#xff0c;并结合背景音乐营造出一个生动、愉悦的视听体验。该项目的目标是通过Web浏览器即时展现精美的烟花效果&#x…

Jackson反序列化的规则 没有无参构造报错问题

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of java.util.function.Supplier. Jackson反序列化的规则 没有无参构造报错问题 &#xff1b; 报错如下&#xff1a; com.fasterxml.jackson.databind.exc.InvalidDefinitionExce…

欧洲地区媒体发稿推广攻略10个利器解析-华媒舍

在当今数字化时代&#xff0c;媒体发稿是企业推广的重要手段之一。尤其是在欧洲地区这个高度发达的市场中&#xff0c;正确地运用媒体发稿推广工具将给企业带来巨大的商机。本文将揭示欧洲地区媒体发稿推广的10个利器&#xff0c;帮助企业更好地利用媒体发布推广信息&#xff0…

通过cpolar在公网访问本地网站

通过cpolar可以轻松将本地网址映射到公网进行访问&#xff0c;下面简要介绍一下实现步骤。 目录 一、cpolar下载 二、安装 三、使用 3.1 登录 3.2 创建隧道 一、cpolar下载 cpolar官网地址&#xff1a;cpolar - secure introspectable tunnels to localhost 通过QQ邮箱…

Python如何求解最长公共子序列

Python-求解两个字符串的最长公共子序列 一、问题描述 给定两个字符串&#xff0c;求解这两个字符串的最长公共子序列&#xff08;Longest Common Sequence&#xff09;。比如字符串1&#xff1a;BDCABA&#xff1b;字符串2&#xff1a;ABCBDAB。则这两个字符串的最长公共子序…

GPT实战系列-大模型为我所用之借用ChatGLM3构建查询助手

GPT实战系列-https://blog.csdn.net/alex_starsky/category_12467518.html 如何使用大模型查询助手功能&#xff1f;例如调用工具实现网络查询助手功能。目前只有 ChatGLM3-6B 模型支持工具调用&#xff0c;而 ChatGLM3-6B-Base 和 ChatGLM3-6B-32K 模型不支持。 定义好工具的…

【Android取证篇】小米手机OTG取证知识

【Android取证篇】小米手机OTG取证知识 小米手机OTG使用方法—【蘇小沐】 目录 1、OTG用途 2、手机连不上U盘 3、小米手机有没有OTG 4、手机usb调试找不到 5、MHL能否在HDMI输出视频的同时进行USB传输 1、OTG用途 使用OTG外接设备&#xff0c;需要使用和手机接口对应匹配的…

一起学docker(六)| docker网络

Docker网络 不启动docker&#xff0c;网络情况&#xff1a; 启动docker&#xff0c;网络情况&#xff1a; 作用 容器间的互联和通信以及端口映射容器IP变动时候可以通过服务名直接网络通信而不受影响 常用命令 docker network --help 查看docker网络相关命令docker network…

C++——STL标准模板库——容器详解——stack+queue

一、基本概念 &#xff08;一&#xff09;stack&#xff08;栈或堆栈&#xff09; 一种只允许同一端进出的线性数据结构&#xff0c;数据先进后出。基本模型类似于瓶子。 &#xff08;二&#xff09;queue&#xff08;队列&#xff09; 一种只允许一端进、另一端出的线性数…

Tomcat服务为什么起不来?

转载说明&#xff1a;如果您喜欢这篇文章并打算转载它&#xff0c;请私信作者取得授权。感谢您喜爱本文&#xff0c;请文明转载&#xff0c;谢谢。 服务跑在Tomcat下面&#xff0c;有时候会遇到Tomcat起不来的情况。目前为止常遇到的情况有如下几种&#xff1a; 1. Tomcat服务…

Linux第11步_解决“挂载后的U盘出现中文乱码”

学习完“通过终端挂载和卸载U盘”&#xff0c;我们发现U盘下的中文文件名会出现乱码&#xff0c;现在讲解怎么解决这个问题。其实就是复习一下“通过终端挂载和卸载U盘”&#xff0c;单独讲解&#xff0c;是为了解决问题&#xff0c;一次性搞好&#xff0c;我们会不长记性。 在…

无心剑七绝《高斯黎曼》

七绝高斯黎曼 高耸云端四海惊 斯人伟绩震豪英 黎霞璀璨通灵处 曼妙方程万世名 2024年1月6日 平水韵八庚平韵 《七绝高斯黎曼》是无心剑所作的一首以数学家为主题的七言绝句。全诗巧妙地将两位杰出的数学家——高斯&#xff08;Carl Friedrich Gauss&#xff09;与黎曼&#…

性能优化-OpenMP基础教程(三)

本文主要介绍OpenMP并行编程的环境变量和实战、主要对比理解嵌套并行的效果。 &#x1f3ac;个人简介&#xff1a;一个全栈工程师的升级之路&#xff01; &#x1f4cb;个人专栏&#xff1a;高性能&#xff08;HPC&#xff09;开发基础教程 &#x1f380;CSDN主页 发狂的小花 &…

HarmonyOS 应用开发学习笔记 stateStyles:多态样式

1、 HarmoryOS Ability页面的生命周期 2、 Component自定义组件 3、HarmonyOS 应用开发学习笔记 ets组件生命周期 4、HarmonyOS 应用开发学习笔记 ets组件样式定义 Styles装饰器&#xff1a;定义组件重用样式 Extend装饰器&#xff1a;定义扩展组件样式 前面记录了ets组件样式…