数字图像学笔记 —— 18. 图像抖动算法

news2024/11/20 22:37:58

文章目录

  • 为什么需要图像抖动
  • 图像抖动算法实现的基本思路
  • 常见图像抖动算法实现
    • Floyd-Steinberg 抖动算法
    • Atkinson 抖动算法
    • 算法实现

为什么需要图像抖动

在数字图像中,为了表示数字图像的细节,像素的颜色深度信息最少也是8位,即 0 − 256 0 - 256 0256. 但是在实际中,我们有很多显示设备的颜色深度信息仅有4位,甚至1位,即黑白。这样当我们试图在这样的设备上显示一副有深度信息的图像时,如果不做特殊的处理,就会遇到很大的麻烦。

所以,图像抖动算法(Image Dithering)最早是在数字图像处理中为了解决颜色深度受限制的问题而提出的。当图像的颜色深度较低,即每个像素可以表示的颜色数量有限时,图像的颜色表现力会受到限制,这可能会导致严重的颜色带状现象(banding)和颜色失真。

图像抖动算法的核心思想是通过在图像中引入一些噪声,将颜色误差以某种方式分散到附近的像素,使得在视觉上能够模拟出更多的颜色。这种技术尤其在早期的计算机图形显示系统中被广泛使用,因为这些系统的颜色深度通常非常有限。例如,在黑白打印机或只有黑白显示能力的设备中,通过抖动算法可以产生不同灰度级别的效果。

图像抖动算法实现的基本思路

图像抖动算法的基本思路是在减少颜色深度或者灰度等级时,尽可能地保留原始图像的视觉信息。这通常涉及到一种称为"误差扩散"的方法,该方法将每个像素值从其原始值量化到最近的可用颜色或灰度等级,并将造成的误差分散到周围的像素。

以下是图像抖动算法的基本步骤:

  • 选择一个图像和一个颜色或灰度级别的集合:这个集合可能只有两个颜色(如黑白),也可能有多个颜色。

  • 遍历图像的每个像素:对于图像中的每个像素,算法都会尝试找到最接近该像素颜色的颜色,然后将该像素颜色设置为该颜色。这个步骤通常称为"量化"。

  • 计算误差:量化步骤会导致一些颜色信息的丢失。这种丢失的颜色信息被称为"误差",可以通过将原始像素颜色和量化后的像素颜色相减来计算。

  • 扩散误差:接下来,算法将这个误差分散到相邻的像素。这个步骤的目的是尽可能地减少量化步骤对图像视觉质量的影响。误差可以按照多种方式分散,具体取决于使用的抖动算法。

重复以上步骤:算法将重复以上步骤,直到遍历了图像中的所有像素。

通过这种方式,抖动算法能够在颜色或灰度级别受限的情况下,模拟出更多的颜色或灰度等级,从而提高图像的视觉质量。

在这里插入图片描述

比方说,上图所示的Firefox标识,最左侧的是具有8位深度信息的灰度图,但是中间和右侧的都是只有1位深度信息的黑白图。从视觉看似乎很相似,甚至细节上没有太多缺失。但是如果放大后看,就是下面这个效果了。

在这里插入图片描述

常见图像抖动算法实现

图像抖动的实现算法有很多,但是在这里我只实现了其中两种,现在就具体说明。

Floyd-Steinberg 抖动算法

Floyd-Steinberg 抖动算法将误差分散到当前像素的右边和下面的像素。具体的误差扩散模式如下:

 X   7/16
3/16 5/16 1/16

在这个模式中,X 表示当前像素,数字表示误差扩散的比例。例如,当前像素的右边像素将接收 7/16 的误差,下面一行的左边、中间和右边的像素分别接收 3/16、5/16 和 1/16 的误差。

Atkinson 抖动算法

Atkinson 抖动算法将误差分散到当前像素的右边和下面的像素,但它的误差扩散模式与 Floyd-Steinberg 不同:

 X  1/8 1/8
1/8 1/8
    1/8

在这个模式中,误差被均匀地分散到六个像素,每个像素接收 1/8 的误差。

算法实现

import cv2
import numpy as np


#################### Floyd-Steinberg Dithering ####################

def floyd_steinberg_dithering_kernel(image):
    for y in range(image.shape[0] - 1):
        for x in range(1, image.shape[1] - 1):
            old_pixel = image[y, x]
            new_pixel = np.round(old_pixel / 255) * 255
            image[y, x] = new_pixel
            error = old_pixel - new_pixel
            image[y, x + 1] += error * 7 / 16
            image[y + 1, x - 1] += error * 3 / 16
            image[y + 1, x] += error * 5 / 16
            image[y + 1, x + 1] += error * 1 / 16
    return image


def floyd_steinberg_dithering():
    # Load an RGB image
    image = cv2.imread("Data/test.png")

    # Convert the image to grayscale
    gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

    # Apply Floyd-Steinberg dithering
    dithered_image = floyd_steinberg_dithering_kernel(np.copy(gray_image))

    # Display the original grayscale image and the dithered image
    cv2.imshow("Original", gray_image)
    cv2.imshow("FS Dithered", dithered_image)
    cv2.waitKey(0)
    cv2.destroyAllWindows()


#################### Atkinson Dithering ####################


def atkinson_dithering_kernel(image):
    error = np.zeros_like(image, dtype=np.float32)
    for y in range(image.shape[0] - 2):
        for x in range(image.shape[1] - 2):
            old_pixel = image[y, x] + error[y, x]
            new_pixel = np.round(old_pixel / 255) * 255
            image[y, x] = new_pixel
            diff = old_pixel - new_pixel
            error[y, x + 1] += diff * 1 / 8
            error[y, x + 2] += diff * 1 / 8
            error[y + 1, x - 1] += diff * 1 / 8
            error[y + 1, x] += diff * 1 / 8
            error[y + 1, x + 1] += diff * 1 / 8
            error[y + 2, x] += diff * 1 / 8
    return image


def atkinson_dithering():
    # Load an RGB image
    image = cv2.imread("Data/test.png")

    # Convert the image to grayscale
    gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

    # Apply Atkinson dithering
    dithered_image = atkinson_dithering_kernel(np.copy(gray_image.astype(np.float32)))

    # Display the original grayscale image and the dithered image
    cv2.imshow("Original", gray_image)
    cv2.imshow("A Dithered", dithered_image.astype(np.uint8))
    cv2.waitKey(0)
    cv2.destroyAllWindows()


def main():
    # Load an RGB image
    image = cv2.imread("Data/test.png")

    # Convert the image to grayscale
    gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

    # Apply Floyd-Steinberg dithering
    fs_dithered_image = floyd_steinberg_dithering_kernel(np.copy(gray_image))

    # Apply Atkinson dithering
    atk_dithered_image = atkinson_dithering_kernel(np.copy(gray_image.astype(np.float32)))

    # Display the original grayscale image and the dithered image
    cv2.imshow("Original", gray_image)
    cv2.imshow("FS Dithered", fs_dithered_image)
    cv2.imshow("ATK Dithered", atk_dithered_image)
    cv2.waitKey(0)
    cv2.destroyAllWindows()


if __name__ == "__main__":
    # floyd_steinberg_dithering()
    # atkinson_dithering()
    main()

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

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

相关文章

Linux:centos:周期性计划任务管理《crontab》

crontab常用基础属性 -e 编辑计划任务 -l 查看计划任务 -r 删除计划任务 -u 指定用户的计划任务 首先创建一个名为test的用户名 crontab时间规定 格式:分钟 小时 日期 月份 星期 命令 分钟-- 0-59整数 小时 -- 0-23整数 日期 -- 1--31 整数 月份 -- 1-12 整数 星期…

C++ queue类成员介绍

目录 🤔queue模板介绍: 🤔queue特点: 🤔queue内存图解: 🤔 queue的成员函数 🔍queue构造函数: 🔍queue赋值函数: 🔍queue判断函…

黑马Redis视频教程实战篇(三)

目录 一、优惠券秒杀 1.1 全局唯一ID 1.2 Redis实现全局唯一ID 1.3 添加优惠卷 1.4 实现秒杀下单 1.5 库存超卖问题分析 1.6 代码实现乐观锁解决超卖问题 1.7 优惠券秒杀-一人一单 1.8 集群环境下的并发问题 二、分布式锁 2.1 基本原理和实现方式对比 2.2 Redis分布…

js常见面试笔试题

一.js实现距离最近的回文数 给定一个整数 n ,你需要找到与它最近的回文数(不包括自身)。 “最近的”定义为两个整数差的绝对值最小。 示例 1: 输入: "123" 输出: "121" function findNearestPalindrome…

Jenkins+Python自动化测试之持续集成详细教程

前言 今天呢笔者想和大家来聊聊JenkinsPython自动化测试持续集成,废话呢就不多说了哟咱们直接进入主题哟。 一、Jenkins安装 ​ Jenkins是一个开源的软件项目,是基于java开发的一种持续集成工具,用于监控持续重复的工作,旨在提供…

I.MX RT1170加密启动详解(2):Authenticated HAB认证原理

文章目录 1 基础2 使能过程3 Boot flow 1 基础 HAB认证是基于RSA或ECDSA算法的公钥密码学,它用一系列的私钥对image进行加密,然后BootROM在上电后用对应的公钥验证加密的镜像是否被修改。这个密钥结构就是PKI(Public Key Infrastructure)树 (1)normal …

chatgpt赋能python:Python中画笔颜色的函数介绍

Python中画笔颜色的函数介绍 在Python中,我们可以使用turtle模块来绘制图形,其中画笔颜色是非常重要的一部分。画笔颜色可以决定图形的风格和色调,是图形表现的关键因素之一。Python中提供了几种方法来设置画笔颜色。 1. 设置画笔颜色的函数…

ENU、EPSG坐标系科普(三维重建)

ENU和EPSG实际上代表了两个不同的概念,这两者并不是直接对比的。 1. ENU坐标系:ENU坐标系是一种本地切面坐标系,用于表示与地理位置相关的空间数据。在ENU坐标系中,E代表东(East),N代表北&…

RabbittMQ快速实战和集群架构

介绍对比: Kafka:topic不能太多,一个缺点,影响Kafka的吞吐量 集群搭建:【单个也是一个集群(特殊)】 集群搭建:https://blog.csdn.net/p393975269/article/details/129830252 1:默认…

【明解STM32】中断系统理论基础知识篇之中断寄存器功能原理

目录 一、前言 二、寄存器概述 三、NVIC寄存器组 四、SCB寄存器组 五、中断屏蔽寄存器组 六、总结 一、前言 在之前的STM32的中断系统理论基础知识之基本原理及NVIC中,分别中断的基本原理,中断的管理机制和中断的处理流程进行了较为详细的论述&…

SOLIDWORKS钣金成形工具

SOLIDWORKS钣金成形工具主要用来创建使用冲制或压印制作的钣金特征。成形工具的工作原理是:几何体代表冲制或压印形成的凹陷区域,停止面是指工具要被应用到的钣金面,也可以定义移除面,若定义了移除面,则该面会形成通孔…

模拟人生打开显示找不到msvcp120.dll如何修复?msvcp120.dll是什么呢?

如果您在打开模拟人生游戏时遇到了msvcp120.dll文件丢失的错误信息,这可能是由于缺少Microsoft Visual C Redistributable for Visual Studio 2013软件包导致的。遇到这个情况不需要着急,只需要修复一下就可以。msvcp120.dll如何修复?下面就把…

黑马Redis视频教程实战篇(二)

目录 一、什么是缓存? 1.1 为什么要使用缓存? 1.2 如何使用缓存? 二、添加商户缓存 2.1 缓存模型和思路 2.2 代码实现 三、缓存更新策略 2.1 数据库缓存不一致解决方案 2.2 数据库和缓存不一致采用什么方案 四、实现商铺和缓存与数…

SAP-MM发票校验过账到总账

一.概念 SAP的MIRO发票校验,通常是参照采购订单(PO)生成,但有时会收到无PO参考的发票,例如供应商的运费、质检费等,这些不想过帐到发票项目的物料或成本科目,那么可以过帐到某个总账…

kong网关启用jwt认证插件

认证流程: 1、创建一个用户 2、生成jwt的所需要的key和密钥 3、在https://jwt.io/的生成jwt token 4、启用jwt插件 5、发送请求的时候携带jwt的token信息 官方指导:https://docs.konghq.com/hub/kong-inc/jwt/configuration/examples/ 一、创建一个新的…

深蓝学院C++基础笔记 第 0 章 C++介绍

一、什么是C 1、c 是一门比较流行的编程语言 \qquad ●https://www.tiobe.com/tiobe-index/2、c是C 语言的扩展 ● 更加关注性能 \qquad ● 与底层硬件紧密结合 \qquad \qquad java和python等语相对来说更加关注易用性。 \qquad ● 对象生命周期的精确控制 \qquad \qqua…

冒泡排序小练习(接收键盘录入、拆分、转整、冒泡递减排序、输出)

接收键盘录入(含输入字符串排错)字符串,拆分、转整、冒泡排序(递减)输出。 【学习的细节是欢悦的历程】 Python 官网:https://www.python.org/ Free:大咖免费“圣经”教程《 python 完全自学教程》,不仅仅是基础那么简单…… 地址…

unity3d 中场景不显示了

应该是把Layers下面的Nothing打开了

Java学习路线(18)——File、递归、字符集和IO流(上)

一、文件 (1)概述 File类在java.io.File下,代表操作系统的文件对象(文件、文件夹)File类提供诸如:定位文件,获取文件信息,删除文件,创建文件等操作 (2&…

大数据Doris(三十):Broker Load导入HDFS数据时进行数据过滤

文章目录 Broker Load导入HDFS数据时进行数据过滤 一、创建Doris表 二、准备HDFS数据