I'm new to programming, i won't become a developer but I like to learn a few stuff, since i'm a creative artist. I'm trying to solve an exercise I came up with.
I'm looking for guidelines to create a 1 pixel image from code. I want to try to create the most basic form of a white image, and I thought that a lossless 1 pixel gif of white (#FFFFFF or RGB 255, 255, 255) could be the simplest form, generating the smallest code possible.
Where would I even start?
I created two 1 pixel white .gifs, one in Paint and another in Photoshop, using 8bit Grayscale color mode. Gave both of them the same name, and when I hash them, both hashes are different, so they are not exactly the same. I also tried exporting from photoshop twice, to different directories, and this time hashes are the same.
My intention is to create the simplest and purest form of that white pixel.
Tips?
In Python, you can use PIL to create an image. Here is the code for what you requested.
from PIL import Image # import library
img = Image.new(mode = "RGB", size = (1,1)) # creates a RGB image with the size of 1x1
pixels = img.load() # Creates the pixel map
pixels[0,0] = (255,255,255) # Set the colour of the first (and only) pixel to white
img.save(format='GIF', fp='./test.gif')
# Saves the image as a GIF in a file called test.gif in your current directory
The purest, simplest form that is widely readable (GIMP, Photoshop, MS-Paint) is NetPBM IMHO.
Here is a single white pixel - no metadata, no copyright, no EXIF, no creation date, no palette, no checksum. You can create it with a text editor or code:
P1
1 1
0
Save it as white.pbm
If you want a simple, single, red pixel, save the following as red.ppm:
P3
1 1
255
255 0 0
The simplest Python to create a white single pixel GIF with PIL is probably:
from PIL import Image
Image.new('L', (1,1), 255).save('white.gif')
The simplest Python to create a red single pixel GIF with PIL is probably:
from PIL import Image
Image.new('RGB', (1,1), 'red').save('red.gif')
Related
I am stuck with a basic problem to which I was not able to find any answer nor solution no matter what I tried. Please help me out and enlighten me, what I am doing wrong :-)
Task:
Make a Numpy array of Pixels, manipulate them by an algorithm and then print an image from that array.
Occuring Problem:
When I manipulate single pixels this way, there is noise artifacts appearing next to manipulated pixels (see example pictures)
Details - What I want to do:
I have a numpy array to create an image from.
The array is created as black:
shape = np.zeros((100, 100, 3), dtype=np.uint8)
Now I want to manipulate single pixels by an algorithm, for instance:
color = (255, 255, 255)
shape[50, 50] = color
There will be 100s and 1000s of pixels manipulated in color this way.
At the end, I want to make an image from that shape array and print it to the screen:
arr_image = Image.fromarray(shape, 'RGB')
arr_image.save('test.jpg')
Details - What I tried:
No matter, what I do, I get pixel noise next to created pixels in images created using the example code!
I tried:
Searching the Internet/Stackoverflow: No such problem described/found
Taking working code from other examples and manipulate to my needs: Same artifacts occuring after manipulation
Drawing pixels in the created image after making a black-only (0,0,0) image from a numpy array by using Image method putpixel: Same artifacts appearing
Changing up syntax: Same artifacts appearing
Checking the underlying numpy arrays for grey values at the position of the artifacts: THERE IS NO SUCH GREY VALUES IN THE ARRAYS!!
When using Pillow method show(), the pixel noise is gone (!) in the windows popup, however (!!!), when I open the image from outside python, the pixel noise is in the exact same image visible (!!!!!!!!!!!!!)
Example code that produces this problem:
import numpy as np
from PIL import Image
shape = np.zeros((100, 100, 3), dtype=np.uint8)
white = (255, 255, 255)
shape[50][50] = white
arr_image = Image.fromarray(shape, 'RGB')
# arr_image.putpixel((50, 50), white)
arr_image.save('test.jpg')
Example images:
Black Array
[Same Array, but point 50][50] is set to white
[Same Array, but 100 random points were placed at x][y] and set to white
The solution to this is: Do not use .jpg data, the compression of this format causes observed pattern.
When using .png, the bug was instantly solved! In this image (.png), you can now see that there is 0 pixel noise nor strange artifact patterns, when generating an array-based image as described above: enter image description here
Thank you #dantechguy and #Mark Setchell, you guys saved me! :-))
Goal
I have hundreds of images that all look similar to this one here:
I simply want to use the green screen to create a mask for each image that looks like this one here (the border should preferably be smoothed out a little bit):
Here is the original image if you want to do tests: https://mega.nz/#!0YJnzAJR!GRYI4oNWcsKztHGoK7e4uIv_GvXBjMvyry7cPmyRpRA
What I've tried
I found this post where the user used Imagemagick to achieve chroma keying.
for i in *; do convert $i -colorspace HSV -separate +channel \
\( -clone 0 -background none -fuzz 3% +transparent grey43 \) \
\( -clone 1 -background none -fuzz 10% -transparent grey100 \) \
-delete 0,1 -alpha extract -compose Multiply -composite \
-negate mask_$i; done;
But no matter how I tweak the numbers, the results are not perfect:
I feel really dumb, that I cannot find a solution to such a simple problem myself. Also note, that I am using Linux. So no Photoshop or After Effects! :)
But I am sure that there has to be a solution to this problem.
Update 1
I've just tried using this greenscreen script by fmw42 by running ./greenscreen infile.jpg outfile.png and I am rather satisfied with the result.
But it takes around 40 seconds to process one image which results in a total 8 hours for all my images (although I have a rather power workstation, see specs below)
Maybe this has something to do witch those errors that occur while processing?:
convert-im6.q16: width or height exceeds limit `black' # error/cache.c/OpenPixelCache/3911.
convert-im6.q16: ImageSequenceRequired `-composite' # error/mogrify.c/MogrifyImageList/7995.
convert-im6.q16: no images defined `./GREENSCREEN.6799/lut.png' # error/convert.c/ConvertImageCommand/3258.
convert-im6.q16: unable to open image `./GREENSCREEN.6799/lut.png': No such file or directory # error/blob.c/OpenBlob/2874.
convert-im6.q16: ImageSequenceRequired `-clut' # error/mogrify.c/MogrifyImageList/7870.
convert-im6.q16: profile 'icc': 'RGB ': RGB color space not permitted on grayscale PNG `mask.png' # warning/png.c/MagickPNGWarningHandler/1667.
Workstation specs
Memory: 125,8 GiB
Processor: AMD® Ryzen 9 3900x 12-core processor × 24
Graphics: GeForce GTX 970/PCIe/SSE2 (two of them)
We know that the background is green and is distinguishable from the object by its color, so I suggest using color thresholding. For this, I have written a simple OpenCV Python code to demonstrate the results.
First, we need to install OpenCV.
sudo apt update
pip3 install opencv-python
# verify installation
python3 -c "import cv2; print(cv2.__version__)"
Then, we create a script named skull.py in the same directory with the images.
import cv2
import numpy as np
def show_result(winname, img, wait_time):
scale = 0.2
disp_img = cv2.resize(img, None, fx=scale, fy=scale)
cv2.imshow(winname, disp_img)
cv2.waitKey(wait_time)
img = cv2.imread('skull.jpg')
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# define range of green color in HSV
lower_green = np.array([70, 200, 100])
upper_green = np.array([90, 255, 255])
# Threshold the HSV image to extract green color
mask = cv2.inRange(hsv, lower_green, upper_green)
mask = cv2.bitwise_not(mask)
#cv2.imwrite('mask.png', mask)
show_result('mask', mask, 0)
cv2.destroyAllWindows()
You can easily find a tutorial about HSV color operations using OpenCV. I will not go over the functions used here, but one part is important. Image operations are generally done in RGB color space, which holds red, green and blue components. However, HSV is more like human vision system which holds hue, saturation and value components. You can find the conversion here. Since we seperate color based on our perception, HSV is more suitable for this task.
The essential part is to choose the threshold values appropriately. I chose by inspection around 80 for hue (which is max. 180), and above 200 and 100 for saturation and value (max. 255), respectively. You can print the values of a particular pixel by the following lines:
rows,cols,channels = hsv.shape
print(hsv[row, column])
Note that the origin is left upper corner.
Here is the result:
Two things may be needed. One is doing the operation for a set of images, which is trivial using for loops. The other is that if you do not like some portion of the result, you may want to know the pixel location and change the threshold accordingly. This is possible using mouse events.
for i in range(1, 100):
img = imread(str(i) + '.jpg')
def mouse_callback(event, x, y, flags, params):
if event == cv2.EVENT_LBUTTONDOWN:
row = y
column = x
print(row, column)
winname = 'img'
cv2.namedWindow(winname)
cv2.setMouseCallback(winname, mouse_callback)
Keep in mind that show_result function resizes the image by scale factor.
If you do not want to deal with pixel positions, rather you want smooth results, you can apply morphological transformations. Especially opening and closing will get the work done.
kernel = np.ones((11,11), np.uint8)
opened = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
closed = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)
Result with opening (kernel=11x11):
I can't really fit this in a comment, so I've put it as an answer. If you want to use Fred's greenscreen script, you can hopefully use GNU Parallel to speed it up.
Say you use the commands:
mkdir out
greenscreen image.png out/image.png
to process one image, and you have thousands, you can do the following to keep all your CPU cores busy in parallel till they are all processed:
mkdir out
parallel greenscreen {} out/{} ::: *.png
If on a Unix-like system, you can try my greenscreen script that makes calls to ImageMagick and is written in Bash Unix. For example:
Input:
greenscreen img.jpg result.png
Result (green turned transparent):
The result has been reduced in size by 50%, just so that StackOverflow will not object to the original result being too large. However, StackOverflow has changed the image from transparent PNG to white background JPG.
Note that other images, may need values for the arguments other than the defaults. You can get my script at http://www.fmwconcepts.com/imagemagick/. Note that for commercial use, you will need to contact me about licensing.
I am using matplotlib to read an image, but the number of channels changes after I save the original image with imsave. Here is the code:
import matplotlib.image as mpimg
img = mpimg.imread('sample.tiff')
print(img.shape)
mpimg.imsave('sample2.tiff', img)
img2 = mpimg.imread('sample2.tiff')
print(img2.shape)
And here is the output:
(2160, 2160)
(2160, 2160, 4)
The image becomes a 4-channel image while it was 1-channel originally. And it seems that the final channel is always 255.
What is happening here? And the original image looks meaning less as it is all black. But when I read & save it with imread and imsave, I can finally see some meaningful figures.
The input image sample.tiff is a one channel gray scale image. One cannot know why that is the case, it simply depends on where you got that image from.
imread converts this image to a 2D numpy array.
When given a 2D numpy array as input imsave will apply a colormap to the array, and, without further arguments given, apply a normalization between the minimum and maximum data value. The resulting image is hence a color image with 4 channels.
imread then converts this image to a 3D numpy array.
It looks like you are not the first person to have this problem - see here.
My suggestion would be to use imageio (or PIL) to save the image (in fact, to read it too) and it works fine:
import imageio
import matplotlib.image as mpimg
img = mpimg.imread('a.tif')
imageio.imwrite('result.tif',img)
I have a 565 * 584 image as shown
I want to reduce the radius of the circle by certain number of pixels without changing the size of the image. How can I do it? Please explain or give some ideas. Thank You.
I would use ImageMagick and an erosion like this:
convert http://i.stack.imgur.com/c8lfe.jpg -morphology erode octagon:8 out.png
If you know that the background of the image is a constant, as in your example, this is easy.
Resize the entire image by the ratio you wish to shrink by. Then create a new image at the size of the original and fill it with the background color, then paste the resized image into the center of it.
Here's how you'd do it in OpenCV Python. Going with Mark Setchell's approach, simply specify a round structuring element so that you can maintain or respect the round edges of the object. The closest thing that OpenCV has to offer is the elliptical mask.
As such:
import numpy as np # Import relevant packages - numpy and OpenCV
import cv2
# Read in image and threshold - convert to grayscale first
im = cv2.imread('c8lfe.jpg', 0) > 128
# Specify radius of ellipse
radius = 21
# Obtain structuring element, then erode image
se = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (radius, radius))
# Make sure you convert back to grayscale and multiply by 255
out = 255*(cv2.erode(im, se).astype('uint8'))
# Show the image, wait for user key, then close window and write image
cv2.imshow('Reduced shape', out)
cv2.waitKey(0)
cv2.destroyAllWindows()
cv2.imwrite('out.png', out)
We get:
Be advised that the small bump at the top right corner of your shape will mutate. As we are essentially shrinking the perimeter of the object, that bump will also shrink as well. If you wish to preserve the structure of the object while maintaining the image resolution, use Mark Ransom's approach or my slightly modified version of his approach. Both are shown below.
However, to be self-contained, we can certainly do what Mark Ransom has suggested. Resize the image, initialize a blank image that is size of the original image, and place it in the centre:
import numpy as np # Import relevant packages - OpenCV and Python
import cv2
im = cv2.imread('c8lfe.jpg', 0) # Read in the image - grayscale
scale_factor = 0.75 # Set scale factor - We are shrinking the image by 25%
# Get the desired size (row and columns) of the shrunken image
desired_size = np.floor(scale_factor*np.array(im.shape)).astype('int')
# Make sure desired size is ODD for easier placement
if desired_size[0] % 2 == 0:
desired_size[0] += 1
if desired_size[1] % 2 == 0:
desired_size[1] += 1
# Resize the image. Columns come first, followed by rows, which is why we
# reverse the desired_size array
rsz = cv2.resize(im, tuple(desired_size[::-1]))
# Determine half width of both dimensions of shrunken image
half_way = np.floor(desired_size/2.0).astype('int')
# Create output image that is the same size as the input and find its centre
out = np.zeros_like(im, dtype='uint8')
centre = np.floor(np.array(im.shape)/2.0).astype('int')
# Place shrunken image in the centre of the larger output image
out[centre[0]-half_way[0]:centre[0]+half_way[0]+1, centre[1]-half_way[1]:centre[1]+half_way[1]+1] = rsz
# Show the image, wait for user key, then close window and write image
cv2.imshow('Reduced shape', out)
cv2.waitKey(0)
cv2.destroyAllWindows()
cv2.imwrite('out.png', out)
We get:
Another suggestion
What I can also recommend you do is pad the array with zeroes, then reshrink the image back to its original size. You would essentially extend the borders of the original image so that the borders contain zeroes. In this case, we would do what Mark Ransom also suggested, but we are working within the inside, out.
Here's the way to pad a matrix with zeroes using OpenCV C++: Pad array with zeros- openCV . However, in Python, simply use numpy's pad function:
import numpy as np # Import relevant packages - numpy and OpenCV
import cv2
# Read in image and threshold - convert to grayscale first
im = cv2.imread('c8lfe.jpg', 0)
# Set how many pixels along the border you want to add on each side
pad_radius = 75
# Pad the image
out = np.lib.pad(im, ((pad_radius, pad_radius), (pad_radius, pad_radius)), 'constant', constant_values=((0,0),(0,0)))
# Shrink it back to what the original size was
out = cv2.resize(out, im.shape[::-1])
# Show the image, wait for user key, then close window and write image
cv2.imshow('Reduced shape', out)
cv2.waitKey(0)
cv2.destroyAllWindows()
cv2.imwrite('out.png', out)
We thus get:
Img is a dtype=float64 numpy data type. When I run this code:
Img2 = np.array(Img, np.uint8)
the background of my images turns white. How can I avoid this and still get an 8-bit image?
Edit:
Sure, I can give more info. The single image is compiled from a stack of 400 images. They are each coming from an .avi video file, and each image is converted into a NumPy array like this:
gray_img = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
A more complicated operation is performed on this whole stack, but does not involve creating new images. It's simply performing calculations on each 1D array to yield a single pixel.
The interpolation is most likely linear (the default in plotting images with matplotlib. The images were saved as .PNGs.
You probably see overflow. If you cast 257 to np.uint8, you will get 1. According to a google search, avi files contain images with a color depth of 15 - 24 bit. When you cast this depth to np.uint8, you will see white regions getting darkened and (if a normalization takes place somewhere) also dark regions getting white (-5 -> 251). For the regions that become bright, you could check whether you have negative pixel values in the original image Img.
The Docs say that sometimes you have to do some scaling to get a proper cast, and to rather use higher depth whenever possible to avoid artefacts.
The solution seems to be either working at higher depth, i.e. casting to np.uint16 or np.uint32, or to scale the pixel values before reducing the depth, i.e. with Img2 already being a numpy matrix
# make sure that values are between 0 and 255, i.e. within 8bit range
Img2 *= 255/Img2.max()
# cast to 8bit
Img2 = np.array(Img, np.uint8)