otsu算法详细推导、实现及Multi Level OTSU算法实现

article/2025/8/18 23:06:49

otsu算法详细推导、实现及Multi Level OTSU算法实现

微信公众号:幼儿园的学霸

目录

文章目录

  • otsu算法详细推导、实现及Multi Level OTSU算法实现
  • 目录
  • 简介
  • 推导及实现
    • 常规推导
    • 算法步骤及实现
      • 步骤
      • 实现
  • 从概率的角度解释
    • 推导
    • 实现
  • 扩展-MultiLevel OTSU
  • 延伸思考
  • 算法评价
  • 参考资料

简介

OTSU算法也称最大类间差法,有时也称之为大津算法,由大津于1979年提出,被认为是图像分割中阈值选取的最佳算法,计算简单,不受图像亮度和对比度的影响,因此在数字图像处理上得到了广泛的应用。它是按图像的灰度分布特性,将图像分成背景(background)和目标(object)两部分。考虑到方差是灰度分布均匀性的一种度量,理想情况下,对于同一类,其类内方差应该是很小的,同时背景和目标之间的类间方差越大,说明构成图像的两部分的差别越大,当部分目标错分为背景或部分背景错分为目标都会导致两部分差别变小。因此,使类间方差最大的分割意味着错分概率最小。

该方法的基本思想是根据选取的阈值将图像分为目标和背景两个部分,计算该灰度值下的类间方差值,将类间方差最大时对应的阈值/灰度值作为最佳阈值。

推导及实现

常规推导

设图像尺寸为MxN,其二值化的最佳阈值为T,该阈值将图像分为背景和目标2类别。其中属于背景的像素点数量为N0,属于目标的像素点数量为N1;背景像素点数占整幅图像的比例为 ω 0 \omega_0 ω0,其灰度均值为 μ 0 \mu_0 μ0;目标像素点数占整幅图像的比例为 ω 1 \omega_1 ω1,其灰度均值为 μ 1 \mu_1 μ1;整幅图像的灰度均值为 μ \mu μ,则:
ω 0 = N 0 M ∗ N (1) \omega_{0} = \frac{N_0}{M*N} \tag{1} ω0=MNN0(1)

ω 1 = N 1 M ∗ N (2) \omega_{1} = \frac{N_1}{M*N} \tag{2} ω1=MNN1(2)

N 0 + N 1 = M ∗ N (3) N_0 + N_1 = M*N \tag{3} N0+N1=MN(3)
由上面公式(1)(2)(3)得:
ω 0 + ω 1 = 1 (4) \omega_{0} + \omega_{1} = 1 \tag{4} ω0+ω1=1(4)

μ = μ 0 ∗ N 0 + μ 1 ∗ N 1 M ∗ N = μ 0 ω 0 + μ 1 ω 1 (5) \begin{aligned} \mu &= \frac{\mu_0*N_0 + \mu_1*N_1}{M*N} \\ &= \mu_0\omega_0 + \mu_1\omega_1 \tag{5} \end{aligned} μ=MNμ0N0+μ1N1=μ0ω0+μ1ω1(5)
在论文中,类内方差(Within-class variance)公式如下:

σ W 2 = ω 0 σ 0 2 + ω 1 σ 1 2 (6) \sigma_{W}^{2}=\omega_{0} \sigma_{0}^{2}+\omega_{1} \sigma_{1}^{2} \tag{6} σW2=ω0σ02+ω1σ12(6)

从上式可以看出,类内方差指的是两类像素的方差的加权和,这里权指的是该类像素点数量占整个图像像素点数量的比值

类间方差(Between-class variance)的公式如下:

σ B 2 = ω 0 ( μ 0 − μ T ) 2 + ω 1 ( μ 1 − μ T ) 2 (7) \sigma_{B}^{2}=\omega_{0}(\mu_{0}-\mu_{T})^{2}+\omega_{1}(\mu_{1}-\mu_{T})^{2} \tag{7} σB2=ω0(μ0μT)2+ω1(μ1μT)2(7)
可等价为:

σ B 2 = ω 0 ω 1 ( μ 0 − μ 1 ) 2 (8) \sigma_{B}^{2}=\omega_{0} \omega_{1}(\mu_{0}-\mu_{1})^{2} \tag{8} σB2=ω0ω1(μ0μ1)2(8)
其推导过程如下:

σ B 2 = ω 0 ( μ 0 − μ ) 2 + ω 1 ( μ 1 − μ ) 2 = ω 0 ( μ 0 − ( ω 0 μ 0 + ω 1 μ 1 ) ) 2 + ω 1 ( μ 1 − ( ω 0 μ 0 + ω 1 μ 1 ) ) 2 = ω 0 ( μ 0 − ω 0 μ 0 − ω 1 μ 1 ) 2 + ω 1 ( μ 1 − ω 0 μ 0 − ω 1 μ 1 ) 2 = ω 0 ( ( 1 − ω 0 ) μ 0 − ω 1 μ 1 ) 2 + ω 1 ( ( 1 − ω 1 ) μ 1 − ω 0 μ 0 ) 2 = ω 0 ( ω 1 μ 0 − ω 1 μ 1 ) 2 + ω 1 ( ω 0 μ 1 − ω 0 μ 0 ) 2 = ω 0 ( ω 1 ( μ 0 − μ 1 ) ) 2 + ω 1 ( ω 0 ( μ 1 − μ 0 ) ) 2 = ω 0 ω 1 2 ( μ 0 − μ 1 ) 2 + ω 1 ω 0 2 ( μ 1 − μ 0 ) 2 = ( ω 0 + ω 1 ) ω 0 ω 1 ( μ 0 − μ 1 ) 2 = ω 0 ω 1 ( μ 0 − μ 1 ) 2 (9) \begin{aligned} \sigma_{B}^{2} &= \omega_{0}(\mu_{0}-\mu)^{2}+\omega_{1}(\mu_{1}-\mu)^{2} \\ &=\omega_{0}(\mu_{0}-(\omega_{0} \mu_{0}+\omega_{1} \mu_{1}))^{2}+\omega_{1}(\mu_{1}-(\omega_{0} \mu_{0}+\omega_{1} \mu_{1}))^{2} \\ &= \omega_{0}(\mu_{0}-\omega_{0} \mu_{0}-\omega_{1} \mu_{1})^{2}+\omega_{1}(\mu_{1}-\omega_{0} \mu_{0}-\omega_{1} \mu_{1})^{2} \\ &= \omega_{0}((1-\omega_{0}) \mu_{0}-\omega_{1} \mu_{1})^{2}+\omega_{1}((1-\omega_{1}) \mu_{1}-\omega_{0} \mu_{0})^{2} \\ &= \omega_{0}(\omega_{1} \mu_{0}-\omega_{1} \mu_{1})^{2}+\omega_{1}(\omega_{0} \mu_{1}-\omega_{0} \mu_{0})^{2} \\ &= \omega_{0}(\omega_{1}(\mu_{0}-\mu_{1}))^{2}+\omega_{1}(\omega_{0}(\mu_{1}-\mu_{0}))^{2} \\ &= \omega_{0} \omega_{1}^{2}(\mu_{0}-\mu_{1})^{2}+\omega_{1} \omega_{0}^{2}(\mu_{1}-\mu_{0})^{2} \\ &= (\omega_{0}+\omega_{1}) \omega_{0} \omega_{1}(\mu_{0}-\mu_{1})^{2} \\ &= \omega_{0} \omega_{1}(\mu_{0}-\mu_{1})^{2} \tag{9} \end{aligned} σB2=ω0(μ0μ)2+ω1(μ1μ)2=ω0(μ0(ω0μ0+ω1μ1))2+ω1(μ1(ω0μ0+ω1μ1))2=ω0(μ0ω0μ0ω1μ1)2+ω1(μ1ω0μ0ω1μ1)2=ω0((1ω0)μ0ω1μ1)2+ω1((1ω1)μ1ω0μ0)2=ω0(ω1μ0ω1μ1)2+ω1(ω0μ1ω0μ0)2=ω0(ω1(μ0μ1))2+ω1(ω0(μ1μ0))2=ω0ω12(μ0μ1)2+ω1ω02(μ1μ0)2=(ω0+ω1)ω0ω1(μ0μ1)2=ω0ω1(μ0μ1)2(9)
理想情况下,对于同一类,其类内方差应该是很小的,同时背景和目标之间的类间方差越大,说明构成图像的两部分的差别越大。因此采用遍历的方法得到使类间方差最大的阈值T即为所求。

大津法的形象理解:对于直方图有两个峰值的图像,大津法求得的T近似等于两个峰值之间的低谷

算法步骤及实现

步骤

按照上面的公式,可以初步确定一个容易实现的算法过程:
1.根据图像的尺寸确定图像总的像素点数量MxN
2.遍历图像灰度级0-255中的每一个灰度值L,
假设L将图像分为背景(Background)和目标(Object)两部分
2.1 求解背景的像素点数量及像素的灰度值和 N 0 , S u m 0 N_0,Sum_0 N0,Sum0;目标的像素点数量及像素的灰度值和 N 1 , S u m 1 N_1,Sum_1 N1,Sum1;
2.2 根据上面公式(1)(2)求解两类像素点各自占图像的比例 ω 0 , ω 1 \omega_{0},\omega_{1} ω0,ω1;
根据下面的公式计算两类的灰度均值 μ 0 , μ 1 \mu_0,\mu_1 μ0,μ1:

μ 0 = S u m 0 N 0 μ 1 = S u m 1 N 1 \mu_0 = \frac{Sum_0}{N_0} \\ \mu_1 = \frac{Sum_1}{N_1} μ0=N0Sum0μ1=N1Sum1

2.3 根据公式(8)计算类间方差
3.在上面遍历过程中找到使类间方差最大的灰度值L即为所求

上面步骤存在很大的优化空间,仅为说明算法过程

实现

实现代码如下:

//
// Created by liheng on 11/22/20.
//#include <iostream>
#include <chrono>
#include <opencv2/opencv.hpp>/*!* 大津法求解二值化分割阈值* @param image 输入图像.CV_8UC1* @return >0 大津法求解的分割阈值* @author liheng*/
int ThresholdOTSU(const cv::Mat &image)
{CV_Assert( image.type() == CV_8UC1 );int thresh = -1;int pixNumber = image.cols * image.rows;//图像总像素点int pixCount[256] = { 0 }; //每个灰度值所占像素个数.灰度级为0-255.//1°统计每个灰度级中像素的个数.本质:灰度直方图{//for (int i=0; i<image.rows; ++i)//{//    for (int j=0; j<image.cols; ++j)//        pixCount[image.at<unsigned char>(i,j)]++;//}cv::Size size(image.size());if( image.isContinuous() ){size.width *= size.height;size.height = 1;}for(int i=0; i<size.height; ++i ){const uchar* sdata = image.ptr(i);for( int j = 0; j < size.width; ++j )pixCount[sdata[j]] += 1;}}//2°穷举0-255各灰度,求解使得类间方差最大的灰度值kconst auto PpixNum = 1.0f/image.rows*image.cols;//像素总数float gmax = -FLT_MAX;for(int k=0;k<256;++k){//背景及前景的像素点数量及和(用于求均值)int backNum = 0;float backSum = 0;int objectNum = 0;float objectSum = 0;//遍历区分背景和前景//for(int i=0;i<256;++i)//{//    if( i<=k )//背景,此处为i<=k,如果为i<k呢?//    {//        backNum +=pixCount[i];//        backSum += i*pixCount[i];//    }//    else//前景//    {//        objectNum += pixCount[i];//        objectSum += i*pixCount[i];//    }//}//上面修改为该形式,能够减少for循环中的判断//阈值k将像素分为背景和前景2部分for(int i=0;i<=k;++i)//背景,此处为i<=k,如果为i<k呢?{backNum +=pixCount[i];backSum += i*pixCount[i];}for(int i=k+1;i<256;++i)//前景{objectNum += pixCount[i];objectSum += i*pixCount[i];}//求解类间方差const auto w0 = backNum*PpixNum;const auto w1 = objectNum*PpixNum;//考虑到PpixNum为定值,上面2行可修改为下面形式,但结果可能与opencv结果存在稍许不同!!!//const auto w0 = backNum;//const auto w1 = objectNum;const float u0 = backSum*1.0f/(backNum+FLT_EPSILON);const float u1 = objectSum*1.0f/(objectNum+FLT_EPSILON);const float sigma = w0*w1*std::pow((u0-u1),2);//w0*w1*(u0-u1)^2if( sigma>=gmax ){thresh = k;gmax = sigma;}}//利用概率学相关内容,考虑将上面的双重循环,修改为单层循环return thresh;
}void ShowHist(const cv::Mat& image,cv::Mat& hist_img,int scale=2)
{int bins = 256;int hist_size[] = {bins};float range[] = { 0, 256 };const float* ranges[] = { range};cv::MatND hist;int channels[] = {0};cv::calcHist( &image, 1, channels, cv::Mat(), // do not use maskhist, 1, hist_size, ranges,true, // the histogram is uniformfalse );double max_val;cv::minMaxLoc(hist, nullptr, &max_val, nullptr, nullptr);const int hist_height=image.rows;hist_img = cv::Mat::zeros(hist_height,bins*scale, CV_8UC3);for(int i=0;i<bins;i++){float bin_val = hist.at<float>(i);int intensity = cvRound(bin_val*hist_height/max_val);  //要绘制的高度cv::rectangle(hist_img,cv::Point(i*scale,hist_height-1),cv::Point((i+1)*scale - 1, hist_height - intensity),CV_RGB(255,255,255));}//cv::copyMakeBorder(hist_img,hist_img,10,10,10,10,cv::BORDER_CONSTANT,cv::Scalar(0,0,0));
}int main()
{cv::Mat image = cv::imread("./input.bmp",cv::IMREAD_GRAYSCALE);cv::imshow("src",image);//求解直方图cv::Mat hist_img;int scale = 2;ShowHist(image,hist_img);cv::Mat ourdst,cvdst;auto th = ThresholdOTSU(image);cv::threshold(image,ourdst,th,255,cv::THRESH_BINARY);//在直方图上绘制阈值位置cv::line(hist_img,cv::Point(th*scale,0),cv::Point(th*scale,hist_img.rows-1),cv::Scalar(0,0,255),2);auto cvTh = cv::threshold(image,cvdst,0,255,cv::THRESH_OTSU);cv::imshow("hist",hist_img);cv::imshow("ourdst",ourdst);cv::imshow("cvdst",cvdst);cv::waitKey(0);return 0;
}

输入图像如下所示:
输入图像

OTSU阈值分割结果如下(和OpenCV分割结果一致,OpenCV源码分割结果不在展示):
OTSU分割结果

输入图像的灰度直方图和OTSU阈值如下图所示:图像中的红线位置为OTSU算法求解的阈值:
图片灰度值直方图及OTSU阈值位置

可以看到OTSU算法求解的阈值和灰度直方图中的2个双峰均值比较接近。

如果选择两个波峰之间的波谷作为阈值,就能轻松地把这两类像素分开。但是图像的直方图往往是不连续的,有非常多尖峰和抖动,要找到准确的极值点十分困难

从概率的角度解释

在上面的介绍中对OTSU算法的实现思想已经有了一定的认知。接着从概率学的角度继续探讨该算法。

推导

设待分割图像有L个灰度级,灰度值范围为[0,1,2,...,L-2,L-1],其中灰度值为i的像素个数为 n i n_i ni,那么可以得到图像总的像素个数为:
N = n 1 + n 2 + ⋯ + n L − 2 + n L − 1 = ∑ i = 0 L − 1 n i (11) \begin{aligned} N &=n_1+n_2+\cdots+n_{L-2}+n_{L-1} \\ &=\sum_{i=0}^{L-1} n_{i} \end{aligned} \tag{11} N=n1+n2++nL2+nL1=i=0L1ni(11)
则灰度值为i的像素在图像中出现的概率为:
p i = n i N i , p i ≥ 0 , ∑ i = 0 L − 1 p i = 1 (12) p_{i}=\frac{n_i}{N_i}, p_{i} \geq 0, \sum_{i=0}^{L-1} p_{i}=1 \tag{12} pi=Nini,pi0,i=0L1pi=1(12)
假设灰度值(阈值)k将图像中的像素划分为2类: C 0 , C 1 C_0,C_1 C0,C1,其中类 C 0 C_0 C0中像素的灰度级为 [ 0 , 1 , ⋯ , k − 1 , k ] [0,1,\cdots,k-1,k] [0,1,,k1,k],类 C 1 C_1 C1中像素的灰度级为 [ k + 1 , k + 2 , ⋯ , L − 2 , L − 1 ] [k+1,k+2,\cdots,L-2,L-1] [k+1,k+2,,L2,L1]
在图像中,某一类别出现的概率为:
ω 0 = P r ( C 0 ) = ∑ i = 0 k p i = ω ( k ) ω 1 = P r ( C 1 ) = ∑ i = k + 1 L − 1 p i = 1 − ω ( k ) (13) \begin{aligned} \omega_{0} &= P_r(C_{0})=\sum_{i=0}^{k} p_{i}=\omega(k) \\ \omega_{1} &= P_r(C_{1})=\sum_{i=k+1}^{L-1} p_{i}=1-\omega(k) \tag{13} \end{aligned} ω0ω1=Pr(C0)=i=0kpi=ω(k)=Pr(C1)=i=k+1L1pi=1ω(k)(13)
上面公式中,记为 ω ( k ) \omega(k) ω(k)是为了将出现概率和分割阈值k之间以函数的形式联系起来。后述公式是同样的道理。
根据期望公式,图像整体灰度值为:
μ = μ ( L ) = ∑ i = 0 L − 1 i ∗ p i (14) \mu = \mu(L)=\sum_{i=0}^{L-1} i * p_{i} \tag{14} μ=μ(L)=i=0L1ipi(14)
根据条件概率公式,某一类别的平均灰度值为:
μ 0 = ∑ i = 0 k i ∗ P r ( i ∣ C 0 ) = ∑ i = 0 k i ∗ p i ω 0 = ∑ i = 0 k i p i ω ( k ) = 1 ω ( k ) ∑ i = 0 k i p i = μ ( k ) ω ( k ) (15) \begin{aligned} \mu_{0} &=\sum_{i=0}^{k} i * P_r(i \mid C_{0})=\sum_{i=0}^{k} i*\frac{p_i}{\omega_0} \\ &= \sum_{i=0}^{k} \frac{i p_i}{\omega(k)} = \frac{1}{{\omega(k)}} \sum_{i=0}^{k}{i p_i} \\ &= \frac{\mu(k)}{\omega(k)} \tag{15} \end{aligned} μ0=i=0kiPr(iC0)=i=0kiω0pi=i=0kω(k)ipi=ω(k)1i=0kipi=ω(k)μ(k)(15)
同理,
μ 1 = ∑ i = k + 1 L − 1 i ∗ P r ( i ∣ C 1 ) = ∑ i = k + 1 L − 1 i ∗ p i ω 1 = μ − μ ( k ) 1 − ω ( k ) (16) \begin{aligned} \mu_{1} &=\sum_{i=k+1}^{L-1} i*P_r(i \mid C_{1})=\sum_{i=k+1}^{L-1} i*\frac{p_{i}}{\omega_{1}} \\ &=\frac{\mu-\mu(k)}{1-\omega(k)} \end{aligned} \tag{16} μ1=i=k+1L1iPr(iC1)=i=k+1L1iω1pi=1ω(k)μμ(k)(16)
根据上述公式(13)~(16),对于任一分割阈值k,有下面公式成立:
ω 0 μ 0 + ω 1 μ 1 = μ , ω 0 + ω 1 = 1 (17) \omega_{0} \mu_{0}+\omega_{1} \mu_{1}=\mu, \quad \omega_{0}+\omega_{1}=1 \tag{17} ω0μ0+ω1μ1=μ,ω0+ω1=1(17)

除了期望,还可以计算图像的方差和各类别的方差,如下:
σ 2 = ∑ i = 0 L − 1 ( i − μ ) 2 p i σ 0 2 = ∑ i = 0 k ( i − μ 0 ) 2 P r ( i ∣ C 0 ) = ∑ i = 0 k ( i − μ 0 ) 2 p i ω 0 σ 1 2 = ∑ i = k + 1 L − 1 ( i − μ 1 ) 2 P r ( i ∣ C 1 ) = ∑ i = k + 1 L − 1 ( i − μ 1 ) 2 p i ω 1 (18) \begin{gathered} \sigma^{2}=\sum_{i=0}^{L-1}(i-\mu)^{2} p_{i} \\ \sigma_{0}^{2}=\sum_{i=0}^{k}(i-\mu_{0})^{2} P_r(i \mid C_{0})=\sum_{i=0}^{k} \frac{(i-\mu_{0})^{2} p_i} {\omega_{0}} \\ \sigma_{1}^{2}=\sum_{i=k+1}^{L-1}(i-\mu_{1})^{2} P_r(i \mid C_{1})=\sum_{i=k+1}^{L-1}\frac{(i-\mu_{1})^{2} p_{i}} {\omega_{1}} \end{gathered} \tag{18} σ2=i=0L1(iμ)2piσ02=i=0k(iμ0)2Pr(iC0)=i=0kω0(iμ0)2piσ12=i=k+1L1(iμ1)2Pr(iC1)=i=k+1L1ω1(iμ1)2pi(18)

再对公式(8)进行进一步整理:
σ B 2 = ω 0 ω 1 ( μ 0 − μ 1 ) 2 = ω 0 ω 1 ( μ ( k ) ω ( k ) − μ − μ ( k ) 1 − ω ( k ) ) 2 = ω ( k ) ( 1 − ω ( k ) ) ( μ ( k ) ∗ ( 1 − ω ( k ) ) − ω ( k ) ∗ ( μ − μ ( k ) ) ω ( k ) ∗ ( 1 − ω ( k ) ) ) 2 = ω ( k ) ( 1 − ω ( k ) ) ( μ ( k ) − μ ∗ ω ( k ) ω ( k ) ∗ ( 1 − ω ( k ) ) ) 2 = ( μ ( k ) − μ ∗ ω ( k ) ) 2 ω ( k ) ( 1 − ω ( k ) ) (19) \begin{aligned} \sigma_{B}^{2} &= \omega_{0} \omega_{1}(\mu_{0}-\mu_{1})^{2} \\ &= \omega_{0} \omega_{1}\left(\frac{\mu(k)}{\omega(k)} -\frac{\mu-\mu(k)}{1-\omega(k)} \right)^2 \\ &= \omega(k)(1-\omega(k)) \left(\frac{\mu(k)*(1-\omega(k)) - \omega(k)*(\mu-\mu(k)) }{\omega(k)*(1-\omega(k))}\right)^2 \\ &= \omega(k)(1-\omega(k)) \left(\frac{\mu(k) - \mu*\omega(k) }{\omega(k)*(1-\omega(k))}\right)^2 \\ &= \frac{\left( \mu(k) - \mu*\omega(k) \right)^2}{\omega(k)(1-\omega(k))} \end{aligned} \tag{19} σB2=ω0ω1(μ0μ1)2=ω0ω1(ω(k)μ(k)1ω(k)μμ(k))2=ω(k)(1ω(k))(ω(k)(1ω(k))μ(k)(1ω(k))ω(k)(μμ(k)))2=ω(k)(1ω(k))(ω(k)(1ω(k))μ(k)μω(k))2=ω(k)(1ω(k))(μ(k)μω(k))2(19)
因此求解最大类间方差可写作:
σ B 2 ( k ∗ ) = max ⁡ 0 ≤ k < L σ B 2 ( k ) (20) \sigma_{B}^{2}\left(k^{*}\right)=\max _{0 \leq k<L} \sigma_{B}^{2}(k) \tag{20} σB2(k)=0k<LmaxσB2(k)(20)

公式(19)以函数的表达形式展示类间方差和分割阈值k之间的关系. 图像整体的灰度均值和分割阈值是无关的,这样能够进一步优化代码实现,优化后的代码附后。

在论文中还有下列公式成立:
σ W 2 + σ B 2 = σ 2 (21) \sigma_W^2 + \sigma_B^2 = \sigma^2 \tag{21} σW2+σB2=σ2(21)
类间方差和类内方差的和为定值,也即最大化类间差异就是最小化类内差异
下面对公式(20)进行推导
σ W 2 + σ B 2 = ( ω 0 σ 0 2 + ω 1 σ 1 2 ) + ( ω 0 ( μ 0 − μ ) 2 + ω 1 ( μ 1 − μ ) 2 ) = ( ω 0 σ 0 2 + ω 0 ( μ 0 − μ ) 2 ) + ( ω 1 σ 1 2 + ω 1 ( μ 1 − μ ) 2 ) (22) \begin{aligned} \sigma_W^2 + \sigma_B^2 &= \left(\omega_{0} \sigma_{0}^{2}+\omega_{1} \sigma_{1}^{2}\right) + \left(\omega_{0}(\mu_{0}-\mu)^{2}+\omega_{1}(\mu_{1}-\mu)^{2} \right) \\ &= \left(\omega_{0} \sigma_{0}^{2}+ \omega_{0}(\mu_{0}-\mu)^{2} \right) + \left( \omega_{1} \sigma_{1}^{2} +\omega_{1}(\mu_{1}-\mu)^{2} \right) \end{aligned} \tag{22} σW2+σB2=(ω0σ02+ω1σ12)+(ω0(μ0μ)2+ω1(μ1μ)2)=(ω0σ02+ω0(μ0μ)2)+(ω1σ12+ω1(μ1μ)2)(22)

其中:

ω 0 σ 0 2 = ω 0 [ 1 ω 0 ∗ ∑ i = 0 k ( i − μ 0 ) 2 p i ] = ω 0 [ 1 ω 0 ∗ ∑ i = 0 k ( ( i − μ ) + ( μ − μ 0 ) ) 2 p i ] = ω 0 [ 1 ω 0 ∗ ∑ i = 0 k ( ( i − μ ) 2 + 2 ( i − μ ) ( μ − μ 0 ) + ( μ − μ 0 ) 2 ) p i ] = ( ∑ i = 0 k ( i − μ ) 2 ∗ p i ) + ( ∑ i = 0 k 2 ( i − μ ) ( μ − μ 0 ) ∗ p i ) + ( ( μ − μ 0 ) 2 ∗ ∑ i = 0 k p i ) = ( ∑ i = 0 k ( i − μ ) 2 ∗ p i ) + ( 2 ( μ − μ 0 ) ∑ i = 0 k ( i − μ ) p i ) + ( ω 0 ( μ − μ 0 ) 2 ) = ( ∑ i = 0 k ( i − μ ) 2 ∗ p i ) + 2 ( μ − μ 0 ) [ ∑ i = 0 k i p i − μ ∑ i = 0 k p i ] + ( ω 0 ( μ − μ 0 ) 2 ) = ( ∑ i = 0 k ( i − μ ) 2 ∗ p i ) + ( 2 ( μ − μ 0 ) ∑ i = 0 k ( i − μ ) p i ) + ( ω 0 ( μ − μ 0 ) 2 ) = ( ∑ i = 0 k ( i − μ ) 2 ∗ p i ) + 2 ( μ − μ 0 ) [ ∑ i = 0 k i p i − μ ∑ i = 0 k p i ] + ( ω 0 ( μ − μ 0 ) 2 ) = ( ∑ i = 0 k ( i − μ ) 2 ∗ p i ) + 2 ( μ − μ 0 ) ( ω 0 μ 0 − μ ω 0 ) + ( ω 0 ( μ − μ 0 ) 2 ) 根据公式(15),(13)进行替换而来 = ( ∑ i = 0 k ( i − μ ) 2 ∗ p i ) − 2 ω 0 ( μ 0 − μ ) 2 + ( ω 0 ( μ − μ 0 ) 2 ) = ( ∑ i = 0 k ( i − μ ) 2 ∗ p i ) − ω 0 ( μ 0 − μ ) 2 (23) \begin{aligned} \omega_{0} \sigma_{0}^{2} &= \omega_{0}\left[\frac{1}{\omega_{0}} * \sum_{i=0}^{k}\left(i-\mu_{0}\right)^{2} p_{i}\right]=\omega_{0}\left[\frac{1}{\omega_{0}} * \sum_{i=0}^{k}\left(\left(i-\mu\right)+\left(\mu-\mu_{0}\right)\right)^{2} p_{i}\right] \\ &=\omega_{0}\left[\frac{1}{\omega_{0}} * \sum_{i=0}^{k}\left(\left(i-\mu\right)^{2}+2\left(i-\mu\right)\left(\mu-\mu_{0}\right)+\left(\mu-\mu_{0}\right)^{2}\right) p_{i}\right] \\ &= \left(\sum_{i=0}^{k}\left(i-\mu\right)^{2} * p_{i} \right) +\left(\sum_{i=0}^{k} 2(i-\mu)(\mu-\mu_{0}) * p_{i} \right)+ \left((\mu-\mu_{0})^{2} * \sum_{i=0}^{k} p_{i}\right) \\ &= \left(\sum_{i=0}^{k}\left(i-\mu\right)^{2} * p_{i} \right) + \left(2(\mu-\mu_{0}) \sum_{i=0}^{k}(i-\mu) p_{i} \right)+ \left(\omega_{0}(\mu-\mu_{0})^{2}\right)\\ &= \left(\sum_{i=0}^{k}\left(i-\mu\right)^{2} * p_{i} \right) + 2(\mu-\mu_{0})\left[\sum_{i=0}^{k} i p_{i}-\mu \sum_{i=0}^{k} p_{i}\right] + \left(\omega_{0}(\mu-\mu_{0})^{2}\right) \\ &= \left(\sum_{i=0}^{k}\left(i-\mu\right)^{2} * p_{i} \right) + \left(2(\mu-\mu_{0}) \sum_{i=0}^{k}(i-\mu) p_{i} \right)+ \left(\omega_{0}(\mu-\mu_{0})^{2}\right)\\ &= \left(\sum_{i=0}^{k}\left(i-\mu\right)^{2} * p_{i} \right) + 2(\mu-\mu_{0})\left[\sum_{i=0}^{k} i p_{i}-\mu \sum_{i=0}^{k} p_{i}\right] + \left(\omega_{0}(\mu-\mu_{0})^{2}\right) \\ &= \left(\sum_{i=0}^{k}\left(i-\mu\right)^{2} * p_{i} \right) + 2(\mu-\mu_{0})\left(\omega_0\mu_0-\mu \omega_0\right) + \left(\omega_{0}(\mu-\mu_{0})^{2}\right) \text{根据公式(15),(13)进行替换而来} \\ &= \left(\sum_{i=0}^{k}\left(i-\mu\right)^{2} * p_{i} \right) - 2\omega_0(\mu_0-\mu)^2 + \left(\omega_{0}(\mu-\mu_{0})^{2}\right) \\ &= \left(\sum_{i=0}^{k}\left(i-\mu\right)^{2} * p_{i} \right) - \omega_0(\mu_0-\mu)^2 \end{aligned} \tag{23} ω0σ02=ω0[ω01i=0k(iμ0)2pi]=ω0[ω01i=0k((iμ)+(μμ0))2pi]=ω0[ω01i=0k((iμ)2+2(iμ)(μμ0)+(μμ0)2)pi]=(i=0k(iμ)2pi)+(i=0k2(iμ)(μμ0)pi)+((μμ0)2i=0kpi)=(i=0k(iμ)2pi)+(2(μμ0)i=0k(iμ)pi)+(ω0(μμ0)2)=(i=0k(iμ)2pi)+2(μμ0)[i=0kipiμi=0kpi]+(ω0(μμ0)2)=(i=0k(iμ)2pi)+(2(μμ0)i=0k(iμ)pi)+(ω0(μμ0)2)=(i=0k(iμ)2pi)+2(μμ0)[i=0kipiμi=0kpi]+(ω0(μμ0)2)=(i=0k(iμ)2pi)+2(μμ0)(ω0μ0μω0)+(ω0(μμ0)2)根据公式(15),(13)进行替换而来=(i=0k(iμ)2pi)2ω0(μ0μ)2+(ω0(μμ0)2)=(i=0k(iμ)2pi)ω0(μ0μ)2(23)

同样可推导:

ω 1 σ 1 2 = ( ∑ i = k + 1 L − 1 ( i − μ ) 2 ∗ p i ) − ω 1 ( μ 1 − μ ) 2 (24) \begin{aligned} \omega_{1} \sigma_{1}^{2} = \left(\sum_{i=k+1}^{L-1}\left(i-\mu\right)^{2} * p_{i} \right) - \omega_1(\mu_1-\mu)^2 \end{aligned} \tag{24} ω1σ12=(i=k+1L1(iμ)2pi)ω1(μ1μ)2(24)

因此,式(21)可继续展开:
σ W 2 + σ B 2 = ( ω 0 σ 0 2 + ω 0 ( μ 0 − μ ) 2 ) + ( ω 1 σ 1 2 + ω 1 ( μ 1 − μ ) 2 ) = ∑ i = 0 k ( i − μ ) 2 ∗ p i + ∑ i = k + 1 L − 1 ( i − μ ) 2 ∗ p i = ∑ i = 0 L − 1 ( i − μ ) 2 ∗ p i = σ 2 (25) \begin{aligned} \sigma_W^2 + \sigma_B^2 &= \left(\omega_{0} \sigma_{0}^{2}+ \omega_{0}(\mu_{0}-\mu)^{2} \right) + \left( \omega_{1} \sigma_{1}^{2} +\omega_{1}(\mu_{1}-\mu)^{2} \right) \\ &= \sum_{i=0}^{k}\left(i-\mu\right)^{2} * p_{i} + \sum_{i=k+1}^{L-1}\left(i-\mu\right)^{2} * p_{i} \\ &= \sum_{i=0}^{L-1}\left(i-\mu\right)^{2} * p_{i} \\ &= \sigma^2 \end{aligned} \tag{25} σW2+σB2=(ω0σ02+ω0(μ0μ)2)+(ω1σ12+ω1(μ1μ)2)=i=0k(iμ)2pi+i=k+1L1(iμ)2pi=i=0L1(iμ)2pi=σ2(25)

公式得证。

实现

按照公式(19)进行实现的代码如下:

int ThresholdOTSU2(const cv::Mat &image)
{CV_Assert( image.type() == CV_8UC1 );int thresh = -1;int pixNumber = image.cols * image.rows;//图像总像素点float Ppix[256] = { 0 }; //每一个灰度值出现的概率float u = 0;//图像整体均值//公式12,每一灰度值出现的概率{//求各灰度值出现的次数cv::Size size(image.size());if( image.isContinuous() ){size.width *= size.height;size.height = 1;}for(int i=0; i<size.height; ++i ){const uchar* sdata = image.ptr(i);for( int j = 0; j < size.width; ++j )Ppix[sdata[j]] += 1;}//求灰度值出现的概率及整体均值const auto p = 1.0/pixNumber;for(int i=0;i<256;++i){Ppix[i] *= p;u += i*Ppix[i];}}//遍历float wk=0;float uk=0;float gmax = -FLT_MAX;for(int k=0;k<256;++k){//注意此处.没有在每个循环中遍历求0-k的和,// 而是在上一次求解的基础上累加,防止重复计算wk += Ppix[k];//0-k灰度值的像素 出现的概率.Note:包含kuk += k*Ppix[k];const auto temp = uk-u*wk;const auto sigma = temp*temp/(wk*(1-wk)+FLT_EPSILON);if( sigma>=gmax )//考虑对相同的最大方差下的阈值进行存储,然后求这些阈值的均值{thresh = k;gmax = sigma;}}return thresh;
}

扩展-MultiLevel OTSU

在上面的算法中,讨论的是2个类别的分割,针对图像来说就是二值化处理。如果对该问题进行扩展,我们想把图像像素划分为M类,则对应的目标函数可以扩展为:
σ B 2 = ∑ k = 0 M − 1 ω k ( μ k − μ ) 2 \sigma_{B}^{2}=\sum_{k=0}^{M-1} \omega_{k}\left(\mu_{k}-\mu\right)^{2} σB2=k=0M1ωk(μkμ)2

对大津算法的多级推广成为多大津算法(multi Otsu method)
论文: A Fast Algorithm for Multilevel Thresholding. 来自台湾的一篇论文,说明还是比较详细.

我们需要找到 ( t 0 , t 1 , ⋯ , t M − 1 , ∣ t 0 < t 1 < ⋯ < t M − 1 ) (t_0,t_1,\cdots,t_{M-1},\mid t_0< t_1 <\cdots< t_{M-1}) (t0,t1,,tM1,t0<t1<<tM1)使得目标函数(类间方差)取最大值
根据上一节的结论:类间方差和类内方差的和为定值,因此最小化类内方差和最大化类间方差是相同的,因此目标函数可以进行修改如下:
σ W 2 = ∑ k = 0 M − 1 w k μ k 2 \sigma_{W}^{2}=\sum_{k=0}^{M-1} w_{k} \mu_{k}^{2} σW2=k=0M1wkμk2

找出使上公式最小的一系列阈值即可。

此处为我的理解,正好和原论文中结论相反

关于多阈值OTSU算法在上面提到的论文中有比较详细的说明,里面讨论了其算法实现、简化过程,以实现fast的目的.该部分暂时不赘述,看后面的情况,有时间在补上一篇.
此处将按照论文实现的算法贴出,有需要的可以浏览一番.

#include <iostream>
#include <chrono>
#include <opencv2/opencv.hpp>
#include <numeric>
cv::Mat SegImage(const cv::Mat &src,const std::vector<int> &thresholds,const std::vector<cv::Vec3b> &colors);/*!* 多级大津算法* @param image 输入图像.CV_8UC1* @param classes 分类的类别* @param thresholds 分割阈值* @author liheng* @Reference http://github.com/hipersayanX/MultiOtsuThreshold*/
void MultiOtsuThreshold(const cv::Mat &image, unsigned int classes, std::vector<int> &thresholds)
{CV_Assert( image.type() == CV_8UC1 );float maxSum = 0;std::vector<int>(classes-1,0).swap(thresholds);//cal histogram. 求解直方图std::vector<int> histogram(256, 0);{cv::Size size(image.size());if( image.isContinuous() ){size.width *= size.height;size.height = 1;}for(int i=0; i<size.height; ++i ){const uchar* sdata = image.ptr(i);for( int j = 0; j < size.width; ++j )histogram[sdata[j]] += 1;}// Since we use sum tables add one more to avoid unexistent colors.//for (int i = 0; i < histogram.size(); i++)//    histogram[i]++;}//buildTablesstd::vector<float> H(histogram.size() * histogram.size(), 0.f);{// Create cumulative sum tables.std::vector<int> P(histogram.size() + 1);std::vector<int> S(histogram.size() + 1);P[0] = 0;S[0] = 0;int sumP = 0;int sumS = 0;for (int i = 0; i < histogram.size(); i++) {sumP += int(histogram[i]);sumS += int(i * histogram[i]);P[i + 1] = sumP;S[i + 1] = sumS;}// Calculate the between-class variance for the interval u-v//std::vector<float> H(histogram.size() * histogram.size(), 0.f);for (int u = 0; u < histogram.size(); u++){float *hLine = H.data() + u * histogram.size();for (int v = u + 1; v < histogram.size(); v++)hLine[v] = std::pow(S[v]-S[u], 2)*1.0f/(P[v] - P[u]+FLT_EPSILON);}}std::vector<int> index(classes + 1);index[0] = 0;index[index.size() - 1] = histogram.size() - 1;std::function<void(float *maxSum,std::vector<int> *lhthres,const std::vector<float> &H,int u,int vmax,int level,int levels,std::vector<int> *index)> lhOTSU_for_loop =[&](float *maxSum, std::vector<int> *lhthres,const std::vector<float> &H,int u,int vmax,int level,int levels,std::vector<int> *index){int classes = index->size() - 1;for (int i = u; i < vmax; i++){(*index)[level] = i;if (level + 1 >= classes){// Reached the end of the for loop.// Calculate the quadratic sum of al intervals.float sum = 0.;for (int c = 0; c < classes; c++){int u = index->at(c);int v = index->at(c + 1);sum += H[v + u * levels];}if (*maxSum < sum){// Return calculated threshold.*lhthres = std::vector<int>(index->begin()+1,index->begin()+lhthres->size()+1);*maxSum = sum;}}else{// Start a new for loop level, one position after current one.lhOTSU_for_loop(maxSum,lhthres,H,i + 1,vmax + 1,level + 1,levels,index);}}};lhOTSU_for_loop(&maxSum,&thresholds,H,1,histogram.size() - classes + 1,1,histogram.size(), &index);}int main(int argc, char *argv[])
{cv::Mat image = cv::imread("./1.bmp",cv::IMREAD_GRAYSCALE);cv::imshow("src",image);//灰度直方图cv::Mat hist_img;int scale = 2;ShowHist(image,hist_img);//MultiLevel OTSUunsigned int classes = 4;std::vector<cv::Vec3b> colors(classes);colors[0] = cv::Vec3b(0,0,0);colors[1] = cv::Vec3b(255,0,0);colors[2] = cv::Vec3b(0,255,0);colors[3] = cv::Vec3b(0,0,255);std::vector<int> thresholds;MultiOtsuThreshold(image,classes,thresholds);//在直方图上绘制阈值位置for( const auto th:thresholds)cv::line(hist_img,cv::Point(th*scale,0),cv::Point(th*scale,hist_img.rows-1),cv::Scalar(0,0,255),2);for(int i=0;i<thresholds.size();++i)std::cout<<thresholds[i] <<" ";std::cout<<std::endl;cv::Mat thresholded = SegImage(image, thresholds, colors);cv::imshow("4otsu-dst",thresholded);//cv::imwrite("./res.bmp",thresholded);//cv::Mat otsudst;//auto th = ThresholdOTSU(image);//cv::threshold(image,otsudst,th,255,cv::THRESH_BINARY);//cv::imshow("2otsu-dst",otsudst);cv::imshow("hist",hist_img);cv::waitKey(0);return 0;
}cv::Mat SegImage(const cv::Mat &src,const std::vector<int> &thresholds,const std::vector<cv::Vec3b> &colors)
{cv::Mat dst = cv::Mat::zeros(src.size(),CV_8UC3);std::vector<cv::Vec3b> colorTable(256);int j = 0;for (int i = 0; i < colorTable.size(); i++){if (j < thresholds.size() && i >= thresholds[j])j++;colorTable[i] = colors[j];}//    for (int y = 0; y < src.rows; y++)
//    {
//        const uchar *srcLine = src.ptr(y);
//        auto *dstLine = dst.ptr<cv::Vec3b>(y);
//
//        for (int x = 0; x < src.cols; x++)
//            dstLine[x] = colorTable[srcLine[x]];
//    }cv::cvtColor(src,dst,cv::COLOR_GRAY2BGR);cv::LUT(dst,colorTable,dst);return dst;
}

输入图像如下:
在这里插入图片描述

分割结果如下:

MultiLevelOTSU

延伸思考

从OTSU算法的思想、实现过程来看,其虽然是对图像进行分割,但是算法的核心输入为灰度直方图/概率直方图,算法过程也是对概率直方图进行处理。因此我们该算法不仅仅适用于图像分割领域,也可以适用于其他能够求解概率直方图的场合,求解最大类间方差,如点云分类等。

算法评价

  • 应用:是求图像全局阈值的最佳方法,应用不言而喻,适用于大部分需要求图像全局阈值的场合
  • 优点:计算简单快速,不受图像亮度和对比度的影响
  • 缺点:对图像噪声敏感,只能针对单一目标分割,当目标和背景大小比例(面积)悬殊、类间方差函数可能呈现双峰或者多峰,这个时候效果不好
  • 解释:当图像中的目标与背景的面积相差很大时,表现为直方图没有明显的双峰,或者两个峰的大小相差很大,分割效果不佳,或者目标与背景的灰度有较大的重叠时也不能准确的将目标与背景分开。导致这种现象出现的原因是该方法忽略了图像的空间信息,同时该方法将图像的灰度分布作为分割图像的依据,因而对噪声也相当敏感。所以,在实际应用中,总是将其与其他方法结合起来使用。

含有噪声滤波效果对比

参考资料

1.原论文链接
2.otsu阈值分割算法原理_大津法—OTSU算法
3.最大类间方差法(大津法OTSU)
4.大津法(OTSU 最大类间方差法)公式推导

5.大津法(OTSU 最大类间方差法)详细数学推导(公式繁杂,欢迎讨论)

该文最后的推导中公式有处笔误,整体没有问题

6.A Fast Algorithm for Multilevel Thresholding
7.多类别的最大类间方差法(Otsu’s method)



下面的是我的公众号二维码图片,按需关注
图注:幼儿园的学霸


http://chatgpt.dhexx.cn/article/Ngn3KPKO.shtml

相关文章

OTSU算法及其改进算法学习

这篇文章还是来自斯坦福课后作业hw2_3&#xff0c;主要是结合一个例子介绍otsu算法【亦称为大律算法&#xff0c;小日本】及其改进算法。 本文将先介绍老外的题目、解题思路及maltab解答&#xff0c;然后分析otsu算法步骤&#xff0c;末了给出opencv实现。 老外的题目&#xff…

Otsu Thresholding

1. Otsu Thresholding Explained Otsu对image中的所有像素都假定为阈值&#xff0c;然后根据此值将image分为前景物体和背景&#xff1b;遍历所有像素值 计算类内方差&#xff0c;最小的类内方差对应的threshold即为最优阈值&#xff1b; 以6阶灰度图像为例 A 6-level greys…

Otsu算法原理及实现

在图像处理中Otsu方法&#xff0c;是以 Nobuyuki otsu 的名字命名的&#xff08;日本人&#xff0c;大津展之&#xff09;&#xff0c;常用于基于图像分割的聚类。该算法的理论依据是&#xff1a;假定图像包含两类像素&#xff08;前景像素和背景像素&#xff09;&#xff0c;直…

10 Otsu 算法

文章目录 前言一、Otsu 是什么&#xff1f;二、算法实验1.使用第三方库2.不使用第三方库 前言 Otsu 是一种利用图像的灰度特征自动计算二值化阈值的方法&#xff0c;常被称为 Otsu 自动阈值法。 使用 Otsu 方法可以避免主观性和繁琐性的阈值选取操作&#xff0c;并能够在一定…

OTSU(最大类间方差法、大津算法)

OTSU是阈值分割中一种常用的算法&#xff0c;它可以根据图像自动生成最佳分割阈值。OTSU的核心思想是类间方差最大化。 import cv2 import numpy as np from matplotlib import pyplot as pltimage cv2.imread("2.bmp") gray cv2.cvtColor(image, cv2.COLOR_BGR2G…

Bootstrap模态框里 再弹模态框

Bootstrap模态框里 再弹模态框 后端代码点击编辑 按钮 将参数赋值隐藏 input 中 , 便于修改 获取对应id修改模态框详情模态框 后端代码 /*** 财务审核使用详情** param request* param id* return*/RequestMapping(params "getUseDatil")ResponseBodypublic JSONAr…

新增模态框

平时我们在VS中也常常会用到模态框&#xff0c;今天我们就来聊聊模态框&#xff0c;但是我要说的是新增模态框&#xff0c;而不是修改模态框喔。在书写模态框代码时&#xff0c;我们还要引用一个插件: 然后就可以进行对代码进行书写了。 我们先说说模态框插件的用法&#xff0c…

html模态框常见问题,模态框无法弹出的问题

问题起因&#xff1a; 昨晚写到了一个模态框&#xff0c;用到了bootstrap和jquery&#xff0c;依赖的js已经复制到项目中&#xff0c;并在Jsp页面上进行了引用&#xff0c;最初的引用如下&#xff1a; 问题描述&#xff1a; 模态框无法正常弹出&#xff0c;使用浏览器查看资源看…

Vue模态框的封装

一、模态框 1、模态框&#xff1a;若对话框不关闭&#xff0c;不能操作其父窗口 2、非模态框&#xff1a;对话框不关闭&#xff0c;可以操作其窗口 二、Vue组件实现模态框的功能 1、模态框是一个子组件 2、显示和隐藏由父组件决定 3、对话框的标题也是由父组件传递的 4、对话框…

Bootstrap之模态框

前言 模态框&#xff08;Modal&#xff09;是覆盖在父窗体上的子窗体。通常&#xff0c;目的是显示来自一个单独的源的内容&#xff0c;可以在不离开父窗体的情况下有一些互动。子窗体可提供信息、交互等。 用法 您可以切换模态框&#xff08;Modal&#xff09;插件的隐藏内…

php什么是模态框,bootstrap模态框有什么用

Bootstrap Modals(模态框)是使用定制的Jquery 插件创建的。 它可以用来创建模态窗口丰富用户体验&#xff0c;或者为用户添加实用功能。您可以在 Modals(模态框)中使用 Popover(弹出框)和 Tooltip(工具提示插件)。(推荐学习&#xff1a;Bootstrap视频教程) 将通过一些实例和解释…

弹出模态框

想必大家都知道弹出层的重要性&#xff0c;在很多的地方都能用到这个方法&#xff0c;所以说这种是非常的普遍的实用性&#xff0c;在大家编写过程中也是很常见的模态框以及弹出层&#xff0c;基本也是一个概念。 插件不可缺少 1.多窗口模式 层叠置顶 Esc 关闭 为什么是说多窗…

模态框动态赋值

模态框动态赋值&#xff0c;可以有多种方式&#xff1a;1、每次一个个填充&#xff1b;2、直接针对模态框中的ID赋值。。。 今天说下同事犯的错误&#xff0c;大家引以为鉴&#xff1a; 首先如图&#xff1a; 他在点击详情链接时&#xff0c;是能拿到相关参数的&#xff0c;进…

html 自定义模态框,自定义对话框、模态框

致敬iphoneX的小刘海....自定义模态框 body{ text-align: center; } #modalBg{ position: absolute; left: 0; top: 0; background-color: rgba(0,0,0,0.2); width: 100%; height: 100%; margin: auto; display: none; } #modal{ min-width: 30%; background-color: white; bor…

html怎么自动弹出模态框,纯CSS实现带点击模态框外部自动关闭的模态框

在网页中我们经常会用到模态框,一般会用于显示表单或者是提示信息。由于模态框涉及到页面上比较多的交互效果,最简单的交互就是打开以及关闭两个操作,而关闭又会涉及是否需要在打开状态下点击模态框外部能够关闭这样的功能,因为这些交互问题,所以一般都会首先考虑到使用Ja…

模态框拖拽

1、点击弹出层&#xff0c;回弹出模态框&#xff0c;并且显示半透明的遮挡层 2、点击关闭按钮&#xff0c;可以关闭模态框&#xff0c;并且同时关闭灰色半透明遮挡层 3、鼠标放到模态框最上面一行&#xff0c;可以按住鼠标拖拽模态框在页面中移动 4、鼠标松开&#xff0c;可…

模态框案例

模态框 模态框在很多网站是很常见的&#xff0c;比如网易云音乐登录&#xff0c;百度登录等网站。 网易云音乐如图&#xff1a; 让我们来看看代码吧&#xff01;&#xff01;注意&#xff1a;这里为了更好的演示效果&#xff0c;增加了遮挡层。 HTML部分&#xff1a; <!-- …

零基础Bootstrap入门教程(16)--模态框

点此查看 所有教程、项目、源码导航 本文目录 1. 概述2. 模态框样式3. 按钮直接关联模态框4. 通过JS方法打开/关闭模态框5. 调整大小6. 动画效果7. 小结 1. 概述 模态框这个名字有点高雅了&#xff0c;其实就是对话框&#xff0c;用来弹出一个新的界面。 模态框的使用非常的场…

模态框

模态对话框&#xff08;Modal Dialogue Box&#xff0c;又叫做模式对话框&#xff09;&#xff0c;是指在用户想要对对话框以外的应用程序进行操作时&#xff0c;必须首先对该对话框进行响应 摘自百度百科 先放上效果图&#xff1a; 基本功能为点击窗口中间的发光按钮&#xff…

HTML+CSS+JavaScript实现模态框(可拖拽)

前言 模态框是指覆盖在父窗口上的子窗口&#xff0c;但在HTML网页中&#xff0c;并没有父窗口和子窗口的概念。这里是通过可隐藏的遮罩层和一个可隐藏的盒子来实现模态框的效果。 效果演示&#xff1a; 下面开始详细介绍如何实现一个可拖拽的模态框。只对 JS 部分详解&#x…