Find clipped pixels in a RGB image - python

I have an image image.png and I want to find all clipped pixels. Here is what I have so far:
for i in range(1,width):
for j in range(1, height):
r,g,b = image.getpixel((i,j))
If( ): # I don't know what should be the condition here
# do something else
I use Python, Tkinter, Pil.
Thanks

If by 'clipped' you mean saturated, then you probably want to create a threshold based on the intensity of the pixel. There are a few equations that try to determine this, but I would recommend one of the Grayscale equations. Looking at the equation used in ATSC:
I=.2126*r+.7152*g+.0722*b
Then just figure out what range of values for I you considered 'clipped'.

Related

Comparing and plotting regions of the same color over a dataset of a few hundred images

A chem student asked me for help with plotting image segmenetation:
A stationary camera takes a picture of the experimental setup every second over a period of a few minutes, so like 300 images yield.
The relevant parts in the setup are two adjacent layers of differently-colored foams observed from the side, a 2-color sandwich shrinking from both sides, basically, except one of the foams evaporates a bit faster.
I'd like to segment each of the images in the way that would let me plot both foam regions' "width" against time.
Here is a "diagram" :)
I want to go from here --> To here
Ideally, given a few hundred of such shots, in which only the widths change, I get an array of scalars back that I can plot. (Going to look like a harmonic series on either side of the x-axis)
I have a bit of python and matlab experience, but have never used OpenCV or Image Processing toolbox in matlab, or actually never dealt with any computer vision in general. Could you guys throw like a roadmap of what packages/functions to use or steps one should take and i'll take it from there?
I'm not sure how to address these things:
-selecting at which slice along the length of the slice the algorithm measures the width(i.e. if the foams are a bit uneven), although this can be ignored.
-which library to use to segment regions of the image based on their color, (some k-means shenanigans probably), and selectively store the spatial parameters of the resulting segments?
-how to iterate that above over a number of files.
Thank you kindly in advance!
Assume your Intensity will be different after converting into gray scale ( if not, just convert to other color space like HSV or LAB, then just use one of the components)
img = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
First, Threshold your grayscaled input into a few bands
ret,thresh1 = cv.threshold(img,128,255,cv.THRESH_BINARY)
ret,thresh2 = cv.threshold(img,27,255,cv.THRESH_BINARY_INV)
ret,thresh3 = cv.threshold(img,77,255,cv.THRESH_TRUNC)
ret,thresh4 = cv.threshold(img,97,255,cv.THRESH_TOZERO)
ret,thresh5 = cv.threshold(img,227,255,cv.THRESH_TOZERO_INV)
The value should be tested out by your actual data. Here Im just give a example
Clean up the segmented image using median filter with a radius larger than 9. I do expect some noise. You can also use ROI here to help remove part of noise. But personally I`m lazy, I just wrote program to handle all cases and angle
threshholed_images_aftersmoothing = cv2.medianBlur(threshholed_images,9)
Each band will be corresponding to one color (layer). Now you should have N segmented image from one source. where N is the number of layers you wish to track
Second use opencv function bounding rect to find location and width/height of each Layer AKA each threshholed_images_aftersmoothing. Eg. boundingrect on each sub-segmented images.
C++: Rect boundingRect(InputArray points)
Python: cv2.boundingRect(points) → retval¶
Last, the rect have x,y, height and width property. You can use a simple sorting order to sort from top to bottom layer based on rect attribute x. Run though all vieo to obtain the x(layer id) , height vs time graph.
Rect API
Public Attributes
_Tp **height** // this is what you are looking for
_Tp width
_Tp **x** // this tells you the position of the band
_Tp y
By plot the corresponding heights (|AB| or |CD|) over time, you can obtain the graph you needed.
The more correct way is to use Kalman filter to track the position and height graph as I would expect some sort of bubble will occur and will interfere with the height of the layers.
To be honest, i didnt expect a chem student to be good at this. Haha good luck
Anything wrong you can find me here or Email me if i`m not watching stackoverflow
You can select a region of interest straight down the middle of the foams, a few pixels wide. If you stack these regions for each image it will show the shrink over time.
If for example you use 3 pixel width for the roi, the result of 300 images will be a 900 pixel wide image, where the left is the start of the experiment and the right is the end. The following image can help you understand:
Though I have not fully tested it, this code should work. Note that there must only be images in the folder you reference.
import cv2
import numpy as np
import os
# path to folder that holds the images
path = '.'
# dimensions of roi
x = 0
y = 0
w = 3
h = 100
# store references to all images
all_images = os.listdir(path)
# sort images
all_images.sort()
# create empty result array
result = np.empty([h,0,3],dtype=np.uint8)
for image in all_images:
# load image
img = cv2.imread(path+'/'+image)
# get the region of interest
roi = img[y:y+h,x:x+w]
# add the roi to previous results
result = np.hstack((result,roi))
# optinal: save result as image
# cv2.imwrite('result.png',result)
# display result - can also plot with matplotlib
cv2.imshow('Result', result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Update after question edit:
If the foams have different colors, your can use easily separate them by color by converting the image you hsv and using inrange (example). This creates a mask (=2D array with values from 0-255, one for each pixel) that you can use to calculate average height and extract the parameters and area of the image.
You can find a script to help you find the HSV colors for separation on this GitHub

Image Positioning OpenCV

This may be called "Region of Interest" I'm not exactly sure. But, what I'd like to do is rather easy to explain.
I have a photo that I need to align to a grid.
https://snag.gy/YaAWdg.jpg
For starters; the little text that says "here" must be 151px from the top of the screen.
Next; from "here" to position 8 of the chin must be 631px
Finally; a straight line down the middle of the picture at line 28 on the nose must be done.
If i'm not making sense please tell me to elaborate.
I have the following ideas (this is pseudo code)
It is simply to loop until the requirements are met with a resize function, a lot like brute forcing but thats all i can think of..
i.e..
while (top.x,y = 151,0)
img.top-=1 ## this puts the image one pixel above until reaching the desired positioning
while (top.x,y & eight.x,y != 631)
resize += 1 # resize by 1 pixel until the height is reached
## center the nose
image.position = nose.
Consider switching the order of your operations to prevent the need to iterate. A little bit of math and a change of perspective should do the trick:
1.) Resize the image such that the distance from "here" to the chin is 631px.
2.) Use a region of interest to crop your image so that "here" is 151px from the top of the screen.
3.) Draw your line.
EDIT:
The affine transform in OpenCV would work to morph your image into the proper fill, assuming that you have all the proper constraints defined.
If all you need to do is a simple scale... First calculate the distance between points, using something like this.
Point2f a(10,10);
Point2f b(100,100);
float euclideanDist(Point& p, Point& q) {
Point diff = p - q;
return cv::sqrt(diff.x*diff.x + diff.y*diff.y);
}
Then create a scale factor to resize your image
float scaleFactor = euclideanDist(a,b) / 631;
cv::resize(input, output, cv::Size(), scaleFactor, scaleFactor, cv::INTER_LINEAR);
Using both instances of scaleFactor will create a uniform scaling in X&Y. Using two different scale factors will scale X and Y independently.
Take a look to OpenCV's tutorial, for images with faces you can use Haar Cascades to simplify the work. (https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_objdetect/py_face_detection/py_face_detection.html#face-detection)
Otherwise, look at ROI (Region of Interest) to extract an area and apply your algorithm on it (resize or crop)

How to find mean value for a given part of an Image

I have "n" number of contour detected images(frame). I wants to find mean value for the rectangle portion of that image. (Instead of finding mean value for a full image, i need to calculate the mean value for the rectangle portion of that image.)
I have rectangle's x,y position and width, height values. First Image x,y,w,h is 109,45 ,171,139 and second image x,y,w,h is 107,71,175,110. I get the values using the below code. cv2.rectangle(frame, (x,y),(x+w,y+h), (0,0,255), 3) I know using "ROI" concept we can do mean calculation. So, I referred some links. Ex. Get the ROI of two binary images and find difference of the mean image intesities between 2 ROI in python. But, I have confused with the parameter settings. Can anyone help me to resolve my problem ? Thanks in advance...
There's easier way to get rectangle from an image in Python. Since cv2 operates on NumPy arrays, you can use normal slicing (note, that i corresponds to y and j - to x, not the other way):
rect = image[i:i+h, j:j+w]
And taking mean is even simpler:
rect.mean()

guessing the rgb gradient from a map?

I have a map with a scale like this one: (the numbers are just an example)
which describes a single variable on a map. However, I don't have access to the original data and know pretty close to nothing
about image processing. What I have done is use PIL to get the pixel-coordinates and RGB values of each point on the map. Simply using pix = im.load() and saving pix[x,y] for each x,y. Now I would like to guess the value of each point using the gradient above.
Is there a standard formula for such a gradient? Does it look very familiar to the trained eye? I have visited Digital Library of Mathematical Functions for some examples ... but I'm not sure if it's using the hue, the rgb height function or something else (to make things easier I'm also colorblind to some greens/brows/reds) :)
Any tips on how to proceed, libraries, links or ideas are appreciated. Thank you!
edit:
Following the replies and martineau's suggestion, I've tried to catch the colors at the top and bottom:
def rgb2hls(colotup):
'''converts 225 based RGB to 360 based HLS
`input`: (222,98,32) tuple'''
dec_rgb = [x/255.0 for x in colotup] # use decimal 0.0 - 1.0 notation for RGB
hsl_col = colorsys.rgb_to_hls(dec_rgb[0], dec_rgb[1], dec_rgb[2])
# PIL uses hsl(360,x%,y%) notation and throws errors on float, so I use int
return (int(hsl_col[0]*360), int(hsl_col[1]*100), int(hsl_col[2]*100))
def pil_hsl_string(hsltup):
'''returns a string PIL can us as HSL color
from a tuple (x,y,z) -> "hsl(x,y%,z%)"'''
return 'hsl(%s,%s%%,%s%%)' % (hsltup[0], hsltup[1], hsltup[2])
BottomRed = (222,98,32) # taken with gimp
TopBlue = (65, 24, 213)
hue_red = pil_hsl_string(rgb2hls(BottomRed))
hue_blue = pil_hsl_string(rgb2hls(TopBlue))
However they come out pretty different ... which makes me worry about using the rgb_to_hls function to extract the values. Or I'm I doing something very wrong? Here's what the color s convert to with the code:
Interesting question..
If you do a clock-wise walk in HSL color-space from 250,85%,85% --> 21,85%,85% you get a gradient very close to the one you've shown. The obvious difference being that your image exhibits a fairly narrow band of greenish values.
So, if you have the 4 magic numbers then you can interpolate to any point within the map.
These of course being the first and last colour, also the first and last scale value.
Here's the image I got with a straight linear gradient on the H channel (used the gimp).
EDIT: I've since whipped up a program to grab the pixel values for each row, graphing the results. You can see that indeed, the Hue isn't linear, you can also see the S & V channels taking a definite dip at around 115 (115 pixels from top of image) This indeed corresponds with the green band.
Given the shape of the curves, I'm inclined to think that perhaps they are intended to model something. But don't have the experience in related fields to recognise the shape of the curves.
Below, I've added the graphs for the change in both the HSV and RGB models.
The left of the graph represents the top of the bar.
The X-axis labels represent pixels
Quite interesting, me thinks. Bookmarked.
The scale in the image looks like an HSV gradient to me, something like what is mentioned in this question. If so, you could use the colorsys.rgb_to_hls() or colorsys.rgb_to_hsv() functions to obtain a hue color value between 0 and 1 from the r,g,b values in a pixel. That can then be mapped accordingly.
However, short of doing OCR, I have no idea how to determine the range of values being represented unless it's some consistent range you can just hardcode.
I would recomend to define an area where you want to compare the colour. Take an FFT of the regions. Each colour is defined by a frequency. You do the same on the countour scale. then compare and narrow on a value.
I have found some like to understand it better.
http://www.imagemagick.org/Usage/fourier/
You can get something like that by varying the hue with a fixed saturation and luminance.
http://en.wikipedia.org/wiki/HSL_and_HSV

How to check if an RGB image contains only one color?

I'm using Python and PIL.
I have images in RGB and I would like to know those who contain only one color (say #FF0000 for example) or a few very close colors (#FF0000 and #FF0001).
I was thinking about using the histogram but it is very hard to figure out something with the 3 color bands, so I'm looking for a more clever algorithm.
Any ideas?
ImageStat module is THE answer! Thanks Aaron.
I use ImageStat.var to get the variance and it works perfectly.
Here is my piece of code:
from PIL import Image, ImageStat
MONOCHROMATIC_MAX_VARIANCE = 0.005
def is_monochromatic_image(src):
v = ImageStat.Stat(Image.open(src)).var
return reduce(lambda x, y: x and y < MONOCHROMATIC_MAX_VARIANCE, v, True)
Try the ImageStat module. If the values returned by extrema are the same, you have only a single color in the image.
First, you should define a distance between two colors.
Then you just have to verify for each pixel that it's distance to your color is small enough.
Here's a little snippet you could make use of :
import Image
im = Image.open("path_to_image")
width,height = im.size
for w in range(0,width):
for h in range(0,height):
# this will hold the value of all the channels
color_tuple = im.getpixel((w,h))
# do something with the colors here
Maybe use a hash and store the tuples as the key and it's number of appearances as value?

Categories

Resources