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

news/2024/12/12 22:21:50/

文章目录

  • 为什么需要图像抖动
  • 图像抖动算法实现的基本思路
  • 常见图像抖动算法实现
    • 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/81/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) * 255image[y, x] = new_pixelerror = old_pixel - new_pixelimage[y, x + 1] += error * 7 / 16image[y + 1, x - 1] += error * 3 / 16image[y + 1, x] += error * 5 / 16image[y + 1, x + 1] += error * 1 / 16return imagedef floyd_steinberg_dithering():# Load an RGB imageimage = cv2.imread("Data/test.png")# Convert the image to grayscalegray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)# Apply Floyd-Steinberg ditheringdithered_image = floyd_steinberg_dithering_kernel(np.copy(gray_image))# Display the original grayscale image and the dithered imagecv2.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) * 255image[y, x] = new_pixeldiff = old_pixel - new_pixelerror[y, x + 1] += diff * 1 / 8error[y, x + 2] += diff * 1 / 8error[y + 1, x - 1] += diff * 1 / 8error[y + 1, x] += diff * 1 / 8error[y + 1, x + 1] += diff * 1 / 8error[y + 2, x] += diff * 1 / 8return imagedef atkinson_dithering():# Load an RGB imageimage = cv2.imread("Data/test.png")# Convert the image to grayscalegray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)# Apply Atkinson ditheringdithered_image = atkinson_dithering_kernel(np.copy(gray_image.astype(np.float32)))# Display the original grayscale image and the dithered imagecv2.imshow("Original", gray_image)cv2.imshow("A Dithered", dithered_image.astype(np.uint8))cv2.waitKey(0)cv2.destroyAllWindows()def main():# Load an RGB imageimage = cv2.imread("Data/test.png")# Convert the image to grayscalegray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)# Apply Floyd-Steinberg ditheringfs_dithered_image = floyd_steinberg_dithering_kernel(np.copy(gray_image))# Apply Atkinson ditheringatk_dithered_image = atkinson_dithering_kernel(np.copy(gray_image.astype(np.float32)))# Display the original grayscale image and the dithered imagecv2.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.ppmy.cn/news/109854.html

相关文章

WebrtcNode, publish-sdp offer 流程(1)

1. AmqpClient - New message received sdp offer 的消息 AmqpClient - RpcServer New message received {method: onTransportSignaling,args: [aa230ce0863e42baa8bae5c14e91e809,{sdp: v0\r\n o- 2367615733001925388 2 IN IP4 127.0.0.1\r\n s-\r\n t0 0\r\n agroup:BUND…

【Java 并发编程】深入理解 AQS - AbstractQueuedSynchronizer

深入理解 AQS - AbstractQueuedSynchronizer 1. AQS1.1 什么是 AQS1.2 AQS 具备的特性 2. AQS 原理解析2.1 AQS 原理概述2.1.1 什么是 CLH 锁2.1.2 AQS 中的队列 2.2 AQS 共享资源的方式:独占式和共享式2.2.1 Exclusive(独占式)2.2.2 Share&a…

Linux系统下imx6ull QT编程—— C++类和对象(三)

Linux QT编程 文章目录 Linux QT编程一、类和对象 一、类和对象 C 在 C 语言的基础上增加了面向对象编程,C 支持面向对象程序设计。类是 C 的核心特性,通常被称为用户定义的类型。类用于指定对象的形式,它包含了数据表示法和用于处理数据的方…

Java革命性ORM框架Jimmer简单介绍

首发于Enaium的个人博客 本文使用Jimmer的官方用例来介绍Jimmer的使用方法,Jimmer同时支持Java和Kotlin,本文使用Java来介绍,实际上Kotlin比Java使用起来更方便,这里为了方便大家理解,使用Java来介绍,本篇文章只是对Jimmer的一个简单介绍,更多的内容请参考官方文档 这里开始就…

代码随想录算法训练营第四十二天|0/1背包问题理论基础 416. 分割等和子集

目录 0/1背包问题理论基础 二维dp数组 一维dp数组 LeeCode 416. 分割等和子集 0/1背包问题理论基础 二维dp数组 动规五部曲 1.确定dp数组及下标含义: dp[i][j] : 从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和的最大值; 2…

Flutter问题记录 - TextField组件多行提示文本显示不全

文章目录 前言开发环境问题描述问题分析解决方案最后 前言 梳理Flutter项目的过程中发现还有一些遗留的TODO没处理,其中有一个和TextField组件相关。 开发环境 Flutter: 3.10.1Dart: 3.0.1 问题描述 TextField组件设置maxLines: null不限制行数,同时…

elementUI中<el-select>下拉框选项过多的页面优化方案——多列选择

效果展示(多列可以配置) 一、icon下拉框的多列选择: 二、常规、通用下拉框的多列选择: 【注】第二种常规、通用下拉框的多列选择,是在第一种的前端代码上删除几行代码就行(把icon显示标签删去),所以下面着重…

root 密码破解(rd.break)

在Linux系统中,忘记root密码时,可以用此方法进行暴力修改root密码 示例: 设置一个新的记不住的密码 $ echo cnakdnvf | passwd --stdin root $ poweroff 1.启动此虚拟机,选中以下行,并按 【 e 】进入内核编辑页面 …