如何自动确定具有最高对比度的RGBA图像的背景颜色?

发布于 2025-01-27 18:12:02 字数 2117 浏览 2 评论 0原文

背景

我有一堆RGBA图像。想象一下我想显示的图标,徽标或类似图像。这些图像可以具有任何颜色。另请注意,这些图像是RGBA,因此它们具有透明度的第四个Alpha通道。

并发症

最初,我在白色背景上展示了图像。但是,有时整个图像或其中的一部分是白色的,因此这些区域对用户看上去很空。

问题是图像不是全白或全黑。可能有多种颜色和颜色梯度。而且,由于RGBA图像的工作方式,像素并不总是全部颜色和不透明的。即使图像仅在中心显示纯黑图标,也可能在黑色图标的边缘有过渡区域,其像素具有某些透明度或更灰色的颜色。

问题

假定,对于每个图像,我可以在黑色,灰色或白色背景之间进行选择。 如何算法可以确定三种背景颜色选项中的哪个最适合任何单独的RGBA图像?

“最佳”是指图像的任何区域都不能虚假地出现空白,并且图像和背景之间的对比度最高。

示例

在下面的玩具示例中,我想显示的图标周围有一个白色圆圈。因此,我不能将白色用作背景颜色。我需要选择灰色或黑色。黑色是最好的,因为它最大化对比度。

在第二个玩具示例中,情况变得更加复杂,因为不同颜色的多个区域直接接触图像的透明区域。黑色背景似乎具有最高的对比度,但不再是微不足道的。

想法

  • 不透明区域的内部没有关系。背景颜色不会影响可见度。
  • 完全透明区域的内部没有关系。没有什么可以看不见的。
  • 我可能需要在不透明和透明之间的这些过渡区域中找到这些像素。然后确定这些颜色。

这似乎效率低下。由于大多数图标,徽标等都是为了明亮或黑暗的背景而设计的,因此也许足以在不透明和透明之间的过渡区域进行一些像素进行采样?

我觉得我正在重新发明轮子,并且有人以前已经找到了解决这个问题的解决方案。

stackoverflow上的现有问题

  • 我发现了有关背景颜色确定最佳字体颜色的现有问题。但是我的问题是不同的。我看到的问题仅处理仅在两种简单的RGB颜色之间最大化对比度。

附加示例3

请注意该徽标周围如何具有细小的白线。这意味着最佳背景颜色可能应该是黑色的。

可以与之播放的示例

这里有十几个左右的RGBA图像可以播放。我不拥有版权 - 这仅是为了说明问题。 ”

我添加了所有示例图像,从左到右如下:

  • 在棋盘上,在棋盘上显示
  • 上的
  • 在白色背景上的黑色
  • 背景
  • 透明区域,而仅提取了alpha通道

Background

I have a bunch of RGBA images. Imagine icons, logos or similar images that I would like to display. These images can be of any color. Please also note that the images are RGBA, so they have the fourth alpha channel for transparency.

Complication

Initially, I showed the images all on a white background. However, sometimes the whole image or parts of it were white, so these areas looked empty to the user.

The problem is that images are not all-white or all-black. There may be multiple colors and color gradients. And, due to the way RGBA images work, pixels are not always all one color and all opaque. Even if an image just showed a pure black icon in the center, there may be transition areas at the edges of the black icon with pixels of some transparency or more greyish colors.

Question

Assume, for each image I can choose between a black, grey, or white background.
How can I algorithmically determine which of the three background color options is the best for any individual RGBA image?

By "best" I mean that no areas of the image should falsely appear empty and the highest contrast between image and background is achieved.

Example

In the toy example below, the icon I want to show has a white circle around it. So I cannot use white as a background color. I need to choose grey or black. Black would be best since it maximises contrast.

Toy example 1

In the second toy example things are slightly more complicated since multiple areas of different color directly touch the transparent areas of the image. The black background appears to have the highest contrast but it is no longer trivial.

Toy example 1

Ideas

  • The inner parts of opaque areas do not matter. The background color would not affect the visibility.
  • The inner parts of the fully transparent areas do not matter. There is nothing that could be made invisible.
  • I would probably need to find those pixels in these transition areas between opaque and transparent. And then determine the color of these.

This appears awfully inefficient. Since most icons, logos, etc. have been designed for either a bright or a dark background, maybe it is sufficient to sample just a few pixels in the transition area between opaque and transparent?

I feel I am reinventing the wheel and someone must have found a solution to this problem before.

Existing questions on Stackoverflow

  • I found existing questions on determining the best font color given a background color. However my problem is different. The problems I saw were only dealing with maximising the contrast between only two simple RGB colors.

Additional example 3

Note how this logo has a fine white line around it. This implies the optimal background color should probably be black.

enter image description here

Examples to play around with

Here are a dozen or so RGBA images to play around with. I don't own the copyright -- this is just for the illustration of the problem.
Google Drive link

Edited in by Mark Setchell

I have added all sample images, going left-to-right as follows:

  • over a chessboard to show transparent areas
  • over black background
  • over grey background
  • over white background
  • with just the alpha channel extracted

enter image description here

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

草莓味的萝莉 2025-02-03 18:12:02

这个想法是计算图像的最“主要”颜色,然后选择与图像中最强的颜色相反的相应背景颜色。因为我们只能选择白色,灰色或黑色;我们可以假设最大化对比度的最佳颜色等于与最主要的颜色相反的颜色。这是一种使用 sklearn.cluster.kmean.kmean.kmeans()


the我们可以选择的三个可能的rgb颜色代码是:

White: rgb(255, 255, 255)
Gray: rgb(128, 128, 128)
Black: rgb(0, 0, 0)

我们可以通过找到优势颜色的平均值来计算最佳颜色,并将其分类在颜色区域之间。由于颜色通道的范围从[0-255],我们可以简单地将其分为三个象限:

# Find best color
if dominant_color_average <= 85:
    print('White!')
elif dominant_color_average > 85 and dominant_color_average <= 170:
    print('Gray!')
elif dominant_color_average > 170:
    print('Black!')

以下是一些示例:

使用n_clusters = 5,以下是最主要的 每种颜色的颜色和百分比分布

[217 215 213] 2.66%
[158 156 154] 2.80%
[84 82 79] 3.02%
[19 16 14] 6.87%
[254 254 254] 84.66%

可视化(在深色背景上,您可以看到白色)

结果

Dominant color: [254, 254, 254]
Dominant color average: 254
Black!

第二张图像

n_clusters = 5,以下是最主要的颜色和

[ 27 172 221] 0.84%
[179 186 188] 1.99%
[239 241 241] 10.98%
[118 118 118] 21.18%
[254 254 254] 65.02%

每种颜色的百分比分布(在黑暗的背景下,您可以看到白色

的示例徽标

Dominant color: [254, 254, 254]
Dominant color average: 254
Black!

具有主导的白色示例,因此最好的背景应该是黑色。可能会有一种更有趣的方法来计算最佳颜色,但我会把它留给您。


徽标的结果不是主要是白色

”

[249 247 249] 8.34%
[197  46 140] 21.02%
[173  74 213] 22.45%
[149 150 215] 22.68%
[205 103  84] 25.51%

Dominant color: [205, 103, 84]
Dominant color average: 130
Gray!

[220 246 246] 0.84%
[ 25 210 234] 11.26%
[  8  74 111] 22.17%
[  7 126 175] 23.17%
[ 2 28 45] 42.56%

Dominant color: [2, 28, 45]
Dominant color average: 25
White!

​=“ https://i.sstatic.net/xk13d.png” alt =“在此处输入图像说明”>

[120 180 218] 2.51%
[  2  89 148] 5.76%
[245 245 248] 8.51%
[237  29  59] 11.43%
[  0 123 196] 71.79%

Dominant color: [0, 123, 196]
Dominant color average: 106
Gray!

“在此处输入图像说明”

[ 17 124 143] 1.53%
[26 75 84] 1.74%
[  7 173 203] 1.84%
[  0 215 254] 22.30%
[33 34 34] 72.58%

Dominant color: [33, 34, 34]
Dominant color average: 33
White!

代码

import cv2
import numpy as np
from sklearn.cluster import KMeans

def visualize_colors(cluster, centroids, exact=False):
    # Get the number of different clusters, create histogram, and normalize
    labels = np.arange(0, len(np.unique(cluster.labels_)) + 1)
    (hist, _) = np.histogram(cluster.labels_, bins = labels)
    hist = hist.astype("float")
    hist /= hist.sum()
    
    # Convert each RGB color code from float to int
    if not exact:
        centroids = centroids.astype("int")
    
    # Create frequency rect and iterate through each cluster's color and percentage
    rect = np.zeros((50, 300, 3), dtype=np.uint8)
    colors = sorted([(percent, color) for (percent, color) in zip(hist, centroids)])
    start = 0
    for (percent, color) in colors:
        print(color, "{:0.2f}%".format(percent * 100))
        end = start + (percent * 300)
        cv2.rectangle(rect, (int(start), 0), (int(end), 50), \
                      color.astype("uint8").tolist(), -1)
        start = end
    return colors, rect

# Load image and convert to a list of pixels
image = cv2.imread('1.png')
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
reshape = image.reshape((image.shape[0] * image.shape[1], 3))

# Find and display most X dominant colors
cluster = KMeans(n_clusters=5).fit(reshape)
colors, visualize = visualize_colors(cluster, cluster.cluster_centers_)
visualize = cv2.cvtColor(visualize, cv2.COLOR_RGB2BGR)

# Obtain dominant RGB color code
dominant_color = colors[-1][1].tolist()
dominant_color_average = int(sum(dominant_color) / 3)
print('Dominant color:', dominant_color)
print('Dominant color average:', dominant_color_average)

# Find best color
if dominant_color_average <= 85:
    print('White!')
elif dominant_color_average > 85 and dominant_color_average <= 170:
    print('Gray!')
elif dominant_color_average > 170:
    print('Black!')

cv2.imshow('visualize', visualize)
cv2.waitKey()

The idea is to calculate the most "dominant" color of an image then choose the corresponding background color most opposite to the strongest color present in the image. Since we can only choose white, gray, or black; we can assume the best color to maximize contrast is equal to the color that is polar opposite to the most dominant color. Here's an approach using K-Means Clustering to determine the dominant colors of an image with sklearn.cluster.KMeans()


The three possible RGB color codes we can choose are:

White: rgb(255, 255, 255)
Gray: rgb(128, 128, 128)
Black: rgb(0, 0, 0)

We can calculate the best color by finding the average of the dominant color and categorize it between color zones. Since a color channel ranges from [0 - 255], we can simply split it into three quadrants:

# Find best color
if dominant_color_average <= 85:
    print('White!')
elif dominant_color_average > 85 and dominant_color_average <= 170:
    print('Gray!')
elif dominant_color_average > 170:
    print('Black!')

Here are some examples:

With n_clusters=5, here are the most dominant colors and percentage distribution

[217 215 213] 2.66%
[158 156 154] 2.80%
[84 82 79] 3.02%
[19 16 14] 6.87%
[254 254 254] 84.66%

Visualization of each color (on a dark background so you can see the white)

Results

Dominant color: [254, 254, 254]
Dominant color average: 254
Black!

The second image

With n_clusters=5, here are the most dominant colors and percentage distribution

[ 27 172 221] 0.84%
[179 186 188] 1.99%
[239 241 241] 10.98%
[118 118 118] 21.18%
[254 254 254] 65.02%

Visualization of each color (on a dark background so you can see the white)

Results

Dominant color: [254, 254, 254]
Dominant color average: 254
Black!

Both of your example logos have dominant white colors therefore the best background should be black. There could be a more interesting way of calculating the best color but I'll leave that up to you.


Results with logos that are not dominantly white

enter image description here

[249 247 249] 8.34%
[197  46 140] 21.02%
[173  74 213] 22.45%
[149 150 215] 22.68%
[205 103  84] 25.51%

Dominant color: [205, 103, 84]
Dominant color average: 130
Gray!

enter image description here

[220 246 246] 0.84%
[ 25 210 234] 11.26%
[  8  74 111] 22.17%
[  7 126 175] 23.17%
[ 2 28 45] 42.56%

Dominant color: [2, 28, 45]
Dominant color average: 25
White!

enter image description here

[120 180 218] 2.51%
[  2  89 148] 5.76%
[245 245 248] 8.51%
[237  29  59] 11.43%
[  0 123 196] 71.79%

Dominant color: [0, 123, 196]
Dominant color average: 106
Gray!

enter image description here

[ 17 124 143] 1.53%
[26 75 84] 1.74%
[  7 173 203] 1.84%
[  0 215 254] 22.30%
[33 34 34] 72.58%

Dominant color: [33, 34, 34]
Dominant color average: 33
White!

Code

import cv2
import numpy as np
from sklearn.cluster import KMeans

def visualize_colors(cluster, centroids, exact=False):
    # Get the number of different clusters, create histogram, and normalize
    labels = np.arange(0, len(np.unique(cluster.labels_)) + 1)
    (hist, _) = np.histogram(cluster.labels_, bins = labels)
    hist = hist.astype("float")
    hist /= hist.sum()
    
    # Convert each RGB color code from float to int
    if not exact:
        centroids = centroids.astype("int")
    
    # Create frequency rect and iterate through each cluster's color and percentage
    rect = np.zeros((50, 300, 3), dtype=np.uint8)
    colors = sorted([(percent, color) for (percent, color) in zip(hist, centroids)])
    start = 0
    for (percent, color) in colors:
        print(color, "{:0.2f}%".format(percent * 100))
        end = start + (percent * 300)
        cv2.rectangle(rect, (int(start), 0), (int(end), 50), \
                      color.astype("uint8").tolist(), -1)
        start = end
    return colors, rect

# Load image and convert to a list of pixels
image = cv2.imread('1.png')
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
reshape = image.reshape((image.shape[0] * image.shape[1], 3))

# Find and display most X dominant colors
cluster = KMeans(n_clusters=5).fit(reshape)
colors, visualize = visualize_colors(cluster, cluster.cluster_centers_)
visualize = cv2.cvtColor(visualize, cv2.COLOR_RGB2BGR)

# Obtain dominant RGB color code
dominant_color = colors[-1][1].tolist()
dominant_color_average = int(sum(dominant_color) / 3)
print('Dominant color:', dominant_color)
print('Dominant color average:', dominant_color_average)

# Find best color
if dominant_color_average <= 85:
    print('White!')
elif dominant_color_average > 85 and dominant_color_average <= 170:
    print('Gray!')
elif dominant_color_average > 170:
    print('Black!')

cv2.imshow('visualize', visualize)
cv2.waitKey()
や莫失莫忘 2025-02-03 18:12:02

这更像是“进行中的工作” ,而不是一个完整的答案 - 但没有要求Afaik完成SO答案,任何其他人都欢迎您采用这个想法并发展或发展适应它。

因此,虽然我还没有提出完整的解决方案,但我试图解决您问题的部分,上面说“不透明区域的内部无关紧要” 以及内部部分透明区域。我的想法是,如果您可以识别出和不重要的零件,那么您将更接近解决方案...

我只是在这里的终端中使用 Imagemagick ,但是您可以做到如果该方法证明有用,则与Python相同。因此,如果我拍摄您的第一张图像,我可以以下提取alpha频道:

magick example_1.png -alpha extract alpha.png

“在此处输入图像描述”

如果我现在在边缘周围添加一个1像素宽的黑色边框,我可以在红色从0,0开始,红色意志“流” 沿新边界的边缘周围,并从各个方面渗入图像,但没有达到透明的内部岛屿。边界的原因是如果黑色接触图像的边缘,这允许一个微小的1像素通道以使洪水在障碍物周围流动:

magick example_1.png -alpha extract -bordercolor black -border 1 -fill red -draw "color 0,0 floodfill" result.png

​红色像素是您认为自己感兴趣的……,接下来需要发生的事情是进一步思考的主题。我想我们可以使剩余的黑色零件白色:

magick example_1.png -alpha extract -bordercolor black -border 1 -fill red -draw "color 0,0 floodfill" -fill white -opaque black result.png

”在此处输入图像描述

,然后将其推回图像作为新的,修改的alpha通道。


这是其他几个样本接受相同处理的样本,因此您可以看到它的发展方向。

“在此处输入图像说明”

“

This is more of a "work-in-progress", than a complete answer - but there is no requirement AFAIK for SO answers to be complete, and anyone else is welcome to take the idea and develop or adapt it.

So, whilst I have not yet come up with a full solution, I am attempting to address the part of your question that says "the inner parts of the opaque areas do not matter" and likewise the inner parts of the transparent areas. My idea is that if you can identify the parts that do and don't matter, you will be closer to a solution...

I am just using ImageMagick in the Terminal here, but you can do exactly the same things with Python if the approach proves useful. So, if I take your first image, I can extract the alpha channel like this:

magick example_1.png -alpha extract alpha.png

enter image description here

If I now add a 1-pixel wide black border all around the edges, I can do a flood-fill in red starting at 0,0 and the red will "flow" all around the edges along the new border and seep into the image from all sides but without ever reaching the inner islands of transparency. The reason for the border is in case the black touches the edge of the image, this allows a tiny 1-pixel channel for the flood to flow around the obstruction:

magick example_1.png -alpha extract -bordercolor black -border 1 -fill red -draw "color 0,0 floodfill" result.png

enter image description here

So, what I am saying is that the red pixels are the ones you think you are interested in... and what needs to happen next is the subject of further thought. I guess we could make the remaining black parts white:

magick example_1.png -alpha extract -bordercolor black -border 1 -fill red -draw "color 0,0 floodfill" -fill white -opaque black result.png

enter image description here

and push that back into the image as a new, modified alpha channel.


Here are a couple of other samples subjected to the same treatment so you can see where it is headed..

enter image description here

enter image description here

enter image description here

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文