智能汽车竞赛摄像头处理(3)——动态阈值二值化(大津法)

news2024/9/25 15:26:04

前言

(1)在上一节中,我们学习了对图像的固定二值化处理,可以将原始图像处理成二值化的黑白图像,这里面的本质就是将原来的二维数组进行了处理,处理后的二维数组里的元素都是0和255两个值。

(2)固定阈值二值化的使用是比赛过程中,每一处地方的二值化阈值都是同一个值,而赛道的不同地方以同一个阈值二值化出来的图像可能不合适,甚至不能正常循迹。

(3)为了适应赛道上的不同环境,很多同学会采用动态二值化,即对于采集到的不同图像,通过算法计算出合适的阈值来进行二值化处理,最后的二值化效果可能会比固定阈值化好一些(因为也有不少同学在比赛中采用固定阈值二值化处理,最后拿到了国奖,所以哪个方法更好,还真不好说,主要根据实际情况看自己的作品)。

(4)毋庸置疑,大津法会增加算法处理,处理时间肯定比固定阈值二值化长。

概念

对于图像处理入门,建议大家可以去看这篇文章

详解-OTUS(大津法-最大类间方差)原理及C语言代码实现-CSDN博客

但是,你不想看也没关系,请继续看我后面的内容,你只需要理解到一个点:大津法就是一个对二维数组处理后会得到一个值的算法,而这个值就是我们二值化要的阈值。想要搞清楚大津法原理,请移步上面的文章进行学习,我这里只进行简要介绍。

大津法

        大津法阈值采用最大类间方差的原理,适合于图像灰度分布整体呈现“双峰”的情况。大津法会自动找出一个阈值,使得分割后的两部分类间方差最大。

        最大类间方差是由日本学者大津(Nobuyuki Otsu)于1979年提出,是一种确定图像二值化分割阈值的算法。算法假设图像像素能够根据全局阈值,被分成背景[background]和目标[objects]两部分。然后,计算该最佳阈值来区分这两类像素,使得两类像素区分度最大。

大津法阈值采用最大类间方差的原理,适合于图像灰度分布整体呈现“双峰”的情况。大津法会自动找出一个阈值,使得分割后的两部分类间方差最大。

特性:

  1. 大津法对噪音十分敏感,在处理之前应对图片进行去噪处理。如果图像有存在局部噪声,则会影响大津法的判断
  2. 当目标与背景的面积比例悬殊的时候,类间方差函数可能呈现双峰或者多峰,这个时候 大津法的效果不好

(1)双峰图像,目标和背景面积差距不大,可以很好的判断,如下:

(2)当图像中的目标与背景的面积相差很大时,灰度直方图没有明显的双峰,或者两个峰的大小相差很大,分割效果不佳:

代码实现

一般大津法代码如下:

(你只需要将我的代码复制粘贴到images.c文件中,把这个函数在cpu1.c中调用即可)

images.c

#include "zf_common_headfile.h"

uint8  mt9v03x_image_BandW[MT9V03X_H][MT9V03X_W];

/*begin  大津法比赛   begin*/
//快速大津法二值化 pixelSum = width * height/4;
//-------------------------------------------------------------------------------------------------------------------
//  @brief      快速大津
//  @return     uint8
//  @since      v1.1
//  Sample usage:   OTSU_Threshold = otsuThreshold(mt9v03x_image_dvp[0]);//大津法阈值
//-------------------------------------------------------------------------------------------------------------------
uint8 otsuThreshold_fast(uint8 *image)   //注意计算阈值的一定要是原图像
{
#define GrayScale 256
    int Pixel_Max=0;
    int Pixel_Min=255;
    uint16 width = MT9V03X_W;   //宽100
    uint16 height = MT9V03X_H;  //高80
    int pixelCount[GrayScale];  //各像素GrayScale的个数pixelCount 一维数组
    float pixelPro[GrayScale];  //各像素GrayScale所占百分比pixelPro 一维数组
    int i, j, pixelSum = width * height/4;  //pixelSum是获取总的图像像素个数的1/4,相应下面轮询时高和宽都是以2为单位自增
    uint8 threshold = 0;
//    uint8 last_threshold = 0;
    uint8* data = image;  //指向像素数据的指针

    //清零
    for (i = 0; i < GrayScale; i++)
    {
        pixelCount[i] = 0;
        pixelPro[i] = 0;
    }

    uint32 gray_sum=0;  //每次执行到这会将gray_sum清零
    //统计灰度级中每个像素在整幅图像中的个数
    for (i = 0; i < height; i+=2)   //高
    {
        for (j = 0; j < width; j+=2)    //宽
        {
            pixelCount[(int)data[i * width + j]]++;  //将当前的点的像素值作为计数数组的下标
            gray_sum+=(int)data[i * width + j];       //灰度值总和
            if(data[i * width + j]>Pixel_Max)   Pixel_Max=data[i * width + j];
            if(data[i * width + j]<Pixel_Min)   Pixel_Min=data[i * width + j];
        }
    }

    //计算每个像素值的点在整幅图像中的比例
    for (i = Pixel_Min; i < Pixel_Max; i++)
    {
        pixelPro[i] = (float)pixelCount[i] / pixelSum;
    }

    //遍历灰度级[0,255]
    float w0, w1, u0tmp, u1tmp, u0, u1, u, deltaTmp, deltaMax = 0;

    w0 = w1 = u0tmp = u1tmp = u0 = u1 = u = deltaTmp = 0;
    for (j = Pixel_Min; j < Pixel_Max; j++)
    {

        w0 += pixelPro[j];  //背景部分每个灰度值的像素点所占比例之和   即背景部分的比例
        u0tmp += j * pixelPro[j];  //背景部分 每个灰度值的点的比例 *灰度值

        w1=1-w0;
        u1tmp=gray_sum/pixelSum-u0tmp;

        u0 = u0tmp / w0;              //背景平均灰度
        u1 = u1tmp / w1;              //前景平均灰度
        u = u0tmp + u1tmp;            //全局平均灰度
        deltaTmp = (float)(w0 *w1* (u0 - u1)* (u0 - u1)) ;
        if (deltaTmp > deltaMax)
        {
            deltaMax = deltaTmp;
            threshold = (uint8)j;
        }
        if (deltaTmp < deltaMax)
        {
            break;
        }

    }

    return threshold;
}
/*end  大津法比赛   end*/

/*begin  大津法学习   begin*/
//------------------摄像头参数--------------//
uint8 image_threshold = 46;  //图像阈值 0~255
uint8 dis_image[60][80];

uint8 otsuThreshold(uint8 *image, uint16 width, uint16 height)
{
    #define GrayScale 256
    int pixelCount[GrayScale] = {0};//每个灰度值所占像素个数
    float pixelPro[GrayScale] = {0};//每个灰度值所占总像素比例
    int i,j;
    int Sumpix = width * height;   //总像素点
    uint8 threshold = 0;
    uint8* data = image;  //指向像素数据的指针


    //统计灰度级中每个像素在整幅图像中的个数
    for (i = 0; i < height; i++)
    {
        for (j = 0; j < width; j++)
        {
            pixelCount[(int)data[i * width + j]]++;  //将像素值作为计数数组的下标
          //   pixelCount[(int)image[i][j]]++;    若不用指针用这个
        }
    }
    float u = 0;
    for (i = 0; i < GrayScale; i++)
    {
        pixelPro[i] = (float)pixelCount[i] / Sumpix;   //计算每个像素在整幅图像中的比例
        u += i * pixelPro[i];  //总平均灰度
    }


    float maxVariance=0.0;  //最大类间方差
    float w0 = 0, avgValue  = 0;  //w0 前景比例 ,avgValue 前景平均灰度
    for(i = 0; i < 256; i++)     //每一次循环都是一次完整类间方差计算 (两个for叠加为1个)
    {
        w0 += pixelPro[i];  //假设当前灰度i为阈值, 0~i 灰度像素所占整幅图像的比例即前景比例
        avgValue  += i * pixelPro[i];

        float variance = pow((avgValue/w0 - u), 2) * w0 /(1 - w0);    //类间方差
        if(variance > maxVariance)
        {
            maxVariance = variance;
            threshold = (uint8)i;
        }
    }
    return threshold;
}

/*end  大津法学习   end*/

//图像二值化
//0 - 255
//黑 - 白
void Set_image_towvalues(uint8 value)
{
    uint8 temp_valude;//暂存灰度值
    for(uint8 i = 0;i < MT9V03X_H;i++)//高
    {
      for(uint8 j = 0;j < MT9V03X_W;j++)//宽
      {
          temp_valude = mt9v03x_image[i][j];
          if(temp_valude < value)
          {
              mt9v03x_image_BandW[i][j] = 0;//黑
          }
          else
          {
              mt9v03x_image_BandW[i][j] = 255;//白
          }
      }

    }
}

images.h

#ifndef CODE_IMAGES_H_
#define CODE_IMAGES_H_

extern uint8  mt9v03x_image_BandW[MT9V03X_H][MT9V03X_W];


void Set_image_towvalues(uint8 value);
uint8 otsuThreshold(uint8 *image, uint16 width, uint16 height);
uint8 otsuThreshold_fast(uint8 *image);
#endif /* CODE_IMAGES_H_ */

但是不优化大津法的代码,图像处理时间较长,所以大家又将“大津法”优化成了“快速大津法”,代码也在上面公布了,照我如下调用即可。

cpu1.c

void core1_main(void)
{
    disable_Watchdog();                     // 关闭看门狗
    interrupt_global_enable(0);             // 打开全局中断
    // 此处编写用户代码 例如外设初始化代码等
    mt9v03x_init();//初始化摄像头

    // 此处编写用户代码 例如外设初始化代码等
    cpu_wait_event_ready();                 // 等待所有核心初始化完毕
    while (TRUE)
    {
        // 此处编写需要循环执行的代码
        TFT180_SHOW();
        if(mt9v03x_finish_flag)     //一幅图像完全采集完毕后,再进行图像的显示判断和显示
        {
            //Set_image_towvalues(150); //固定阈值二值化
            BandW_threshold = otsuThreshold_fast(mt9v03x_image[0]);//大津法得到动态阈值BandW_threshold
            Set_image_towvalues(BandW_threshold); //动态阈值二值化得到二维数组mt9v03x_image_BandW


                tft180_displayimage03x(mt9v03x_image_BandW[0],MT9V03X_W,MT9V03X_H);//显示二值化后的图像

            mt9v03x_finish_flag = 0;//图像显示完成后才对标志位清零
        }

        // 此处编写需要循环执行的代码
    }
}

通过调用大津法处理函数,将原始图像进行动态阈值二值化处理,也是会在显示屏上得到黑白图像,固定阈值二值化和动态阈值二值化处理哪个效果好,还得看实际情况。

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

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

相关文章

LeetCode 热题 100 | 链表(中上)

目录 1 141. 环形链表 1.1 哈希表 1.2 快慢指针 2 142. 环形链表 II 2.1 哈希表 2.2 快慢指针 3 21. 合并两个有序链表 4 2. 两数相加 菜鸟做题第三周&#xff0c;语言是 C 1 141. 环形链表 1.1 哈希表 解题思路&#xff1a;遍历链表&#xff0c;在哈希表中…

ROS方向第二次汇报(5)

文章目录 1.本方向内学习内容&#xff1a;1.1.自定义msg&#xff1a;1.1.1.定义msg文件&#xff1a;1.1.2.编辑配置文件&#xff1a; 1.2.自定义srv&#xff1a;1.2.1.定义srv文件&#xff1a;1.2.2.编辑配置文件&#xff1a; 1.3.服务通信案例实现&#xff1a;1.3.1.服务端实现…

HTML+CSS:导航栏组件

效果演示 实现了一个导航栏的动画效果&#xff0c;当用户点击导航栏中的某个选项时&#xff0c;对应的选项卡会向左平移&#xff0c;同时一个小圆圈会出现在选项卡的中心&#xff0c;表示当前选项卡的位置。这个效果可以让用户更加清晰地了解当前页面的位置和内容。 Code <…

正点原子--STM32定时器学习笔记(2)

书接上文&#xff0c;本篇是对基本定时器实验部分进行的总结~ 实验目标&#xff1a;通过TIM6基本定时器定时500ms&#xff0c;让LED0每隔500ms闪烁。 解决思路&#xff1a;使用定时器6&#xff0c;实现500ms产生一次定时器更新中断&#xff0c;在中断里执行“翻转LED0”。 定时…

【leetcode题解C++】98.验证二叉搜索树 and 701.二叉搜索树中的插入操作

98. 验证二叉搜索树 给你一个二叉树的根节点 root &#xff0c;判断其是否是一个有效的二叉搜索树。 有效 二叉搜索树定义如下&#xff1a; 节点的左子树只包含 小于 当前节点的数。节点的右子树只包含 大于 当前节点的数。所有左子树和右子树自身必须也是二叉搜索树。 示例…

kubernetes基本概念和操作

基本概念和操作 1.Namespace1.1概述1.2应用示例 2.Pod2.1概述2.2语法及应用示例 3.Label3.1概述3.2语法及应用示例 4.Deployment4.1概述4.2语法及应用示例 5.Service5.1概述5.2语法及应用示例5.2.1创建集群内部可访问的Service5.2.2创建集群外部可访问的Service5.2.3删除服务5.…

Zoho Mail企业邮箱商业扩展系列第1部分:入门指南与基础设置

今天让我们来认识一下王雪琳&#xff0c;她是一位独立经营的营销咨询机构的个体企业家。在开始自己的事业之前&#xff0c;她进行了广泛的市场调研&#xff0c;明确了自己的业务定位&#xff0c;并全力以赴地投入到了自己的企业中。 一、创业背景 王雪琳的营销业务主要集中在…

日志报错 git -c dif.mnemonicprefix=false -c core.guotepath=false 解决方法

前言: 在进行下面操作前,必须确保,你是否安装了Git。 查看Git 在命令行窗口中输入`git --version`: 如果这个命令成功显示了Git的版本信息,这表明Git已经被安装。 1. 使用Sourcetree SourceTree 是 Windows 和Mac OS X 下免费的 Git 和 Hg 客户端…

Kubernetes operator(五)api 和 apimachinery 篇

云原生学习路线导航页&#xff08;持续更新中&#xff09; 本文是 Kubernetes operator学习 系列第五篇&#xff0c;主要对 k8s.io/api 和 k8s.io/apimachinery 两个项目 进行学习基于 kubernetes v1.24.0 代码分析Kubernetes operator学习系列 快捷链接 Kubernetes operator&a…

数字巨轮航行大数据海洋:数据可视化引领时代潮流

在大数据时代的潮流中&#xff0c;数据可视化如同一艘畅行无阻的科技巨轮&#xff0c;引领我们穿越数字浩瀚的大海&#xff0c;使我们在信息的航程中游刃有余。下面我就从可视化从业者的角度&#xff0c;来简单说说数据可视化是如何帮助我们在大数据时代畅行无阻的。 数据可视化…

【Servlet】——Servlet API 详解

个人主页&#xff1a;兜里有颗棉花糖 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 兜里有颗棉花糖 原创 收录于专栏【Servlet】 本专栏旨在分享学习Servlet的一点学习心得&#xff0c;欢迎大家在评论区交流讨论&#x1f48c; 目录 一、HttpServlet二、Htt…

【unity小技巧】unity3d环境带雾的昼夜系统变化

最终效果 文章目录 最终效果眩光素材眩光配置全局灯光配置天空盒配置天空盒资产配置天空盒&#xff0c;开启雾 代码控制天空盒 环境 雾 灯光昼夜交替变化参考完结 眩光素材 链接&#xff1a;https://pan.baidu.com/s/1qlFSJSju6ZjwCylwkh14eA?pwdveww 提取码&#xff1a;veww…

Oracle和Mysql数据库

数据库 Oracle 体系结构与基本概念体系结构基本概念表空间(users)和数据文件段、区、块Oracle数据库的基本元素 Oracle数据库启动和关闭Oracle数据库启动Oracle数据库关闭 Sqlplussqlplus 登录数据库管理系统使用sqlplus登录Oracle数据库远程登录解锁用户修改用户密码查看当前语…

Android学习之路(29) Gradle初探

前言: 大家回想一下自己第一次接触Gradle是什么时候&#xff1f; 相信大家也都是和我一样&#xff0c;在我们打开第一个AS项目的时候&#xff0c; 发现有很多带gradle字样的文件&#xff1a;setting.gradle, build.gradle,gradle.warpper,以及在gradle文件中各种配置&#xff…

来看看Tomcat和Web应用的目录结构

在前面两篇大致了解了Tomcat的架构和运行流程&#xff0c;以及Tomcat应用中的web.xml。 聊一聊Tomcat的架构和运行流程&#xff0c;尽量通俗易懂一点-CSDN博客 来吧&#xff0c;好好理解一下Tomcat下的web.xml-CSDN博客 那接下来&#xff0c;再看看Tomcat的目录&#xff0c;…

BeanDefinitionRegistry学习

Spring版本5.1.x 简介 在Spring框架中&#xff0c;BeanDefinitionRegistry是一个接口&#xff0c;它主要用于向注册表中注册BeanDefinition实例&#xff0c;完成注册的过程。该接口的主要方法是registerBeanDefinition&#xff0c;用于将一个BeanDefinition实例注册到注册表中…

分享一个WPF项目

最近在学习WPF开发方式&#xff0c;找到一些项目进行拆解学习&#xff1b;本位主要分享一个WPF项目&#xff0c;叫做WPFDevelopers&#xff0c;在git上大约有1.3K星&#xff0c;话不多说&#xff0c;先看看效果&#xff1a; 这个项目开发可以编译启动后直接查看样例、Xaml、Cha…

如何搭建私有云盘SeaFile并实现远程访问本地文件资料

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” #mermaid-svg-hsDnDEybLME85dTx {font-family:"trebuchet ms",verdana,arial,sans-serif;font-siz…

让AI作为人类的“副驾驶”

让AI作为副驾驶&#xff0c;辅助人类完成工作&#xff0c;是人工智能技术在实际应用中的一个重要方向。这个设想在多个领域都有广泛应用的可能性&#xff1a; 驾驶场景&#xff1a;自动驾驶技术中&#xff0c;AI副驾驶可以实时监测路况、规划最优行驶路线、预警潜在危险&#x…

接口自动化测试详解

&#x1f345; 视频学习&#xff1a;文末有免费的配套视频可观看 &#x1f345; 关注公众号【互联网杂货铺】&#xff0c;回复 1 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 引言 与UI相比&#xff0c;接口一旦研发完成&#xff0c;通常变…