I have a Python script that uses Pillow to resize an image to an Instagram sized image with blurred background from the original image.
Before and after images (both JPGs):
https://app.box.com/s/jpv2mxlncp9871zvx9ygt0be4gf0zc9q
Is this simply a function of the 'after' JPG being too small to reflect all the colors in the original image? (Instagram only allows 2048x2048 images max, my original is a JPG converted from TIF from a 24.2-megapixel RAW image taken from a Nikon DSLR). Perhaps it's all in my head, but in my opinion, the 'after' image has lost some saturation / vibrance (compare the yellow buildings and car lights for example)
Has anyone experienced similar issues? Is there some default mode in Pillow that is reducing the number of colors available? I'm thinking of adding an additional saturation step to my script, but that seems like a hack.
EDIT: Added another before / after pair of images to the link above. I also realize I can easily share the script's source (GitHub repo):
https://github.com/princefishthrower/instagramize
The difference is that the original image contains an "ICC Colour Profile" (amongst others) which are not preserved in the output image.
You can see this most easily with exiftool:
exiftool Mountains_Before.jpg | grep -i profile
Or with ImageMagick:
magick identify -verbose Mountains_Before.jpg | grep -A999 Profiles:
Output
Profiles:
Profile-8bim: 92 bytes
Profile-exif: 17796 bytes
Profile-icc: 560 bytes
Profile-iptc: 80 bytes
City[1,90]: 0x00000000: 254700 -%G
Created Date[2,55]: 2020-7-1
unknown[2,62]: 2020-06-30
unknown[2,63]: 21:11:26+00:00
unknown[2,0]: 4
Created Time[2,60]: 20:22:05-20:22
Profile-xmp: 9701 bytes
If you strip the profiles from the original, you will see it too, is washed out and flatter:
magick Mountains_Before.jpg -strip NoProfile.jpg
You can extract the ICC Profile and look at out like this if that sort of thing excites you:
magick Mountains_Before.jpg profile.icc
If you did that, I guess you could re-attach the profile from the BEFORE image to the AFTER image like this:
magick Mountains_After.jpg -profile profile.icc AfterWithProfile.jpg
Keywords: Image processing, ImageMagick, profile, ICC profile, saturation, saturated, desaturated, washed out.
As Mark Setchell pointed out, it is a matter of preserving the color profile of the image, which is possible natively in Pillow, first by retrieving the profile after opening the image:
image = Image.open('mycoolimage.jpg')
iccProfile = image.info.get('icc_profile')
iccBytes = io.BytesIO(iccProfile)
originalColorProfile = ImageCms.ImageCmsProfile(iccBytes)
and when calling save with Pillow you can pass an icc_profile:
image.save('outputimagename.jpg', icc_profile=originalColorProfile.tobytes())
(Obviously, I am doing other manipulations to image in between these two steps here. Apparently one or more of them cause the icc_profile to disappear.)
This answer was also helpful in building this solution.
I added Mountains_After_NEW.jpg for those interested to see the results of these additions.
Related
I'm trying to convert EPS images to JPEG using Pillow. But the results are of low quality. I'm trying to use resize method, but it gets completely ignored. I set up the size of JPEG image as (3600, 4700), but the resulted image has (360, 470) size. My code is:
eps_image = Image.open('img.eps')
height = eps_image.height * 10
width = eps_image.width * 10
new_size = (height, width)
print(new_size) # prints (3600, 4700)
eps_image.resize(new_size, Image.ANTIALIAS)
eps_image.save(
'img.jpeg',
format='JPEG'
dpi=(9000, 9000),
quality=95)
UPD. Vasu Deo.S noticed one my error, and thanks to him the JPG image has become bigger, but quality is still low. I've tried different DPI, sizes, resample values for resize function, but the result does not change much. How can i make it better?
The problem is that PIL is a raster image processor, as opposed to a vector image processor. It "rasterises" vector images (such as your EPS file and SVG files) onto a grid when it opens them because it can only deal with rasters.
If that grid doesn't have enough resolution, you can never regain it. Normally, it rasterises at 100dpi, so if you want to make bigger images, you need to rasterise onto a larger grid before you even get started.
Compare:
from PIL import Image
eps_image = Image.open('image.eps')
eps_image.save('a.jpg')
The result is 540x720:
And this:
from PIL import Image
eps_image = Image.open('image.eps')
# Rasterise onto 4x higher resolution grid
eps_image.load(scale=4)
eps_image.save('a.jpg')
The result is 2160x2880:
You now have enough quality to resize however you like.
Note that you don't need to write any Python to do this at all - ImageMagick will do it all for you. It is included in most Linux distros and is available for macOS and Windows and you just use it in Terminal. The equivalent command is like this:
magick -density 400 input.eps -resize 800x600 -quality 95 output.jpg
It's because eps_image.resize(new_size, Image.ANTIALIAS) returns an resized copy of an image. Therefore you have to store it in a separate variable. Just change:-
eps_image.resize(new_size, Image.ANTIALIAS)
to
eps_image = eps_image.resize(new_size, Image.ANTIALIAS)
UPDATE:-
These may not solve the problem completely, but still would help.
You are trying to save your output image as a .jpeg, which is a
lossy compression format, therefore information is lost during the
compression/transformation (for the most part). Change the output
file extension to a lossless compression format like .png so that
data would not be compromised during compression. Also change
quality=95 to quality=100 in Image.save()
You are using Image.ANTIALIAS for resampling the image, which is
not that good when upscaling the image (it has been replaced by
Image.LANCZOS in newer version, the clause still exists for
backward compatibility). Try using Image.BICUBIC, which produces
quite favorable results (for the most part) when upscaling the image.
I need to increase the dpi of my image before reading with ocr in opencv. The problems are :
I do not know the dpi of my image right now
I do not know how to increase the dpi of an image
I searched in Google, and almost every answer suggests using cv2.resize
image = cv2.imread("source.png")
resized_image = cv2.resize(image, (100, 50)) #I need to change it to 300 DPI
resize only changes the size of image, but after all does not increase the dpi. I tried to use it, and then checked in Photoshop, the dpi was not changed.
How to do it with opencv?
I need to change dpi to 300, why do I need to know current dpi? Because if it is already dpi > 300, I do not need to convert it.
I do it with python.
The dpi is just a number in the JPEG/TIFF/PNG header. It is entirely irrelevant to the world and his dog until you print the image and then it determines how large the print will be given the image's dimensions in pixels.
During image processing, it is irrelevant. The only thing of any interest is the number of pixels you have. That is the ultimate determinant of image quality, or information content - however you want to describe it.
I don't believe you can set it with OpenCV. You can certainly set it with ImageMagick like this in the Terminal:
mogrify -set density 300 *.png # v6 ImageMagick
magick mogrify -set density 300 *.png # v7 ImageMagick
You can check it with:
identify -format "Density: %x x %y" SomeImage.jpg # v6 ImageMagick
magick identify -format ... as above # v7 ImageMagick
You can do similar things with exiftool in Terminal - note that exiftool is MUCH smaller and easier to maintain than ImageMagick because it is "just" a (very capable) single Perl script:
Extract image resolution from EXIF IFD1 information:
exiftool -IFD1:XResolution -IFD1:YResolution image.jpg
Extract all tags with names containing the word "Resolution" from an image|:
exiftool '-*resolution*' image.jpg
Set X/Y Resolution (density) on image.jpg:
exiftool -xresolution=300 -yresolution=300 image.jpg
Here is a little demonstration of what I mean at the beginning of my answer...
Use ImageMagick to create an image 1024x768 with no dpi information:
convert -size 1024x768 xc:black image.jpg
Now examine it:
identify -verbose image.jpg
Image: image.jpg
Format: JPEG (Joint Photographic Experts Group JFIF format)
Mime type: image/jpeg
Class: PseudoClass
Geometry: 1024x768+0+0
Units: Undefined
Colorspace: Gray
Type: Bilevel
...
...
Now change the dpi and set the dpi units and examine it again:
mogrify -set density 300 -units pixelsperinch image.jpg # Change dpi
identify -verbose image.jpg # Examine
Image: image.jpg
Format: JPEG (Joint Photographic Experts Group JFIF format)
Mime type: image/jpeg
Class: PseudoClass
Geometry: 1024x768+0+0 <--- Number of pixels is unchanged
Resolution: 300x300 <---
Print size: 3.41333x2.56 <--- Print size is now known
Units: PixelsPerInch <---
Colorspace: Gray
Type: Bilevel
...
...
And now you can see that suddenly we know how big a print will come out and that the number of pixels has not changed.
Even though this is an old post I just wanted to say that Tesseract has been tested and found to operate better when the height of the characters is around 30 pixels. Please check the following link:
https://groups.google.com/forum/#!msg/tesseract-ocr/Wdh_JJwnw94/24JHDYQbBQAJ
DPI is inherited property of graphical device - monitor, scanner, camera, etc. For example - lets say that we are scanning image, and we want to get image with better quality - so we set higher DPI value in scanner options. If no better DPI option - so we need to buy a better scanner which supports more scan resolutions. There are devices/methods which are able to achieve 100 000 DPI
While saving jpg thumbnails from tiff (CMYK) file I'm encountering the following problem:
Thumbnail created with color mode conversion from CMYK to RGB gets blue hue:
Thumbnail created from the same tiff in photoshop (no ICC profile, not converted to sRGB, RGB only) get the colors the right way:
Thumbnails created without color mode conversion (jpeg with CMYK) gets similar results to the photoshopped ones but are not usable for the web (still blue).
Code snippet:
img = Image.open(img_tiff)
img.thumbnail(size)
img.convert("RGB").save(img_jpg, quality=70, optimize=True)
Also, when trying to include tiff's ICC profile it gets corrupted in some way, and photoshop is complaining about the profile being corrupted. I was including the profile in the save function using:
icc_profile=img.info.get('icc_profile')
What am I doing wrong / missing here?
EDIT:
Searching for the solution I found that the problem was linked to the icc profiles. Tiff file has FOGRA profile, jpeg should have some sRGB. Couldn't get to work profile transformation from within Pillow (ImageCms.profileToProfile() was throwing PyCMSError cannot build transform ,'ImageCmsProfile' object is not subscriptable, 'PIL._imagingcms.CmsProfile' object is not subscriptable ).
Found a workaround to the problem using ImageMagick#7 convert:
com_proc = subprocess.call(['convert',
img_tiff,
'-flatten',
'-profile', 'path/to/sRGB profile',
'-colorspace', 'RGB',
img_jpeg])
The result of conversion is very good, file size is quite small and most important of all, colours are matching the original tiff file.
Still would like to know how to do it properly from within Pillow (ICC profile reading and applying).
Python 3.6.3, Pillow 4.3.0, OSX 10.13.1
I've finally found the way to convert from CMYK to RGB from within Pillow (PIL) without recurring to external call to ImageMagick.
# first - extract the embedded profile:
tiff_embedded_icc_profile = ImageCms.ImageCmsProfile(io.BytesIO(tiff_img.info.get('icc_profile')))
# second - get the path to desired profile (in my case sRGB)
srgb_profile_path = '/Library/Application Support/Adobe/Color/Profiles/Recommended/sRGB Color Space Profile.icm'
# third - perform the conversion (must have LittleCMS installed)
converted_image = ImageCms.profileToProfile(
tiff_img,
inputProfile=tiff_embedded_icc_profile,
outputProfile=srgb_profile_path,
renderingIntent=0,
outputMode='RGB'
)
# finally - save converted image:
converted_image.save(new_name + '.jpg', quality=95, optimize=True)
All colors are preserved.
The reason why CMYK to RGB got the blue tint is because the conversion used only a very rough formula, probably something like:
red = 1.0 – min (1.0, cyan + black)
green = 1.0 – min (1.0, magenta + black)
blue = 1.0 – min (1.0, yellow + black)
To get the colours you expected you would need to use a proper Colour Management System (CMS), such as LittleCMS. Apparently Pillow is able to work with LCMS but I'm not sure if it's included be default so you would probably need to build Pillow yourself.
I'm forming an image in SVG from a bitmap using svgwrite and python where each line is rotated by theta around a common origin into a fan like-pattern. Currently this image is running around 10 MB for 128 lines largely because the excessive amount of floating point retained in the 79000+ line segments (one line segment per pixel).
I'd like to get the image size down significantly by drawing a single line from the origin out to the end-point and then stretch one line 'image' over that SVG line. Is this possible using SVG? Beyond that, I'm open to any suggestion that might get the size down significantly and so I later I can animate the lines in place.
How about this solution (see fiddle):
Slice the image up in the number of strips you need. For the example in the linked fiddle, I used ImageMagick as follows to cut up a 128x128 PNG image into 128 vertical strips:
convert image.png -crop 1x128 +repage +adjoin strip-%d.png
Convert all the image strips to data URI format and embed them in the SVG. I used this bash one-liner:
for i in strip-*.png; do echo "data:image/png;base64,$(openssl base64 < $i | tr -d '\n')"; done > data-uris.txt
In the SVG, use the transform() attribute on the image strip elements to rotate them to the required degree.
For a 128x128 PNG icon of 16.4KB, I end up with an SVG file of 128.1KB. Most of that is the base64-encoded image data (the total size of the 128 PNG strips is already 85.1KB). It could be further reduced a little by rounding of some floats, but I don't think there's a whole lot to be gained.
There might be another approach possible where you embed the image as a whole, and reference another clipped section of the same image over and over, but I couldn't get that to work.
In order to reduce the size of images to be used in a website, I reduced the quality to 80-85%. This decreases the image size quite a bit, up to an extent.
To reduce the size further without compromising the quality, my friend pointed out that raw images from cameras have a lot of metadata called Exif info. Since there is no need to retain this Exif info for images in a website, we can remove it. This will further reduce the size by 3-10 kB.
But I'm not able to find an appropriate library to do this in my Python code. I have browsed through related questions and tried out some of the methods:
Original image: http://mdb.ibcdn.com/8snmhp4sjd75vdr27gbadolc003i.jpg
Mogrify
/usr/local/bin/mogrify -strip filename
Result: http://s23.postimg.org/aeaw5x7ez/8snmhp4sjd75vdr27gbadolc003i_mogrify.jpg
This method reduces the size from 105 kB to 99.6 kB, but also changed the color quality.
Exif-tool
exiftool -all= filename
Result: http://s22.postimg.org/aiq99o775/8snmhp4sjd75vdr27gbadolc003i_exiftool.jpg
This method reduces the size from 105 kB to 72.7 kB, but also changed the color quality.
This answer explains in detail how to manipulate the Exif info, but how do I use it to remove the info?
Can anyone please help me remove all the extra metadata without changing the colours, dimensions, and other properties of an image?
from PIL import Image
image = Image.open('image_file.jpeg')
# next 3 lines strip exif
data = list(image.getdata())
image_without_exif = Image.new(image.mode, image.size)
image_without_exif.putdata(data)
image_without_exif.save('image_file_without_exif.jpeg')
For me, gexiv2 works fine:
#!/usr/bin/python3
from gi.repository import GExiv2
exif = GExiv2.Metadata('8snmhp4sjd75vdr27gbadolc003i.jpg')
exif.clear_exif()
exif.clear_xmp()
exif.save_file()
See also Exif manipulation library for python, which you linked, but didn't read all answers ;)
You can try loading the image with the Python Image Lirbary (PIL) and then save it again to a different file. That should remove the meta data.
You don't even need to do the extra steps #user2141737 suggested. Just opening it up with PIL and saving it again seems to do the trick just fine:
from PIL import Image
image = Image.open('path/to/image')
image.save('new/path/' + file_name)
As for pillow==9.2.0
This seems to print exif data, a mutable mapping
print(im.info)
This seems to clear exif data for PNG
def clear_exif():
with Image.open('./my_image.png', mode='r', formats=['PNG']) as im:
fields_to_keep = ('transparency', )
exif_fields = list(im.info.keys())
for k in exif_fields:
if k not in fields_to_keep:
del im.info[k]
im.save('./my_image.png', format='PNG')