I'm using pgmagick to generate a circular thumbnail. I'm using a process similar to the one discussed here, which does indeed produce a nice circular thumbnail for me. However, I need a white border around the radius of the circle.
My initial approach was to create a new image of a slightly larger white circle with a transparent background and composite the thumbnail over that, letting the white circle "peak out" from under the thumbnail and create a border effect. Here's the pgmagick code I used to achieve that:
border_background = Image(Geometry(220, 220), Color('transparent'))
drawer = Draw()
drawer.circle(110, 110, 33.75, 33.75)
drawer.fill_color(Color('white'))
drawer.stroke_antialias(False)
border_background.draw(drawer.drawer)
border_background.composite(original_thumbnail, 0, 0, CompositeOperator.OverCompositeOp)
This 'works', but the surrounding white border is fairly distorted with choppy edges -- not production ready. If I take drawer.stroke_antialias(False) out, it's even worse.
Any ideas on making this border smoother using pgmagick?
I leave it as a simple exercise for the reader to convert this solution from
commandline to pgmagick (see more below). The code underlying pgmagick is the same as that used by the commandline.
You could draw the circle larger and then "resize" it down. This ameliorates the jaggy look of the circle by averaging the edge with the surrounding background during the resizing operation.
Instead of
gm convert -size 220x220 xc:none -fill white \
-draw "circle 110,110, 33.75,33.75" \
original.png
Do this:
gm convert -size 880x880 xc:none -fill white \
-draw "circle 440,440, 135,135" \
-resize 25% resized.png
You could try other sizes and
decide which is the smallest that satisfies you, e.g.,
gm convert -size 440x440 xc:none -fill white \
-draw "circle 220,220, 67.5,65.5" \
-resize 50% resized.png
This commandline works on both GraphicsMagick ("gm convert") and ImageMagick ("convert")
Looking at the pgmagick documentation at
http://pgmagick.readthedocs.org/en/latest/cookbook.html#scaling-a-image it is not clear that pgmagick offers "resize". The documentation shows "img.scale" which will probably result in a jaggy circle. Using "-scale" on the commandline examples above instead of "-resize" does indeed produce the same jaggy image.
pgmagick does however allow you to specify the filter type, as in
img.scale((150, 100), 'lanczos')
which should be equivalent to "-resize" and is what you want.
You will get a better result if you choose a different approach:
# First draw the thumbnail inside the circle.
background = Image(Geometry(220, 220), Color('transparent'))
drawer = Draw()
drawer.circle(110, 110, 33.75, 33.75)
drawer.fill_color(Color('white'))
background.draw(drawer.drawer)
background.composite(original_thumbnail, 0, 0, CompositeOperator.InCompositeOp)
# Draw only the border of the circle on top of the thumbnail inside the circle
border = Image(Geometry(220, 220), Color('transparent'))
drawer.fill_color(Color('transparent'))
drawer.stroke_color(Color('white'))
drawer.stroke_width(3)
border.draw(drawer.drawer)
background.composite(border, 0, 0, CompositeOperator.OverCompositeOp)
Related
I'm trying to find a way to transform an image by translating one of its vertexes.
I have already found various methods for transforming an image like rotation and scaling, but none of the methods involved skewing like so:
There is shearing, but it's not the same since it can move two or more of the image's vertex while I only want to move one.
What can I use that can perform such an operation?
I took your "cat-thing" and resized it to a nice size, added some perfectly vertical and horizontal white gridlines and added some extra canvas in red at the bottom to give myself room to transform it. That gave me this which is 400 pixels wide and 450 pixels tall:
I then used ImageMagick to do a "Bilinear Forward Transform" in Terminal. Basically you give it 4 pairs of points, the first pair is where the top-left corner is before the transform and then where it must move to. The next pair is where the top-right corner is originally followed by where it ends up. Then the bottom-right. Then the bottom-left. As you can see, 3 of the 4 pairs are unmoved - only the bottom-right corner moves. I also made the virtual pixel black so you can see where pixels were invented by the transform in black:
convert cat.png -matte -virtual-pixel black -interpolate Spline -distort BilinearForward '0,0 0,0 399,0 399,0 399,349 330,430 0,349 0,349' bilinear.png
I also did a "Perspective Transform" using the same transform coordinates:
convert cat.png -matte -virtual-pixel black -distort Perspective '0,0 0,0 399,0 399,0 399,349 330,430 0,349 0,349' perspective.png
Finally, to illustrate the difference, I made a flickering comparison between the 2 images so you can see the difference:
I am indebted to Anthony Thyssen for his excellent work here which I commend to you.
I understand you were looking for a Python solution and would point out that there is a Python binding to ImageMagick called Wand which you may like to use - here.
Note that I only used red and black to illustrate what is going on (atop the Stack Overflow white background) and where aspects of the result come from, you would obviously use white for both!
The perspective transformation is likely what you want, since it preserves straight lines at any angle. (The inverse bilinear only preserves horizontal and vertical straight lines).
Here is how to do it in ImageMagick, Python Wand (based upon ImageMagick) and Python OpenCV.
Input:
ImageMagick
(Note the +distort makes the output the needed size to hold the full result and is not restricted to the size of the input. Also the -virtual-pixel white sets color of the area outside the image pixels to white. The points are ordered clockwise from the top left in pairs as inx,iny outx,outy)
convert cat.png -virtual-pixel white +distort perspective \
"0,0 0,0 359,0 359,0 379,333 306,376 0,333 0,333" \
cat_perspective_im.png
Python Wand
(Note the best_fit=true makes the output the needed size to hold the full result and is not restricted to the size of the input.)
#!/bin/python3.7
from wand.image import Image
from wand.display import display
with Image(filename='cat.png') as img:
img.virtual_pixel = 'white'
img.distort('perspective', (0,0, 0,0, 359,0, 359,0, 379,333, 306,376, 0,333, 0,333), best_fit=True)
img.save(filename='cat_perspective_wand.png')
display(img)
Python OpenCV
#!/bin/python3.7
import cv2
import numpy as np
# Read source image.
img_src = cv2.imread('cat.png')
# Four corners of source image
# Coordinates are in x,y system with x horizontal to the right and y vertical downward
pts_src = np.float32([[0,0], [359,0], [379,333], [0,333]])
# Four corners of destination image.
pts_dst = np.float32([[0, 0], [359,0], [306,376], [0,333]])
# Get perspecive matrix if only 4 points
m = cv2.getPerspectiveTransform(pts_src,pts_dst)
# Warp source image to destination based on matrix
# size argument is width x height
# compute from max output coordinates
img_out = cv2.warpPerspective(img_src, m, (359+1,376+1), cv2.INTER_LINEAR, borderMode=cv2.BORDER_CONSTANT, borderValue=(255, 255, 255))
# Save output
cv2.imwrite('cat_perspective_opencv.png', img_out)
# Display result
cv2.imshow("Warped Source Image", img_out)
cv2.waitKey(0)
cv2.destroyAllWindows()
After importing an image using python's PIL module I would like to get the set of colours in the image as a list of rgb tuples.
What If I know before hand that there will only be 2 colours and the image will be very small, maybe 20x20 pixels? However, I will be running this algorithm over a lot of images. Will It be more effiient to loop through all pixels until I see 2 unique colours? Because I understand loops are very slow in python.
First, let's make an image. I'll just use ImageMagick to make a blue background with magenta writing:
convert -size 300x120 -background blue -fill magenta -gravity center -font AppleChancery label:"StackOverflow" PNG24:image.png
As you can see, I only specified two colours - magenta and blue, but the PNG image actually contains 200+ colours and the JPEG image contains 2,370 different colours!
So, if I want to get the two main colours, I can do this:
from PIL import Image
# Open the image
im = Image.open('image.png')
# Quantize down to 2 colour palettised image using *"Fast Octree"* method:
q = im.quantize(colors=2,method=2)
# Now look at the first 2 colours, each 3 RGB entries in the palette:
print(q.getpalette()[:6])
Sample Result
[0, 0, 255, 247, 0, 255]
If you write that out as 2 RGB triplets, you get:
RGB 0/0/255 = blue
RGB 247/0/255 = magenta
The best way to do this for lots of images is to use multithreading or multiprocessing if you want them done fast!
Keywords: Python, PIL, Pillow, image, image processing, octree, fast octree, quantise, quantize, palette, palettise, palettize, reduce colours, reduce colors, anti-aliasing, font, unique, unique colours, unique colors.
I'd like to be able to automagically convert full color images down to three color (black / red / white) for an e-ink display (Waveshare 7.5"). Right now I'm just letting the screen handle it, but as expected complex images get washed out.
Are there any algorithms or filters I could apply to make things a bit more visible?
Right now I'm using Python, but I'm not averse to other languages/environments if necessary.
Good image:
Washed out image:
You could make your own palette of 3 acceptable colours like this:
magick xc:red xc:white xc:black +append palette.gif
Then you can apply it to your image like this:
magick input.png +dither -remap palette.gif result.png
If you want to send it straight to the frame buffer and it supports RB888, you can try running something like this:
magick input.png +dither -remap palette.gif -depth 8 RGB:/dev/fb0
Just adding a bit to Mark Setchell's answer. For printing you might be better dithering your 3 colors. So here is your image with and without dithering using Imagemagick 7. If using Imagemagick 6, replace magick with convert.
Input:
Create 3 color palette:
magick xc:red xc:white xc:black +append palette.gif
With dithering(default is Floyd-Steinberg):
magick input.png -remap palette.gif result.png
[![enter image description here][2]][2]
With out dithering
magick input.png -dither none -remap palette.gif result2.png
[![enter image description here][3]][3]
If you want Python, then you could try Python Wand. It is based upon Imagemagick.
ADDITION:
To separate the red and black into two image, each of which are represented by black and the rest as white, you can do the following and save as BMP as you want in your comments. (You can do this with or without dithering from above as you desire)
magick result.png -color-threshold "red-red" -negate red.bmp
magick result.png -color-threshold "black-black" -negate black.bmp
Red:
Black:
You appear to be choosing the nearest color for each pixel. See if a dithering algorithm works better for your purposes. Generally, dithering algorithms take into account neighboring pixels when determining how to color a given pixel.
EDIT: In the case of PIL (the Python Imaging Library), it doesn't seem trivial to dither to an arbitrary set of three colors, at least as of 2012.
Just adding a bit to Mark and Fred's answers. I'm using ImageMagick on Raspberry Pi, which is version < 7 and uses "convert". Some of the commands Fred had suggested didn't work for that version. Here's what I did to resize, remap and dither, and split the image into white-and-black and white-and-red sub-images.
# Create palette with red, white and black colors
convert xc:red xc:white xc:black +append palette.gif
# Resize input file into size suitable for ePaper Display - 264x176
# Converting to BMP.
# Note, if working with JPG, it is a lossy
# format and subsequently remapping and working with it results
# in the color palette getting overwritten - we just convert to BMP
# and work with that instead
convert $1 -resize 264x176^ -gravity center -extent 264x176 resized.bmp
# Remap the resized image into the colors of the palette using
# Floyd Steinberg dithering (default)
# Resulting image will have only 3 colors - red, white and black
convert resized.bmp -remap palette.gif result.bmp
# Replace all the red pixels with white - this
# isolates the white and black pixels - i.e the "black"
# part of image to be rendered on the ePaper Display
convert -fill white -opaque red result.bmp result_black.bmp
# Similarly, Replace all the black pixels with white - this
# isolates the white and red pixels - i.e the "red"
# part of image to be rendered on the ePaper Display
convert -fill white -opaque black result.bmp result_red.bmp
I've also implemented in using Python Wand, a Python layer over ImageMagick
# This function takes as input a filename for an image
# It resizes the image into the dimensions supported by the ePaper Display
# It then remaps the image into a tri-color scheme using a palette (affinity)
# for remapping, and the Floyd Steinberg algorithm for dithering
# It then splits the image into two component parts:
# a white and black image (with the red pixels removed)
# a white and red image (with the black pixels removed)
# It then converts these into PIL Images and returns them
# The PIL Images can be used by the ePaper library to display
def getImagesToDisplay(filename):
print(filename)
red_image = None
black_image = None
try:
with WandImage(filename=filename) as img:
img.resize(264, 176)
with WandImage() as palette:
with WandImage(width = 1, height = 1, pseudo ="xc:red") as red:
palette.sequence.append(red)
with WandImage(width = 1, height = 1, pseudo ="xc:black") as black:
palette.sequence.append(black)
with WandImage(width = 1, height = 1, pseudo ="xc:white") as white:
palette.sequence.append(white)
palette.concat()
img.remap(affinity=palette, method='floyd_steinberg')
red = img.clone()
black = img.clone()
red.opaque_paint(target='black', fill='white')
# This is not nececessary - making the white and red image
# white and black instead - left here FYI
# red.opaque_paint(target='red', fill='black')
black.opaque_paint(target='red', fill='white')
red_image = Image.open(io.BytesIO(red.make_blob("bmp")))
black_image = Image.open(io.BytesIO(black.make_blob("bmp")))
except Exception as ex:
print ('traceback.format_exc():\n%s',traceback.format_exc())
return (red_image, black_image)
Here's my writeup on my project on Hackster (including full source code links) - https://www.hackster.io/sridhar-rajagopal/photostax-digital-epaper-photo-frame-84d4ed
I've attributed both Mark and Fred there - thank you!
I am analyzing medical images. All images have a marker with the position. It looks like this
It is the "TRH RMLO" annotation in this image, but it can be different in other images. Also the size varies. The image is cropped but you see that the tissue is starting on the right side.
I found that the presence of these markers distort my analysis.
How can I remove them?
I load the image in python like this
import dicom
import numpy as np
img = dicom.read_file(my_image.dcm)
img_array = img.pixel_array
The image is then a numpy array. The white text is always surrounded by a large black area (black has value zero). The marker is in a different position in each image.
How can I remove the white text without hurting the tissue data.
UPDATE
added a second image
UPDATE2:
Here are two of the original dicom files. All personal information has been removed.edit:removed
Looking at the actual pixel values of the image you supplied, you can see that the marker is almost (99.99%) pure white and this doesn't occur elsewhere in the image so you can isolate it with a simple 99.99% threshold.
I prefer ImageMagick at the command-line, so I would do this:
convert sample.dcm -threshold 99.99% -negate mask.png
convert sample.dcm mask.png -compose darken -composite result.jpg
Of course, if the sample image is not representative, you may have to work harder. Let's look at that...
If the simple threshold doesn't work for your images, I would look at "Hit and Miss Morphology". Basically, you threshold your image to pure black and white - at around 90% say, and then you look for specific shapes, such as the corner markers on the label. So, if we want to look for the top-left corner of a white rectangle on a black background, and we use 0 to mean "this pixel must be black", 1 to mean "this pixel must be white" and - to mean "we don't care", we would use this pattern:
0 0 0 0 0
0 1 1 1 1
0 1 - - -
0 1 - - -
0 1 - - -
Hopefully you can see the top left corner of a white rectangle there. That would be like this in the Terminal:
convert sample.dcm -threshold 90% \
-morphology HMT '5x5:0,0,0,0,0 0,1,1,1,1 0,1,-,-,- 0,1,-,-,- 0,1,-,-,-' result.png
Now we also want to look for top-right, bottom-left and bottom-right corners, so we need to rotate the pattern, which ImageMagick handily does when you add the > flag:
convert sample.dcm -threshold 90% \
-morphology HMT '5x5>:0,0,0,0,0 0,1,1,1,1 0,1,-,-,- 0,1,-,-,- 0,1,-,-,-' result.png
Hopefully you can see dots demarcating the corners of the logo now, so we could ask ImageMagick to trim the image of all extraneous black and just leave the white dots and then tell us the bounding box:
cconvert sample.dcm -threshold 90% \
-morphology HMT '5x5>:0,0,0,0,0 0,1,1,1,1 0,1,-,-,- 0,1,-,-,- 0,1,-,-,-' -format %# info:
308x198+1822+427
So, if I now draw a red box around those coordinates, you can see where the label has been detected - of course in practice I would draw a black box to cover it but I am explaining the idea:
convert sample.dcm -fill "rgba(255,0,0,0.5)" -draw "rectangle 1822,427 2130,625" result.png
If you want a script to do that automagically, I would use something like this, saving it as HideMarker:
#!/bin/bash
input="$1"
output="$2"
# Find corners of overlaid marker using Hit and Miss Morphology, then get crop box
IFS="x+" read w h x1 y1 < <(convert "$input" -threshold 90% -morphology HMT '5x5>:0,0,0,0,0 0,1,1,1,1 0,1,-,-,- 0,1,-,-,- 0,1,-,-,-' -format %# info:)
# Calculate bottom-right corner from top-left and dimensions
((x1=x1-1))
((y1=y1-1))
((x2=x1+w+1))
((y2=y1+h+1))
convert "$input" -fill black -draw "rectangle $x1,$y1 $x2,$y2" "$output"
Then you would do this to make it executable:
chmod +x HideMarker
And run it like this:
./HideMarker someImage.dcm result.png
I have another idea. This solution is in OpenCV using python. It is a rather solution.
First, obtain the binary threshold of the image.
ret,th = cv2.threshold(img,2,255, 0)
Perform morphological dilation:
dilate = cv2.morphologyEx(th, cv2.MORPH_DILATE, kernel, 3)
To join the gaps, I then used median filtering:
median = cv2.medianBlur(dilate, 9)
Now you can use the contour properties to eliminate the smallest contour and retain the other containing the image.
It also works for the second image:
If these annotations are in the DICOM file there are a couple ways they could be stored (see https://stackoverflow.com/a/4857782/1901261). The currently supported method can be cleaned off by simply removing the 60xx group attributes from the files.
For the deprecated method (which is still commonly used) you can clear out the unused high bit annotations manually without messing up the other image data as well. Something like:
int position = object.getInt( Tag.OverlayBitPosition, 0 );
if( position == 0 ) return;
int bit = 1 << position;
int[] pixels = object.getInts( Tag.PixelData );
int count = 0;
for( int pix : pixels )
{
int overlay = pix & bit;
pixels[ count++ ] = pix - overlay;
}
object.putInts( Tag.PixelData, VR.OW, pixels );
If these are truly burned into the image data, you're probably stuck using one of the other recommendations here.
The good thing is, that these watermarks are probably in an isolated totally black are which makes it easier (although it's questionable if removing this is according to the indicated usage; license-stuff).
Without beeing an expert, here is one idea. It might be a sketch of some very very powerful approach tailored to this problem but you have to decide if implementation-complexity & algorithmic-complexity (very dependent on image-statistics) are worth it:
Basic idea
Detect the semi-cross like borders (4)
Calculate the defined rectangle from these
Black-out this rectangle
Steps
0
Binarize
1
Use some gradient-based edge-detector to get all the horizontal edges
There may be multiple; you can try to give min-length (maybe some morphology needed to connect pixels which are not connected based on noise in source or algorithm)
2
Use some gradient-based edge-detector to get all the horizontal edges
Like the above, but a different orientation
3
Do some connected-component calculation to get some objects which are vertical and horizontal lines
Now you can try different chosings of candidate-components (8 real ones) with the following knowledge
two of these components can be described by the same line (slope-intercept form; linear regression problem) -> line which borders the rectangle
it's probably that the best 4 pair-chosings (according to linear-regression loss) are the valid borders of this rectangle
you might add the assumption, that vertical borders and horizontal borders are orthogonal to each other
4
- Calculate the rectangle from these borders
- Widen it by a few pixels (hyper-parameter)
- Black-out that rectangle
That's the basic approach.
Alternative
This one is much less work, use more specialized tools and assumes the facts in the opening:
the stuff to remove is on some completely black part of the image
it's kind of isolated; distance to medical-data is high
Steps
Run some general OCR to detect characters
Get the occupied pixels / borders somehow (i'm not sure what OCR tools return)
Calculate some outer rectangle and black-out (using some predefined widening-gap; this one needs to be much bigger than the one above)
Alternative 2
Sketch only: The idea is to use something like binary-closing on the image somehow to build fully connected-components ouf of the source pixels (while small gaps/holes are filled), so that we got one big component describing the medical-data and one for the watermark. Then just remove the smaller one.
I am sure this can be optimized, but ... You could create 4 patches of size 3x3 or 4x4, and initialize them with the exact content of the pixel values for each of the individual corners of the frame surrounding the annotation text. You could then iterate over the whole image (or have some smart initialization looking only in the black area) and find the exact match for those patches. It is not very likely you will have the same regular structure (90 deg corner surrounded by near 0) in the tissue, so this might give you the bounding box.
Simpler one is still possible!!!.
Just implement following after (img_array = img.pixel_array)
img_array[img_array > X] = Y
In which X is the intensity threshold you want to eliminate after that. Also Y is the intensity value which you want to consider instead of that.
For example:
img_array[img_array > 4000] = 0
Replace white matter greater than 4000 with black intensity 0.
Is it possible to place an image inside an image with OpenCv (JavaCv).
For example i have a 1000x1000 image and a 100x100 image. And at position 600x600 i would like to place the smaller image inside the larger image.
lets say the blue box is the 1000x1000 IplImage and the red one is the 100x100 IplImage.
Is it possible to put the red box in the blue box. Preferably computational rather efficient because it has to work in real time.
Thx in advance
This is in Python, but conversion to Java is going to be real easy. Use GetSubRect(), and Copy(). GetSubRect() returns a rectangular subarray of interest (specify top left point of interest, and the width and height). Then just copy over the image using Copy().
import cv
blue = cv.LoadImage("blue.jpg")
red = cv.LoadImage("red.jpg")
sub = cv.GetSubRect(blue, (100, 100, 50, 50))
cv.Copy(red,sub)
cv.ShowImage('blue_red', blue)
cv.WaitKey(0)
Alternatively, as karlphillip suggests you could specify the 'region of interest' using SetImageROI(), and do much the same thing:
cv.SetImageROI(blue,(100,100,50,50))
cv.Copy(red, blue)
cv.ResetImageROI(blue)
Its very important to reset the ROI, ResetImageROI, otherwise you will only display/save the ROI, and not the whole image.
Demo output:
blue: red: combined: