Save 1 bit deep binary image in Python
Asked Answered
S

3

11

I have a binary image in Python and I want to save it in my pc. I need it to be a 1 bit deep png image once stored in my computer. How can I do that? I tried with both PIL and cv2 but I'm not able to save it with 1 bit depth.

Subtle answered 10/5, 2018 at 7:50 Comment(0)
A
4

Use:

cv2.imwrite(<image_name>, img, [cv2.IMWRITE_PNG_BILEVEL, 1])

(this will still use compression, so in practice it will most likely have less than 1 bit per pixel)

Aesthesia answered 10/5, 2018 at 8:38 Comment(2)
I solved it with: "img.save(filename, bit=1)" from PIL.ImageSubtle
I found out that for this to work, different from what worked before with PIL, we need to save with the PNG extension.Canopy
G
9

I found myself in a situation where I needed to create a lot of binary images, and was frustrated with the available info online. Thanks to the answers and comments here and elsewhere on SO, I was able to find an acceptable solution. The comment from @Jimbo was the best so far. Here is some code to reproduce my exploration of some ways to save binary images in python:

Load libraries and data:

from skimage import data, io, util #'0.16.2'
import matplotlib.pyplot as plt #'3.0.3'
import PIL #'6.2.1'
import cv2 #'4.1.1'
check = util.img_as_bool(data.checkerboard())

The checkerboard image from skimage has dimensions of 200x200. Without compression, as a 1-bit image it should be represented by (200*200/8) 5000 bytes

To save with skimage, note that the package will complain if the data is not uint, hence the conversion. Saving the image takes an average of 2.8ms and has a 408 byte file size

io.imsave('bw_skimage.png',util.img_as_uint(check),plugin='pil',optimize=True,bits=1)

Using matplotlib, 4.2ms and 693 byte file size

plt.imsave('bw_mpl.png',check,cmap='gray')

Using PIL, 0.5ms and 164 byte file size

img = PIL.Image.fromarray(check)
img.save('bw_pil.png',bits=1,optimize=True)

Using cv2, also complains about a bool input. The following command takes 0.4ms and results in a 2566 byte file size, despite the png compression...

_ = cv2.imwrite('bw_cv2.png', check.astype(int), [cv2.IMWRITE_PNG_BILEVEL, 1])

PIL was clearly the best for speed and file size.

I certainly missed some optimizations, comments welcome!

Giess answered 5/3, 2020 at 19:1 Comment(2)
This is what I exactly want. Thank you :)Seabrook
What is the check object ? Is it a numpy array of booleans like [ True, False,True,False] ? It seems PIL expects uint8Pothook
A
4

Use:

cv2.imwrite(<image_name>, img, [cv2.IMWRITE_PNG_BILEVEL, 1])

(this will still use compression, so in practice it will most likely have less than 1 bit per pixel)

Aesthesia answered 10/5, 2018 at 8:38 Comment(2)
I solved it with: "img.save(filename, bit=1)" from PIL.ImageSubtle
I found out that for this to work, different from what worked before with PIL, we need to save with the PNG extension.Canopy
C
2

If you're not loading pngs or anything the format does behave pretty reasonably to just write it. Then your code doesn't need PIL or any of the headaches of various imports and imports on imports etc.

import struct
import zlib
from math import ceil


def write_png_1bit(buf, width, height, stride=None):
    if stride is None:
        stride = int(ceil(width / 8))
    raw_data = b"".join(
        b'\x00' + buf[span:span + stride] for span in range(0, (height - 1) * stride, stride))

    def png_pack(png_tag, data):
        chunk_head = png_tag + data
        return struct.pack("!I", len(data)) + chunk_head + struct.pack("!I", 0xFFFFFFFF & zlib.crc32(chunk_head))

    return b"".join([
        b'\x89PNG\r\n\x1a\n',
        png_pack(b'IHDR', struct.pack("!2I5B", width, height, 1, 0, 0, 0, 0)),
        png_pack(b'IDAT', zlib.compress(raw_data, 9)),
        png_pack(b'IEND', b'')])

Adapted from: http://code.activestate.com/recipes/577443-write-a-png-image-in-native-python/ (MIT)

by reading the png spec: https://www.w3.org/TR/PNG-Chunks.html

Keep in mind the 1 bit data from buf, should be written left to right like the png spec wants in normal non-interlace mode (which we declared). And the excess data pads the final bit if it exists, and stride is the amount of bytes needed to encode a scanline. Also, if you want those 1 bit to have palette colors you'll have to write a PLTE block and switch the type to 3 rather than 0. Etc.

Cairo answered 26/12, 2018 at 5:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.