Otsu 二值化算法:原理、实现与应用

news2024/12/21 6:40:48

摘要: 本文深入探讨了 Otsu 二值化算法,详细阐述其原理,包括类间方差的计算与阈值确定机制。分别给出了该算法在 C#、Python 和 C++ 中的实现代码示例,并对代码进行了详细注释与分析。此外,还探讨了 Otsu 二值化算法在图像分割、目标检测、字符识别等多个领域的广泛应用,展示了其在计算机视觉与图像处理领域的重要地位与价值。

一、引言

在计算机视觉与图像处理领域,图像二值化是一项基础且关键的操作。它将图像中的像素根据一定的规则分为两类,通常为前景(目标)和背景,从而简化图像分析与处理过程。Otsu 二值化算法作为一种自动确定阈值的方法,在众多应用场景中表现出色,无需手动设定阈值,能够根据图像自身的特性自适应地找到最佳阈值,广泛应用于图像分割、目标识别、文档处理等多个领域。

二、Otsu 二值化算法原理

(一)灰度直方图基础

图像的灰度直方图是理解 Otsu 算法的重要基础。灰度直方图描述了图像中各个灰度级出现的频率。对于一幅灰度图像,其灰度值通常在 0(黑色)到 255(白色)之间。通过统计每个灰度值在图像中出现的像素数量,可构建灰度直方图。

设图像的灰度级为 L(对于 8 位灰度图像,L=256),图像中灰度值为i的像素数量为ni ,总像素数量为

(二)类间方差公式推导

Otsu 算法的核心是最大化类间方差。假设通过阈值t将图像像素分为两类C0(灰度值范围为0到t)和C1(灰度值范围为t+1到L-1)。

两类的概率分别为:

两类的均值分别为:

图像的总均值为:

类间方差公式为:

通过遍历所有可能的阈值t(从0到L-2),计算对应的类间方差 ,使得最大的阈值t即为 Otsu 算法所求的最佳阈值。

三、Otsu 二值化算法在 C# 中的实现

(一)代码示例

using Emgu.CV;
using Emgu.CV.Structure;
using Emgu.CV.CvEnum;

class OtsuBinarization
{
    public static Image<Gray, byte> OtsuBinarize(Image<Gray, byte> grayImage)
    {
        // 获取图像的宽度和高度
        int width = grayImage.Width;
        int height = grayImage.Height;

        // 计算灰度直方图
        int[] histogram = new int[256];
        for (int y = 0; y < height; y++)
        {
            for (int x = 0; x < width; x++)
            {
                byte pixelValue = grayImage[y, x].Intensity;
                histogram[pixelValue]++;
            }
        }

        // 总像素数量
        int totalPixels = width * height;

        // 初始化类间方差和最佳阈值
        double maxVariance = 0;
        int optimalThreshold = 0;

        // 计算类间方差并寻找最佳阈值
        double sum = 0;
        double sumB = 0;
        double wB = 0;
        double wF = 0;
        for (int t = 0; t < 256; t++)
        {
            sum += t * histogram[t];
        }

        for (int t = 0; t < 256; t++)
        {
            wB += histogram[t];
            if (wB == 0)
            {
                continue;
            }
            wF = totalPixels - wB;
            if (wF == 0)
            {
                break;
            }
            sumB += t * histogram[t];
            double meanB = sumB / wB;
            double meanF = (sum - sumB) / wF;
            double variance = wB * wF * (meanB - meanF) * (meanB - meanF);
            if (variance > maxVariance)
            {
                maxVariance = variance;
                optimalThreshold = t;
            }
        }

        // 根据最佳阈值进行二值化
        Image<Gray, byte> binaryImage = new Image<Gray, byte>(width, height);
        for (int y = 0; y < height; y++)
        {
            for (int x = 0; x < width; x++)
            {
                byte pixelValue = grayImage[y, x].Intensity;
                if (pixelValue <= optimalThreshold)
                {
                    binaryImage[y, x] = new Gray(0);
                }
                else
                {
                    binaryImage[y, x] = new Gray(255);
                }
            }
        }

        return binaryImage;
    }
}

(二)代码分析

  1. 首先获取输入灰度图像的宽度和高度,用于后续遍历图像像素和计算相关参数。
  2. 构建一个长度为 256 的数组 histogram 来存储灰度直方图。通过两层循环遍历图像的每个像素,将像素的灰度值作为索引,增加对应 histogram 数组元素的值,从而统计每个灰度级的像素数量。
  3. 计算图像的总像素数量 totalPixels,用于后续计算各类的概率。
  4. 初始化类间方差 maxVariance 为 0,最佳阈值 optimalThreshold 为 0。同时初始化一些用于计算类间方差的变量,如 sum(用于计算图像总灰度值之和)、sumB(背景类灰度值之和)、wB(背景类像素数量占比)、wF(前景类像素数量占比)。
  5. 计算图像总灰度值之和 sum,通过遍历灰度直方图,将每个灰度值乘以其对应的像素数量并累加。
  6. 主循环遍历所有可能的阈值 t(从 0 到 255)。在循环内:
    • 首先计算背景类像素数量占比 wB 和前景类像素数量占比 wF
    • 计算背景类灰度值之和 sumB,并进一步计算背景类均值 meanB 和前景类均值 meanF
    • 根据类间方差公式计算当前阈值 t 下的类间方差 variance
    • 如果当前方差大于已记录的最大方差 maxVariance,则更新最大方差和最佳阈值。
  7. 最后,根据最佳阈值 optimalThreshold 对图像进行二值化。再次遍历图像像素,如果像素灰度值小于等于最佳阈值,则将其在二值化图像中设为黑色(0),否则设为白色(255)。

四、Otsu 二值化算法在 Python 中的实现

(一)代码示例

import cv2
import numpy as np

def otsu_binarization(image):
    # 将图像转换为灰度图像
    gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

    # 计算灰度直方图
    histogram = np.zeros(256)
    height, width = gray_image.shape
    for y in range(height):
        for x in range(width):
            pixel_value = gray_image[y, x]
            histogram[pixel_value] += 1

    # 总像素数量
    total_pixels = height * width

    # 初始化类间方差和最佳阈值
    max_variance = 0
    optimal_threshold = 0

    # 计算类间方差并寻找最佳阈值
    sum_gray = 0
    sum_back = 0
    w_back = 0
    w_fore = 0
    for t in range(256):
        sum_gray += t * histogram[t]

    for t in range(256):
        w_back += histogram[t]
        if w_back == 0:
            continue
        w_fore = total_pixels - w_back
        if w_fore == 0:
            break
        sum_back += t * histogram[t]
        mean_back = sum_back / w_back
        mean_fore = (sum_gray - sum_back) / w_fore
        variance = w_back * w_fore * (mean_back - mean_fore) ** 2
        if variance > max_variance:
            max_variance = variance
            optimal_threshold = t

    # 根据最佳阈值进行二值化
    binary_image = np.zeros((height, width), dtype=np.uint8)
    for y in range(height):
        for x in range(width):
            pixel_value = gray_image[y, x]
            if pixel_value <= optimal_threshold:
                binary_image[y, x] = 0
            else:
                binary_image[y, x] = 255

    return binary_image

(二)代码分析

  1. 利用 cv2.cvtColor 函数将输入图像转换为灰度图像,方便后续处理。
  2. 构建一个长度为 256 的 histogram 数组来存储灰度直方图。通过两层循环遍历灰度图像的每个像素,统计每个灰度级的像素数量。
  3. 计算图像的总像素数量 total_pixels
  4. 初始化类间方差 max_variance 为 0,最佳阈值 optimal_threshold 为 0,以及一些用于计算类间方差的变量 sum_gray(图像总灰度值之和)、sum_back(背景类灰度值之和)、w_back(背景类像素数量占比)、w_fore(前景类像素数量占比)。
  5. 计算图像总灰度值之和 sum_gray,通过遍历灰度直方图,将每个灰度值乘以其对应的像素数量并累加。
  6. 主循环遍历所有可能的阈值 t(从 0 到 255)。在循环内:
    • 计算背景类像素数量占比 w_back 和前景类像素数量占比 w_fore
    • 计算背景类灰度值之和 sum_back,进而计算背景类均值 mean_back 和前景类均值 mean_fore
    • 根据类间方差公式计算当前阈值 t 下的类间方差 variance
    • 如果当前方差大于已记录的最大方差 max_variance,则更新最大方差和最佳阈值。
  7. 最后,根据最佳阈值 optimal_threshold 对图像进行二值化。通过两层循环遍历图像像素,如果像素灰度值小于等于最佳阈值,则在二值化图像中设为黑色(0),否则设为白色(255)。

五、Otsu 二值化算法在 C++ 中的实现

(一)代码示例

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

cv::Mat otsuBinarization(cv::Mat grayImage)
{
    // 计算灰度直方图
    int histogram[256] = { 0 };
    int height = grayImage.rows;
    int width = grayImage.cols;
    for (int y = 0; y < height; y++)
    {
        for (int x = 0; x < width; x++)
        {
            histogram[grayImage.at<uchar>(y, x)]++;
        }
    }

    // 总像素数量
    int totalPixels = height * width;

    // 初始化类间方差和最佳阈值
    double maxVariance = 0;
    int optimalThreshold = 0;

    // 计算类间方差并寻找最佳阈值
    double sum = 0;
    double sumB = 0;
    double wB = 0;
    double wF = 0;
    for (int t = 0; t < 256; t++)
    {
        sum += t * histogram[t];
    }

    for (int t = 0; t < 256; t++)
    {
        wB += histogram[t];
        if (wB == 0)
        {
            continue;
        }
        wF = totalPixels - wB;
        if (wF == 0)
        {
            break;
        }
        sumB += t * histogram[t];
        double meanB = sumB / wB;
        double meanF = (sum - sumB) / wF;
        double variance = wB * wF * (meanB - meanF) * (meanB - meanF);
        if (variance > maxVariance)
        {
            maxVariance = variance;
            optimalThreshold = t;
        }
    }

    // 根据最佳阈值进行二值化
    cv::Mat binaryImage(height, width, CV_8UC1);
    for (int y = 0; y < height; y++)
    {
        for (int x = 0; x < width; x++)
        {
            if (grayImage.at<uchar>(y, x) <= optimalThreshold)
            {
                binaryImage.at<uchar>(y, x) = 0;
            }
            else
            {
                binaryImage.at<uchar>(y, x) = 255;
            }
        }
    }

    return binaryImage;
}

(二)代码分析

  1. 首先计算输入灰度图像的灰度直方图。通过两层循环遍历图像的每个像素,将像素的灰度值作为索引,增加对应 histogram 数组元素的值。
  2. 计算图像的总像素数量 totalPixels
  3. 初始化类间方差 maxVariance 为 0,最佳阈值 optimalThreshold 为 0,以及一些用于计算类间方差的变量 sum(图像总灰度值之和)、sumB(背景类灰度值之和)、wB(背景类像素数量占比)、wF(前景类像素数量占比)。
  4. 计算图像总灰度值之和 sum,通过遍历灰度直方图,将每个灰度值乘以其对应的像素数量并累加。
  5. 主循环遍历所有可能的阈值 t(从 0 到 255)。在循环内:
    • 计算背景类像素数量占比 wB 和前景类像素数量占比 wF
    • 计算背景类灰度值之和 sumB,并进一步计算背景类均值 meanB 和前景类均值 meanF
    • 根据类间方差公式计算当前阈值 t 下的类间方差 variance
    • 如果当前方差大于已记录的最大方差 maxVariance,则更新最大方差和最佳阈值。
  6. 最后,根据最佳阈值 optimalThreshold 对图像进行二值化。通过两层循环遍历图像像素,如果像素灰度值小于等于最佳阈值,则在二值化图像中设为黑色(0),否则设为白色(255)。

六、Otsu 二值化算法的应用

(一)图像分割

在图像分割中,Otsu 二值化算法可用于将目标物体从背景中分离出来。例如在医学图像中,将病变组织与正常组织分离,或者在工业检测中,将产品与背景区分开来。通过自动确定阈值,能够适应不同图像的光照、对比度等变化,提高分割的准确性和稳定性。

(二)目标检测

在目标检测任务中,二值化后的图像可以突出目标物体的轮廓,便于后续的特征提取与目标识别。例如在人脸识别系统中,先对图像进行 Otsu 二值化,然后提取人脸的边缘特征,与数据库中的模板进行匹配,从而实现人脸检测与识别。

(三)字符识别

在光学字符识别(OCR)领域,Otsu 二值化发挥着极为关键的作用。无论是印刷体文字还是手写体文字的识别,都需要将文字从复杂的背景图像中清晰地分离出来,以提取文字的笔画、结构等特征信息。通过对包含文字的图像进行 Otsu 二值化处理,可以有效地将文字像素与背景像素区分开来,得到仅包含黑白两种像素值的二值图像。在这种二值图像中,文字部分通常呈现为前景(白色像素),背景则被转换为黑色像素,从而为后续的字符分割、特征提取以及分类识别等操作提供了便利且高质量的图像基础。例如,在文档数字化处理过程中,大量的纸质文档需要被扫描并转换为可编辑的电子文本,Otsu 二值化能够快速准确地处理扫描图像中的文字部分,极大地提高了 OCR 系统的识别准确率和效率。

(四)交通监控与智能驾驶

在交通监控系统中,Otsu 二值化算法被广泛应用于车辆检测、车牌识别等任务。对于交通场景图像,通过二值化可以突出车辆的轮廓、形状以及车牌区域等关键信息。在车辆检测方面,能够快速确定图像中车辆的位置和大致范围,为交通流量统计、违章行为监测等提供基础数据支持。而在车牌识别中,二值化后的车牌图像更易于进行字符分割和识别,有助于实现高效准确的车牌号码读取,这对于智能交通管理系统的运行至关重要。在智能驾驶领域,Otsu 二值化算法也可用于对道路图像进行预处理,例如识别道路标识、区分道路与周围环境等,为自动驾驶车辆的决策和导航提供重要的视觉信息依据,保障自动驾驶过程的安全性和可靠性。

(五)工业视觉检测

工业生产线上,产品的质量检测往往依赖于机器视觉技术,Otsu 二值化算法是其中常用的图像处理手段之一。在零部件的表面缺陷检测中,如金属制品表面的划痕、裂纹检测,或者电子元器件的外观瑕疵检测等任务中,通过对采集到的产品图像进行二值化处理,可以凸显出缺陷部分与正常产品表面的差异。由于缺陷区域通常在灰度值或纹理特征上与正常区域有所不同,Otsu 二值化能够根据图像自身的灰度分布自动确定合适的阈值,将缺陷部分清晰地分离出来,便于后续通过形态学分析、特征提取等方法进一步判断缺陷的类型、大小和位置等信息,从而实现对产品质量的快速、准确检测,有效提高工业生产的自动化程度和产品质量控制水平。

(六)生物特征识别

除了人脸识别,在其他生物特征识别领域如指纹识别、虹膜识别等方面,Otsu 二值化也有着重要的应用。在指纹识别中,指纹图像经过二值化后,指纹的纹路细节更加突出,便于提取指纹的特征点,如端点、分叉点等,这些特征点构成了指纹的独特标识,用于与数据库中的指纹模板进行匹配识别。对于虹膜识别,二值化可以增强虹膜图像的对比度,突出虹膜的纹理结构,有助于提取虹膜的纹理特征,进而实现高精度的身份验证。通过 Otsu 二值化算法对生物特征图像进行预处理,能够提高生物特征提取的准确性和稳定性,提升整个生物特征识别系统的性能和可靠性,在安防监控、门禁系统等众多领域发挥着保障安全和身份识别的重要作用。

(七)遥感图像分析

在遥感图像处理与分析中,Otsu 二值化算法可用于土地利用分类、目标物提取等任务。例如,在对卫星遥感图像进行分析时,通过二值化可以将不同地物类型(如水体、植被、建筑用地等)进行初步分离。由于不同地物在遥感图像中的灰度特征存在差异,Otsu 二值化能够自动确定合适的阈值,将图像中的像素划分为不同类别,从而为进一步的精确分类和信息提取提供基础。在目标物提取方面,比如从遥感图像中提取特定的建筑物、道路等目标,二值化后的图像可以简化目标物的轮廓和区域,便于采用形态学操作、区域生长等方法进行目标物的精确提取和分析,为城市规划、资源监测、环境评估等领域提供有力的技术支持和数据依据。

综上所述,Otsu 二值化算法以其自动确定阈值的优势,在众多计算机视觉和图像处理领域得到了广泛而深入的应用,为提高各种图像处理任务的效率和准确性发挥着不可或缺的作用。随着技术的不断发展,其在新兴领域和复杂应用场景中的潜力也将不断被挖掘和拓展。

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

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

相关文章

uniApp使用腾讯地图提示未添加maps模块

uniApp使用腾讯地图&#xff0c;打包提示未添加maps模块解决方案 这是报错信息&#xff0c;在标准基座运行的时候是没问题的&#xff0c;但是打包后会提示未添加&#xff0c;可以通过在mainfest里面把地图插件上腾讯地图的key更换高德地图的key&#xff0c;定位服务可以继续用腾…

Deepin/Linux clash TUN模式不起作用,因网关导致的问题的解决方案。

网关导致的问题的解决方案 查看路由 ip route寻找默认路由 默认路由应当为Mihomo default dev Mihomo scope link 如果不是&#xff0c;则 sudo ip route add default dev Mihomo在clash TUN开关状态发生变化时&#xff0c;Mihomo网卡会消失&#xff0c;所以提示找不到网卡…

scala中正则表达式的使用

正则表达式&#xff1a; 基本概念 在 Scala 中&#xff0c;正则表达式是用于处理文本模式匹配的强大工具。它通过java.util.regex.Pattern和java.util.regex.Matcher这两个 Java 类来实现&#xff08;因为 Scala 运行在 Java 虚拟机上&#xff0c;可以无缝使用 Java 类库&…

apache应用(客户机地址限制、用户授权限制、日志分割、AWStats日志分析)

目录 一、 客户机地址限制 二、 用户授权限制 三、 日志分割 使用rotatelogs分割工具 使用第三方工具cronolog 四、 AWStats日志分析 具体的apache软件安装可以阅读我之前的文章apache安装https://blog.csdn.net/m0_68472908/article/details/139348739?spm1001.2014.300…

护士资格实践题库(含解析)

1.患者女&#xff0c;30岁。诊断类风湿关节炎入院&#xff0c;经使用药物治疗后患者关节疼痛减轻&#xff0c;但出现体重增加、满月脸、向心性肥胖。提示存在何种药物的副作用&#xff08; &#xff09; A.泼尼松 B.环磷酰胺 C.硫唑嘌呤 D.吲哚美辛 E.阿司匹林 【答案】…

网络安全概论——防火墙原理与设计

一、防火墙概述 防火墙是一种装置&#xff0c;它是由软件/硬件设备组合而成&#xff0c;通常处于企业的内部局域网与 Internet 之间&#xff0c;限制 Internet 用户对内部网络的访问以及管理内部用户访问 Internet 的权限。换言之&#xff0c;一个防火墙在一个被认为是安全和可…

接口测试-Fidder及jmeter使用

一、接口测试的基础 1.接口的含义 也叫做API&#xff0c;是一组定义、程序及协议的集合&#xff0c;提供访问一组例程的能力&#xff0c;无需访问源码获理解内部工作细节 2.接口的分类 代码内部的接口&#xff0c;程序模块间的接口&#xff0c;对于程序接口测试&#xff0c;需…

postman设置cookie

postman发送请求的时候&#xff0c;如何顺带cookie? 示例&#xff1a; Cookie: locale_areazh; contryzh_cn;

Java-31 深入浅出 Spring - IoC 基础 启动IoC XML与注解结合的方式 配置改造 applicationContext.xml

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 大数据篇正在更新&#xff01;https://blog.csdn.net/w776341482/category_12713819.html 目前已经更新到了&#xff1a; MyBatis&#xff…

jmeter连接mysql

查询mysql数据库版本 SELECT VERSION(); 下载jmeter mysql 驱动jar包&#xff0c;版本低于mysql版本&#xff0c;放在jmeter的lib 路径下 MySQL :: Download MySQL Connector/J (Archived Versions) 添加JDBC Connection Configuration 填写 variable name 及数据库信息 注意…

计算机网络基础(2):网络安全/ 网络通信介质

1. 网络安全威胁 网络安全&#xff1a;目的就是要让网络入侵者进不了网络系统&#xff0c;及时强行攻入网络&#xff0c;也拿不走信息&#xff0c;改不了数据&#xff0c;看不懂信息。 事发后能审查追踪到破坏者&#xff0c;让破坏者跑不掉。 网络威胁来自多方面&#xff1a…

数据分析实战—IMDB电影数据分析

1.实战内容 1.加载数据到movies_df&#xff0c;输出前5行&#xff0c;输出movies_df.info(),movies_df.describe() # &#xff08;1&#xff09;加载数据集&#xff0c;输出前5行 #导入库 import pandas as pd import numpy as np import matplotlib import matplotlib.pyplo…

windwos defender实现白名单效果(除了指定应用或端口其它一律禁止)禁止服务器上网

一、应用场景说明 当我们的一台windows服务器中毒&#xff0c;变成别人肉鸡&#xff0c;不断向外请示非法网站或攻击其它服务器。 要彻底清除相关木马或病毒往往需要的时间比较长&#xff0c;比较有效的方法是禁止服务器主动向外发包除了网站端口和远程程序除外。 其实这就是一…

线性代数基础与应用:基底 (Basis) 与现金流及单期贷款模型(中英双语)

具体请参考&#xff1a;https://web.stanford.edu/~boyd/vmls/ 下面的例子来源于这本书。 线性代数基础与应用&#xff1a;基底 (Basis) 与现金流及单期贷款模型 在线性代数中&#xff0c;基底&#xff08;Basis&#xff09;是一个重要的概念&#xff0c;广泛应用于信号处理、…

微信小程序的消息头增加的字段不能有下滑线,字段大写字母自动转换消息字母

微信小程序的消息头增加的字段不能有下滑线&#xff0c;字段大写字母自动转换消息字母。这个是微信小程序的坑。 正式环境&#xff1a; 微信小程序的消息头增加了一个字段device_id,结果node.js打印出来的字段没有该字段。 [2024-12-20T09:45:54.476] [DEBUG] app - ctx.head…

【Java项目】基于SpringBoot的【旅游管理系统 】

【Java项目】基于SpringBoot的【旅游管理系统 】 技术简介&#xff1a;本系统使用JAVA语言开发&#xff0c;采用B/S架构、Spring Boot框架、MYSQL数据库进行开发设计。 系统简介&#xff1a;&#xff08;1&#xff09;管理员功能&#xff1a;可以管理个人中心、用户管理、景区分…

游戏引擎学习第55天

仓库: https://gitee.com/mrxiao_com/2d_game 介绍 今天的主题是让世界存储真正实现稀疏化&#xff0c;即便当前效率可能并不高。我们计划花一些时间处理这个问题&#xff0c;并探讨相关的成本。稀疏化世界存储是接下来的重要步骤&#xff0c;为此需要逐步实施。 修复 SetCa…

每日十题八股-2024年12月19日

1.Bean注入和xml注入最终得到了相同的效果&#xff0c;它们在底层是怎样做的&#xff1f; 2.Spring给我们提供了很多扩展点&#xff0c;这些有了解吗&#xff1f; 3.MVC分层介绍一下&#xff1f; 4.了解SpringMVC的处理流程吗&#xff1f; 5.Handlermapping 和 handleradapter有…

Linux限制root 用户的远程登录(安全要求)

前言&#xff1a;现在基本用户主机都不允许使用root来操作&#xff0c;所以本文通过创建新用户&#xff0c;并限制root用户的ssh来解决这个问题 1. 创建新账户 aingo 首先&#xff0c;使用 root 账户登录系统。 sudo useradd aingo设置 aingo 账户密码&#xff1a; sudo pa…

mysql的事务和存储引擎+备份

mysql的事务和存储引擎备份 一. mysql的事务1.1 mysgl支持事务四种隔离级别1.2 事务控制语句1.3 行锁和死锁1.3.1 行锁1.3.2 死锁1.3.3 如何避免死锁的发生 二. msyql的备份和还原以及日志管理2.1 数据库备份的分类2.2 备份策略2.2.1 物理冷备份&#xff08;全量&#xff09;2.…