RGB to Grayscale (Average Method) Python - python

I'm supposed to write a method that converts an RGB image to Grayscale by using the "average method" where I take the average of the 3 colors (not the weighted method or luminosity method). I then must display the original RGB image and grayscale image next to each other (concatenated). The language I'm writing in is Python. This is what my code looks like currently.
import numpy as np
import cv2
def getRed(redVal):
return '#%02x%02x%02x' % (redVal, 0, 0)
def getGreen(greenVal):
return '#%02x%02x%02x' % (0, greenVal, 0)
def getBlue(blueVal):
return '#%02x%02x%02x' % (0, 0, blueVal)
# Grayscale = (R + G + B / 3)
# For each pixel,
# 1- Get pixels red, green, and blue
# 2- Calculate the average value
# 3- Set each of red, green, and blue values to average value
def average_method(img):
for p in img:
red = p.getRed()
green = p.getGreen()
blue = p.getBlue()
average = (red + green + blue) / 3
p.setRed(average)
p.setGreen(average)
p.setBlue(average)
def main():
img1 = cv2.imread('html/images/sun.jpeg')
img1 = cv2.resize(img1, (0, 0), None, .50, .50)
img2 = average_method(img1)
img2 = np.stack(3 * [img2], axis=2)
numpy_concat = np.concatenate((img1, img2), 1)
cv2.imshow('Numpy Concat', numpy_concat)
cv2.waitKey(0)
cv2.destroyAllWindows
if __name__ =="__main__":
main()
The portion that is commented within the average_method function is the steps that I must follow.
When I try to run the code, I get
File "test.py", line 38, in <module>
main()
File "test.py", line 30, in main
img2 = average_method(img1)
File "test.py", line 15, in average_method
red = p.getRed()
AttributeError: 'numpy.ndarray' object has no attribute 'getRed'
I thought that defining the functions for getRed, getGreen, and getBlue up above would mean they'd become recognizable in my average_method function (I got those functions from online so I hope they're right). I'm also not sure what it has to do with numpy.ndarray. If anyone could help me fill in this average_method function with code that follows the commented steps correctly, I would really appreciate it.
EDIT:::
New code looks like this:
import cv2
import numpy as np
def average_method(img):
for p in img:
gray = sum(p)/3
for i in range(3):
p[i] = gray
def main():
img1 = cv2.imread('html/images/sun.jpeg')
img1 = cv2.resize(img1, (0, 0), None, .50, .50)
img2 = average_method(img1)
img2 = np.stack(3 * [img2], axis=2)
numpy_concat = np.concatenate((img1, img2), 1)
cv2.imshow('Numpy Concat', numpy_concat)
cv2.waitKey(0)
cv2.destroyAllWindows
if __name__ =="__main__":
main()
I now get the error
File "test.py", line 50, in <module>
main()
File "test.py", line 43, in main
img2 = np.stack(3 * [img2], axis=2)
File "<__array_function__ internals>", line 5, in stack
File "C:\Users\myname\AppData\Local\Programs\Python\Python38-32\lib\site-packages\numpy\core\shape_base.py", line 430, in stack
axis = normalize_axis_index(axis, result_ndim)
numpy.AxisError: axis 2 is out of bounds for array of dimension 1
I have that line "img2 = np.stack(3 * [img2], axis=2)" since I was previously told on Stack Overflow I need it due to my img2 now being a greyscale (single-channel) image, when img1 is still color (three-channel). This line apparently fixes that. But now it seems like there is something wrong with that?

In Java, the for loop you highlighted is called an "enhanced for loop". Python doesn't have these because Python for loops pog (in terms of concision).
The Python equivalent of the line in question would be:
for p in img:
No need to state object class or anything like that.
EDIT: After OP changed question
The problem now is that you're not calling the functions correctly. p is an array containing the RGB values for that pixel. To call the function as you defined above do:
for p in img:
red = getRed(p[0])
green = getGreen(p[1])
blue = getBlue(p[2])
average = (red + green + blue) / 3
p[0] = average
p[1] = average
p[2] = average
Remember when you moved the code to Python, you seem to no longer be working in Object Oriented Programming! Pixels don't come with methods that you can call like that anymore.
However, as pointed out by Guimoute in the comments, the code can be much simpler if you get rid of the get[Color] functions and do the following:
for p in img:
gray = sum(p)/3
for i in range(3):
p[i] = gray

Related

"global name 'range_iter_len' is not defined" when using Numba

I was working with massive image manipulation using OpenCV and have the idea to use GPU instead of CPU to speed up the computation. I'm using Pointillism to manipulate the image and it involves quite some math, it took significant time to finish processing each image. Here's the code:
#jit
def toPointillismPainting(img):
stroke_scale = int(math.ceil(max(img.shape) / 250))
#print("Automatically chosen stroke scale: %d" % stroke_scale)
gradient_smoothing_radius = int(round(max(img.shape) / 50))
#print("Automatically chosen gradient smoothing radius: %d" % gradient_smoothing_radius)
# convert the image to grayscale to compute the gradient
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
#print("Computing color palette...")
palette = ColorPalette.from_image(img, 20)
#print("Extending color palette...")
palette = palette.extend([(0, 50, 0), (15, 30, 0), (-15, 30, 0)])
#print("Computing gradient...")
gradient = VectorField.from_gradient(gray)
#print("Smoothing gradient...")
gradient.smooth(gradient_smoothing_radius)
#print("Drawing image...")
# create a "cartonized" version of the image to use as a base for the painting
res = cv2.medianBlur(img, 11)
# define a randomized grid of locations for the brush strokes
grid = randomized_grid(img.shape[0], img.shape[1], scale=3)
batch_size = 10000
#bar = progressbar.ProgressBar()
for h in range(0, len(grid), batch_size):
# get the pixel colors at each point of the grid
pixels = np.array([img[x[0], x[1]] for x in grid[h:min(h + batch_size, len(grid))]])
# precompute the probabilities for each color in the palette
# lower values of k means more randomnes
color_probabilities = compute_color_probabilities(pixels, palette, k=9)
for i, (y, x) in enumerate(grid[h:min(h + batch_size, len(grid))]):
color = color_select(color_probabilities[i], palette)
angle = math.degrees(gradient.direction(y, x)) + 90
length = int(round(stroke_scale + stroke_scale * math.sqrt(gradient.magnitude(y, x))))
# draw the brush stroke
cv2.ellipse(res, (x, y), (length, stroke_scale), angle, 0, 360, color, -1, cv2.LINE_AA)
return res
This returned me an error which usually didn't happen when I'm not using #jit decorator.
global name 'range_iter_len' is not defined
Traceback (most recent call last):
File "[projectname]", line 201, in <module>
res = toPointillismPainting(img)
NameError: global name 'range_iter_len' is not defined
removed temporary images
When debugging, I realized that this range_iter_len variable is nowhere used in my project nor the Pointillism code itself. And I only found a single github issue on Numba and it doesn't seem to have similar root cause. Any help would be appreciated, and I will update any important points I missed or when I found the solution.

Can I use cv2 or Numpy to Invert this picture?

Can I use cv2 or numpy to turn an image into a negative? Something like below but I need to edit still.
My question is mainly the top bit of code if I can use that to invert the grayscale and black&white both to a negative?
import cv2
import numpy as np
img = cv2.imageread('imagename.jpg')
print(img.dtype)
image_neg = 255 - img
cv2.imshow('negative',image_neg)
cv2.waitKey(0)
#######################################
from images import Image
def invert(image):
def blackAndWhite(image):
blackPixel = (0, 0, 0)
whitePixel = (255, 255, 255)
for y in range(image.getHeight()):
for x in range(image.getWidth()):
(r, g, b) = image.getPixel(x, y)
average = (r + g + b) // 3
if average < 128:
image.setPixel(x, y, blackPixel)
else:
image.setPixel(x, y, whitePixel)
def grayscale(image):
for y in range(image.getHeight()):
for x in range(image.getWidth()):
(r, g, b) = image.getPixel(x, y)
r = int(r * 0.299)
g = int(g * 0.587)
b = int(b * 0.114)
lum = r + g + b
image.setPixel(x, y, (lum, lum, lum))
def main():
filename = input("Enter the image file name: ")
image = Image(filename)
#Invert image
invert(image)
image.draw()
#Covert to greyscale, then invert
"""grayscale(image)
invert(image)
image.draw()"""
#Convert to black and white, then invert
"""blackAndWhite(image)
invert(image)
image.draw()"""
if __name__ == "__main__":
main()
I receive the following error:
Traceback (most recent call last):
File "invert.py", line 14, in <module>
image_neg = 255 - image
NameError: name 'image' is not defined
I changed the code in the beginning to say this:
import cv2
import numpy as np
image = cv2.imageread('smokey.gif')
print(image.dtype)
image_neg = 255 - image
cv2.imshow('negative',image_neg)
cv2.waitKey(0)
Well I thought this would work but it tells me line - "invertedImage = cv2.bitwise_not(imageToInvert)" has a SyntaxError: invalid non-printable character U+00A0
I edited my code correctly on here (4 spaces) and I have no clue why it's not showing correctly still.
from images import Image
import cv2
def invert(image):
imageToInvert = cv2.imread(filepath)
invertedImage = cv2.bitwise_not(imageToInvert)
cv2.imgwrite("BWimage.png",invertedImage)
print("inverted image saved")
File_path='smokey.gif'
invert(File_path)
Not sure what error you are getting. Maybe something here will help?
Syntax: cv2.cv.flip(src, flipCode[, dst] )
Parameters:
src: Input array.
dst: Output array of the same size and type as src.
flip code: A flag to specify how to flip the array; 0 means flipping around the x-axis and positive value (for example, 1) means flipping around y-axis. Negative value (for example, -1) means flipping around both axes.
Return Value: It returns an image.
As found in OpenCV
example code:
# Python program to explain cv2.flip() method
# importing cv2
import cv2
# path
path = r'C:\Users\user\Desktop\geeks14.png'
# Reading an image in default mode
src = cv2.imread(path)
# Window name in which image is displayed
window_name = 'Image'
# Using cv2.flip() method
# Use Flip code 0 to flip vertically
image = cv2.flip(src, 0)
# Displaying the image
cv2.imshow(window_name, image)
cv2.waitKey(0)

NoneType error when using cv2.HoughLinesP function put by images

First of all I am putting values into hough_lines such as rho = 2, theta = np.pi/180, threshold = 15, min_line_len = 40 , max_line_gap = 20
lines = hough_lines(masked_edges, rho, theta, threshold, min_line_len, max_line_gap)
def hough_lines(img, rho, theta, threshold, min_line_len, max_line_gap):
#This function is used for drawing line when we give a specific criteria into the pixels we want to pick up.
#This function is used with draw_lines function to draw the lines in specific pixels
#which are drawn by region_of_interest function.
"""`img` should be the output of a Canny transform.
Returns an image with hough lines drawn.
"""
lines = cv2.HoughLinesP(img, rho, theta, threshold, np.array([]), minLineLength=min_line_len, maxLineGap=max_line_gap)
line_img = np.zeros((img.shape[0], img.shape[1], 3), dtype=np.uint8)
draw_lines(line_img, lines)
return line_img
Above is my code and getting following error.
File "auto.py", line 68, in <module>
output = lane.process_image(frame)
File "/home/pi/test/lane.py", line 227, in process_image
lines = hough_lines(masked_edges, rho, theta, threshold, min_line_len, max_line_gap)
File "/home/pi/test/lane.py", line 154, in hough_lines
draw_lines(line_img, lines)
File "/home/pi/test/lane.py", line 97, in draw_lines
for line in lines:
TypeError: 'NoneType' object is not iterable
In the main function, I am trying to put the frame taken by video capture into process_image function in lane file.
cam = cv2.VideoCapture(0)
while True:
print('Succeed to connect...')
data = ''
data=sys.stdin.read(1)[0]
print("data input=",data)
while True:
if not cam.isOpened():
print("Wait for the header")
else:
flag, frame = cam.read()
frame = cv2.flip(frame,1)
cv2.imshow('video', frame)
#print(type(frame))
output = lane.process_image(frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
exit()
I tried many things such that I changed parameter values for hough_lines function and tried to see what functions affects NoneType value in terms of image. However, before cv2.HoughLinesP function, every function has their own value with narray type.
--edit
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import numpy as np
import cv2
import math
import os
def grayscale(img): # It converts the original picture to the gray scale picture.
"""Applies the Grayscale transform
This will return an image with only one color channel
but NOTE: to see the returned image as grayscale
(assuming your grayscaled image is called 'gray')
you should call plt.imshow(gray, cmap='gray')"""
return cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
# Or use BGR2GRAY if you read an image with cv2.imread()
# return cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
def canny(img, low_threshold, high_threshold):
#After applied grayscale function, it converts the grayscale image to edges
#which is a binary image with white pixels.
"""Applies the Canny transform"""
return cv2.Canny(img, low_threshold, high_threshold)
def gaussian_blur(img, kernel_size):
#Gaussian blur is applied for suppressing noise and nonlogical gradients by averaging.
"""Applies a Gaussian Noise kernel"""
return cv2.GaussianBlur(img, (kernel_size, kernel_size), 0)
def region_of_interest(img, vertices):
# This functions is used for tracing a specific line of the road
# utilizing vertices which is are 4 integer points here and ignore_mask_color which ignores
# pixels if those do not meet the criteria.
"""
Applies an image mask.
Only keeps the region of the image defined by the polygon
formed from `vertices`. The rest of the image is set to black.
`vertices` should be a numpy array of integer points.
"""
#defining a blank mask to start with
mask = np.zeros_like(img)
#defining a 3 channel or 1 channel color to fill the mask with depending on the input image
if len(img.shape) > 2:
channel_count = img.shape[2] # i.e. 3 or 4 depending on your image
ignore_mask_color = (255,) * channel_count
else:
ignore_mask_color = 255
#filling pixels inside the polygon defined by "vertices" with the fill color
cv2.fillPoly(mask, vertices, ignore_mask_color)
#returning the image only where mask pixels are nonzero
masked_image = cv2.bitwise_and(img, mask)
return masked_image
def draw_lines(img, lines, color=[255, 0, 0], thickness=2):
"""
NOTE: this is the function you might want to use as a starting point once you want to
average/extrapolate the line segments you detect to map out the full
extent of the lane (going from the result shown in raw-lines-example.mp4
to that shown in P1_example.mp4).
Think about things like separating line segments by their
slope ((y2-y1)/(x2-x1)) to decide which segments are part of the left
line vs. the right line. Then, you can average the position of each of
the lines and extrapolate to the top and bottom of the lane.
This function draws `lines` with `color` and `thickness`.
Lines are drawn on the image inplace (mutates the image).
If you want to make the lines semi-transparent, think about combining
this function with the weighted_img() function below
"""
"""
for line in lines:
for x1,y1,x2,y2 in line:
cv2.line(img, (x1, y1), (x2, y2), color, thickness)
"""
right_slopes = []
right_intercepts = []
left_slopes = []
left_intercepts = []
left_points_x = []
left_points_y = []
right_points_x = []
right_points_y = []
y_max = img.shape[0]
y_min = img.shape[0]
for line in lines:
for x1,y1,x2,y2 in line:
slope = (y2-y1)/(x2-x1)
if slope < 0.0 and slope > -math.inf: # math.inf = floating point positive infinity
left_slopes.append(slope) # left line
left_points_x.append(x1)
left_points_x.append(x2)
left_points_y.append(y1)
left_points_y.append(y2)
left_intercepts.append(y1 - slope*x1)
if slope > 0.0 and slope < math.inf:
right_slopes.append(slope) # right line
right_points_x.append(x1)
right_points_x.append(x2)
right_points_y.append(y1)
right_points_y.append(y2)
right_intercepts.append(y1 - slope*x1)
y_min = min(y1,y2,y_min)
if len(left_slopes) > 0:
left_slope = np.mean(left_slopes)
left_intercept = np.mean(left_intercepts)
x_min_left = int((y_min - left_intercept)/left_slope)
x_max_left = int((y_max - left_intercept)/left_slope)
cv2.line(img, (x_min_left, y_min), (x_max_left, y_max), [255, 0, 0], 8)
if len(right_slopes) > 0:
right_slope = np.mean(right_slopes)
right_intercept = np.mean(right_intercepts)
x_min_right = int((y_min - right_intercept)/right_slope)
x_max_right = int((y_max - right_intercept)/right_slope)
cv2.line(img, (x_min_right, y_min), (x_max_right, y_max), [255, 0, 0], 8)
def hough_lines(img, rho, theta, threshold, min_line_len, max_line_gap):
#This function is used for drawing line when we give a specific criteria into the pixels we want to pick up.
#This function is used with draw_lines function to draw the lines in specific pixels
#which are drawn by region_of_interest function.
"""
`img` should be the output of a Canny transform.
Returns an image with hough lines drawn.
"""
lines = cv2.HoughLinesP(img, rho, theta, threshold, np.array([]), minLineLength=min_line_len, maxLineGap=max_line_gap)
line_img = np.zeros((img.shape[0], img.shape[1], 3), dtype=np.uint8)
draw_lines(line_img, lines)
return line_img
# Python 3 has support for cool math symbols.
def weighted_img(img, initial_img, α=0.8, β=1., γ=0.):
# By appliying "color" binary image, it finally draws the line on the edge image.
"""
`img` is the output of the hough_lines(), An image with lines drawn on it.
Should be a blank image (all black) with lines drawn on it.
`initial_img` should be the image before any processing.
The result image is computed as follows:
initial_img * α + img * β + γ
NOTE: initial_img and img must be the same shape!
"""
return cv2.addWeighted(initial_img, α, img, β, γ)
def process_image(image):
# NOTE: The output you return should be a color image (3 channel) for processing video below
# TODO: put your pipeline here,
# you should return the final output (image where lines are drawn on lanes)
# Read in and grayscale the image
gray = grayscale(image)
# Define a kernel size and apply Gaussian smoothing
kernel_size = 5
blur_gray = gaussian_blur(gray, kernel_size)
# Define our parameters for Canny and apply
low_threshold = 50
high_threshold = 150
edges = canny(blur_gray, low_threshold, high_threshold)
# Next we'll create a masked edges image using cv2.fillPoly()
imshape = image.shape
vertices = np.array([[(120,imshape[0]),(450, 320), (500, 320), (imshape[1],imshape[0])]], dtype=np.int32)
masked_edges = region_of_interest(edges, vertices)
# Define the Hough transform parameters
# Make a blank the same size as our image to draw on
rho = 2 # distance resolution in pixels of the Hough grid
theta = np.pi/180 # angular resolution in radians of the Hough grid
threshold = 15 # minimum number of votes (intersections in Hough grid cell)
min_line_len = 40 # minimum number of pixels making up a line
max_line_gap = 20 # maximum gap in pixels between connectable line segments
line_image = np.copy(image)*1 #creating a blank to draw lines on
# Run Hough on edge detected image
lines = hough_lines(masked_edges, rho, theta, threshold, min_line_len, max_line_gap)
# Create a "color" binary image to combine with line image
color_edges = np.dstack((edges, edges, edges))
# Draw the lines on the edge image
lines_edges = weighted_img(lines, line_image)
return lines_edges

function takes exactly 1 argument (3 given)?

i am trying to change the value of a pixel in an image to the closest value i have in my list, and i cant figure out why i cant change the pixel value.
I've tried converting the image to RGB or RGBA and for some reason sometimes it takes 3 arguments sometime 4.
im = Image.open('rick.png') # Can be many different formats.
rgb_im = im.convert('RGBA')
pix = im.load()
height, width = im.size
image = ImageGrab.grab()
COLORS = (
(0, 0, 0),
(127, 127, 127),
(136, 0, 21),
(237, 28, 36),
(255, 127, 39),
)
def closest_color(r, g, b, COLORS):
min_diff = 9999
answer = None
for color in COLORS:
cr, cg, cb = color
color_diff = abs(r - cr) + abs(g - cg) + abs(b - cb)
if color_diff < min_diff:
answer = color
min_diff = color_diff
return answer
def read_color(height,width, COLORS, pix):
for x in range(height):
for y in range(width):
r,g,b,a = rgb_im.getpixel((x,y))
color = closest_color(r, g, b, COLORS) # color is returned as tuple
pix[x,y] = color # Changing color value? -Here i get the error-
read_color(height,width, COLORS, pix)
im.save('try.png')
I keep getting this error even tho closest_value returns one argument and i dont know why, thnk you for your help!
COLORS - is a list of colors, i've tested the closest_color() function and it works good
Error message:
'Exception has occurred: TypeError
function takes exactly 1 argument (3 given)
File "C:\Users\user\Desktop\תוכנות שעשיתי\program.py", line 133, in
read_color
pix[x,y] = color
File "C:\Users\user\Desktop\תוכנות שעשיתי\program.py", line 137, in
<module>
read_color(height,width, COLORS, pix)'
EDIT!
Apperantly the code is working for most of the images but not for all of them, for exmaple this image doesn't work and i get this error
You are being inconsistent by reading the pixels from the RGBA converted image but setting the pixels in the original maybe-not-RGBA image. Fixing that makes your code work with the sample image.
pix = rgb_im.load()

TypeError: ´numpy.uint8´ object is not iterable`

This code shall convert my RGB-image into Black/White and provide me the RGB-value -which should be (0, 0, 0) or (255, 255, 255).
import cv2
import numpy as np
template = cv2.imread('C:\colorbars.png')
gray = cv2.cvtColor(template, cv2.COLOR_RGB2GRAY)
gray = cv2.resize(gray,(640,480))
ret,gray = cv2.threshold(gray,120,255,0)
gray2 = gray.copy()
mask = np.zeros(gray.shape,np.uint8)
contours, hier = cv2.findContours(gray,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)
for cnt in contours:
if 200<cv2.contourArea(cnt)<5000:
cv2.drawContours(gray2,[cnt],0,(0,255,0),2)
cv2.drawContours(mask,[cnt],0,(0,255,0),-1)
cv2.bitwise_not(gray2,gray2,mask)
y = 250
x = 200
r, g, b = gray2[y,x]
print r, g, b
It works if I check the RGB value of the colored image with the line r, g, b = template[y,x]; however as soon as I want to have the RGB value of the Black/White image, following error message appears:
File "C:\Python27\Lib\site-packages\myprogram.py", Line 22, in <module>
r, g, b = gray2[y,x]
TypeError: ´numpy.uint8´ object is not iterable
I assume it means that there are not enough objects in the array and I assume that the problem lies somewhere in the conversion from color to B/W.
Your "gray" variable is a 2D matrix (because of the grayscale), so when you ask for gray2[x,y], it returns a single unsigned integer in 8 bit (np.unint8) corresponding to the grayscale value of the [x,y] pixel.
When you do : r,g,b =gray2[x,y], you expect 3 values (r, g, b), but it returns only 1 so you get an error.
You should precise what you are trying to do, as asking for the RGB values of a grayscale image makes no sense.
Please try to use just ONE channel to get the result instead of 3 channels,
for example: r = gray2[x,y]

Categories