如何将一个1通道数组保存为具有适当颜色限制的图像

huangapple go评论59阅读模式
英文:

How to save a 1-channel array as an image with proper color limit

问题

I am trying to save a sequence of 1-channel array into grayscale images. These images are supposed to be masks for segmentation.

The issue I am facing is inconsistent colors for the same pixel value in different images:

Image #1
如何将一个1通道数组保存为具有适当颜色限制的图像

When I add additional circles to this image, then the saved image has a different set of colors for previously present circles.

Image #2
如何将一个1通道数组保存为具有适当颜色限制的图像

Ideally, I want them to have consistent colors throughout my entire dataset as each circle represents a unique class of label.

So my question is how can I save these grayscale images with consistent color code?

Added code snippet to generate the above image

import numpy as np
import cv2
import matplotlib.image as mpimg

image = np.zeros(shape=[256, 256], dtype=np.uint8)

cv2.circle(image, center=(50, 50), radius=10, color=(2, 2), thickness= -1)
cv2.circle(image, center=(100, 100), radius=10, color=(3, 3), thickness= -1)
cv2.circle(image, center=(150, 150), radius=10, color=(4, 4), thickness= -1)
cv2.circle(image, center=(75, 200), radius=10, color=(5, 5), thickness= -1)
cv2.circle(image, center=(200, 89), radius=10, color=(6, 6), thickness= -1)

# additional circles (they are part of Image #2)
cv2.circle(image, center=(21, 230), radius=5, color=(7, 7), thickness= -1)
cv2.circle(image, center=(149, 250), radius=5, color=(12, 12), thickness= -1)

mpimg.imsave('image.jpg',image)
英文:

I am trying to save a sequence of 1-channel array into grayscale images. These images are supposed to be masks for segmentation.

The issue I am facing is inconsistent colors for same pixel value in different images:

Image #1
如何将一个1通道数组保存为具有适当颜色限制的图像

When I add additional circles to this image, then saved image has different set of colors for previous present circles.

Image #2
如何将一个1通道数组保存为具有适当颜色限制的图像

Ideally I want them to have consistent colors throughout my entire dataset as each circle represent a unique class of label.

So my question is how can I save these grayscale images with consistent color code?

Added code snippet to generate above image

import numpy as np
import cv2
import matplotlib.image as mpimg

image = np.zeros(shape=[256, 256], dtype=np.uint8)

cv2.circle(image, center=(50, 50), radius=10, color=(2, 2), thickness= -1)
cv2.circle(image, center=(100, 100), radius=10, color=(3, 3), thickness= -1)
cv2.circle(image, center=(150, 150), radius=10, color=(4, 4), thickness= -1)
cv2.circle(image, center=(75, 200), radius=10, color=(5, 5), thickness= -1)
cv2.circle(image, center=(200, 89), radius=10, color=(6, 6), thickness= -1)

# additional circles (they are part of Image #2)
cv2.circle(image, center=(21, 230), radius=5, color=(7, 7), thickness= -1)
cv2.circle(image, center=(149, 250), radius=5, color=(12, 12), thickness= -1)

mpimg.imsave('image.jpg',image)

答案1

得分: 3

以下是翻译好的部分:

首先一些提示...

  • 你不能将JPEG格式用于分类数据。JPEG是一种用于照片的有损格式,允许更改像素以使其在重新阅读时“看起来相似”,但实际上会有所不同。请使用无损PNG格式用于类数据。请参阅答案末尾的演示。

  • 每个像素位置都用单个数字表示你的类别,这意味着你需要单通道图像,也就是灰度图像。这意味着在绘制圆圈时,应该将颜色指定为单个数字,而不是2个数字的元组。

  • 没有必要引入不必要的依赖项 - 你已经在使用OpenCV,所以读取和写入图像也应使用它。

这意味着你的代码应该更像这样:

#!/usr/bin/env python3

import numpy as np
import cv2 as cv

image = np.zeros(shape=[256, 256], dtype=np.uint8)

cv.circle(image, center=(50, 50), radius=10, color=2, thickness= -1)
cv.circle(image, center=(100, 100), radius=10, color=3, thickness= -1)
cv.circle(image, center=(150, 150), radius=10, color=4, thickness= -1)
cv.circle(image, center=(75, 200), radius=10, color=5, thickness= -1)
cv.circle(image, center=(200, 89), radius=10, color=6, thickness= -1)

# additional circles (they are part of Image #2)
cv.circle(image, center=(21, 230), radius=5, color=7, thickness= -1)
cv.circle(image, center=(149, 250), radius=5, color=12, thickness= -1)

cv.imwrite('classes.png', image)

这将以非常低的对比度保存你的图像 - 所有像素将在0..255的可能范围内的0..12范围内,因此它们将很暗,但仍然包含你需要的信息。

要进行可视化,你需要进行对比度拉伸或将数据归一化到整个范围。你可以使用ImageMagick来实现:

magick classes.png -normalize classes-norm.png

如果你不想使用ImageMagick进行归一化,可以在OpenCV中进行等效操作:

normalizedImg = cv.normalize(img, None, 0, 255, cv.NORM_MINMAX)

如果你想将自己的颜色映射应用于数据,可以使用LUT "Lookup Table" 来实现,如下所示:

#!/usr/bin/env python3

import sys
import cv2 as cv
import numpy as np

if __name__ == "__main__":

   # 检查提供的文件名
   if len(sys.argv) != 2:
      sys.exit('Usage: argv[0] filename')

   # 读取灰度类图像
   image = cv.imread(sys.argv[1], cv.IMREAD_GRAYSCALE)

   # 创建具有3个(RGB)条目的256个条目的调色板,最初看起来像灰度
   pal = np.fromfunction(lambda i,j: i, (256,3), dtype=np.uint8)

   # 覆盖从灰色到新颜色的任何所需颜色
   pal[0] = [0,0,0]      # 0 映射到黑色
   pal[1] = [255,0,0]    # 1 映射到红色
   pal[2] = [0,255,0]    # 2 映射到绿色
   pal[3] = [0,0,255]    # 3 映射到蓝色
   pal[4] = [255,255,0]  # 4 映射到黄色
   pal[5] = [0,255,255]  # 5 映射到青色
   pal[6] = [255,0,255]  # 6 映射到洋红
   pal[7] = [128,0,0]    # 7 映射到深红
   pal[8] = [0,128,0]    # 8 映射到深绿
   pal[9] = [0,0,128]    # 9 映射到深蓝

   # 转换LUT为OpenCV的BGR顺序
   pal = pal[:,::-1]

   # 在LUT中查找每个像素
   result = pal[image]

   # 保存结果
   cv.imwrite('result.png', result)

请注意,与默认LUT中设置的暗灰色(12,12,12)不匹配,因此与类12对应的圆圈不显示出来。

请注意,上述颜色化的图像现在是3通道的RGB图像,与单通道灰度classes.png不同。

请注意,如果超过255个类别并且使用np.uint16等,你将需要重新审视代码。

以下是关于JPEG对数据的影响的小演示。首先,我们生成一个100x100的图像,并填充随机噪声,即随机颜色。然后将颜色数量减少到64,并首先保存结果为PNG,然后再次保存相同的数据为JPEG:

magick -size 100x100 xc: +noise random -colors 64 -write a.png a.jpg

PNG

如何将一个1通道数组保存为具有适当颜色限制的图像

JPEG

如何将一个1通道数组保存为具有适当颜色限制的图像

它们看起来相同,对吧?现在让我们数一下每个图像中的唯一颜色数:

magick a.png a.jpg -format "%f: %k\n" info:

结果

a.png: 64
a.jpg: 9682     # 哎呀!看看JPEG做了什么。


<details>
<summary>英文:</summary>

First some tips...

* You cannot use JPEG format for classification data. JPEG is a **lossy** format for **photographs** and is allowed to change your pixels for something that *&quot;looks pretty similar&quot;* but will be different when you re-read it. Use lossless PNG format for class data. See demonstration at end of answer.

* Your classes are each represented by a single number at each pixel location - that means you need a single-channel image, a.k.a. greyscale. That means you should specify colours as a single number when drawing circles, rather than as a tuple of 2 numbers.

* There&#39;s no point introducing unnecessary dependencies - you are already using **OpenCV** so read and write your images with that.

These mean your code should look more like this:

    #!/usr/bin/env python3
    
    import numpy as np
    import cv2 as cv
    
    image = np.zeros(shape=[256, 256], dtype=np.uint8)
    
    cv.circle(image, center=(50, 50), radius=10, color=2, thickness= -1)
    cv.circle(image, center=(100, 100), radius=10, color=3, thickness= -1)
    cv.circle(image, center=(150, 150), radius=10, color=4, thickness= -1)
    cv.circle(image, center=(75, 200), radius=10, color=5, thickness= -1)
    cv.circle(image, center=(200, 89), radius=10, color=6, thickness= -1)
    
    # additional circles (they are part of Image #2)
    cv.circle(image, center=(21, 230), radius=5, color=7, thickness= -1)
    cv.circle(image, center=(149, 250), radius=5, color=12, thickness= -1)
    
    cv.imwrite(&#39;classes.png&#39;, image)

This will save your images with very low contrast - all pixels will be in the range 0..12 out of a possible range of 0..255, so they will be dark, but still contain the information you need.

[![enter image description here][1]][1]

---

In order to visualise, you will need to contrast-stretch, or normalise your data to the full range. You can do that with **ImageMagick** very simply:

    magick classes.png -normalize classes-norm.png

[![enter image description here][2]][2]

If you don&#39;t want to use **ImageMagick** to normalize, the equivalent operation in **OpenCV** is:

    normalizedImg = cv.normalize(img, None, 0, 255, cv.NORM_MINMAX)

---

If you want to apply your own colormap to the data, you can do that using a LUT *&quot;Lookup Table&quot;* like this:

    #!/usr/bin/env python3
    
    import sys
    import cv2 as cv
    import numpy as np
    
    if __name__ == &quot;__main__&quot;:
    
       # Check filename supplied
       if len(sys.argv) != 2:
          sys.exit(&#39;Usage: argv[0] filename&#39;)
    
       # Read greyscale class image
       image = cv.imread(sys.argv[1], cv.IMREAD_GRAYSCALE)
    
       # Create a 256 entry palette each with 3 (RGB) entries, initially looking like greyscale
       pal = np.fromfunction(lambda i,j: i, (256,3), dtype=np.uint8)
    
       #&#160;Overwrite any desired colours from grey to new colour
       pal[0] = [0,0,0]      # 0 maps to black
       pal[1] = [255,0,0]    # 1 maps to red
       pal[2] = [0,255,0]    # 2 maps to green
       pal[3] = [0,0,255]    # 3 maps to blue
       pal[4] = [255,255,0]  # 4 maps to yellow
       pal[5] = [0,255,255]  # 5 maps to cyan
       pal[6] = [255,0,255]  # 6 maps to magenta
       pal[7] = [128,0,0]    # 7 maps to dark red
       pal[8] = [0,128,0]    # 8 maps to dark green
       pal[9] = [0,0,128]    # 9 maps to dark blue
    
       #&#160;Convert LUT to BGR order for OpenCV
       pal = pal[:,::-1]
    
       # Look up each pixel in the LUT
       result = pal[image]
    
       # Save result
       cv.imwrite(&#39;result.png&#39;, result)

[![enter image description here][3]][3]

Obviously you could equally store the LUT in a JSON or a CSV and load it from there rather than putting it in the Python code.

---

Note that the circle corresponding to class 12 doesn&#39;t show up because I was too lazy to remap that away from the dark grey I set in the default LUT (12,12,12).

Note that the image as colourised above is now a 3-channel RGB image, in contrast to the single channel, greyscale `classes.png`.

---

Note that you will need to revisit the code if you exceed 255 classes and use `np.uint16` and so on.

---

Here&#39;s a little demonstration of what JPEG does to your data. First we generate a 100x100 image and fill it with random noise, i.e. random colours. Then reduce the number of colours to 64 and save the result first as a PNG, and then save the same data again as JPEG:

    magick -size 100x100 xc: +noise random -colors 64 -write a.png a.jpg

**PNG**

[![enter image description here][4]][4]

**JPEG**

[![enter image description here][5]][5]

They look the same, don&#39;t they. Now let&#39;s count the number of unique colours in each:

    magick a.png a.jpg -format &quot;%f: %k\n&quot; info:

**Result**

    a.png: 64
    a.jpg: 9682     # OOPS! Look what JPEG did.

Why does JPEG do that? Because the file is 60% smaller but the images look the same:

    -rw-r--r--@    1 mark  staff     46256  9 Jun 11:04 a.png
    -rw-r--r--@    1 mark  staff     18991  9 Jun 11:04 a.jpg

  [1]: https://i.stack.imgur.com/qZDoq.png
  [2]: https://i.stack.imgur.com/lyNxm.png
  [3]: https://i.stack.imgur.com/6SwBT.png
  [4]: https://i.stack.imgur.com/WeB77.png
  [5]: https://i.stack.imgur.com/Hxz79.jpg

</details>



huangapple
  • 本文由 发表于 2023年6月9日 08:36:34
  • 转载请务必保留本文链接:https://go.coder-hub.com/76436502.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定