How to change the color of 4 pixels in an image, multiple times using opencv? - python-3.x

I'm trying to write a program that will convert a 480x480 image into 240x240 quality, by taking the average color of 4 pixels and replacing all 4 with that average color. Then I repeat this for all 2x2 squares in the image. My program so far keeps replacing the entire image with the average color from the top left corner 4 squares, which is bgr [150, 138, 126]. Can someone help me see what I'm doing wrong?
def get_square(x1, x2, y1, y2):
square = image[x1:x2, y1:y2]
return square
def bgr_vals(img):
b_vals, g_vals, r_vals = [], [], []
x, y = 0, 0
for i in range(4):
b, g, r = image[x][y]
b_vals.append(b)
g_vals.append(g)
r_vals.append(r)
if y != 1:
y += 1
elif x == 0 and y == 1:
x += 1
elif x == 1 and y == 1:
y -= 1
return b_vals, g_vals, r_vals
def avg_color(bgr_vals):
b_avg = np.average(bgr_vals[0])
g_avg = np.average(bgr_vals[1])
r_avg = np.average(bgr_vals[2])
return [b_avg, g_avg, r_avg]
image = cv2.imread('src.jpg')
y1, y2 = 0, 2
for i in range(240):
x1, x2 = 0, 2
for i in range(240):
patch = get_square(x1, x2, y1, y2)
bgr_colors = bgr_vals(patch)
color = avg_color(bgr_colors)
image[x1:x2, y1:y2] = color
x1 += 2
x2 += 2
y1 += 2
y2 += 2
Thanks!

In your function bgr_vals you take argument img (which is the current patch), but within the method you inadvertently access image (the whole image) in this line:
b, g, r = image[x][y]
Your script works fine when you fix this typo.
Here are some tips for the future:
Images in OpenCV (and most libraries) are stored in row-first order, this means that you should write image[y][x] or img[y][x]. In your case it doesn't matter but in future work it might.
In general try to test your programs with non-square images, it is a common pitfall to only test with square-sized images
When you have cascaded loops, don't use the same variable name in the loop (you used i both times). Again, in your case it doesn't matter but as soon as you would use the value of i the confusion would start
Instead of increasing x, y within the loop you can use the loop variable directly, by getting a range with a step:
for y in range(0, 480, 2):
for x in range(0, 480, 2):
…
Use array shape instead of hard-coding dimensions:
for y in range(0, image.shape[0], 2):
for x in range(0, image.shape[1], 2):
…

Related

How to get canvas current size with guiZero

I am aiming for make GUI that changes depending on the canvas size. I need to be able to actively check the window size so i know when to display extras.
Using
Python 3.8.2
GuiZero
You can use tkinter event <Configure> on the canvas:
def on_resize(event):
print(app.height)
...
app.tk.bind('<Configure>', on_resize)
I was finally able to make something but it does throw and error after the app quits.
w=None
while True:
x=app.tk.winfo_height()
if x!=w:
print(x)
w=app.tk.winfo_height()
app.update()
I came across this problem when creating a digital etch-a-sketch, using a Raspberry Pi and two potentiometers as the horizontal and vertical controls. How to get the current size of the canvas? Annoyingly when you set height and width to "fill" and then try to interrogate these values all you get is "fill", which is no use if you're trying to determine the upper bounds of the available canvas. I dug down into the object hierarchy and discovered that .tk_winfo_height() and .tk.winfo_width() return integer values. For this purpose I've removed the code that reacts to the twiddling of the potentiometers and put a row of buttons at the bottom of the screen to control the vertical and horizontal movement.
from guizero import App, Box, Drawing, PushButton
x = 0
y = 0
def clear_screen():
drawing.clear()
def move_left():
global x, y
if x > 0 :
drawing.line(x, y, x - 1, y)
x = x - 1
def move_right():
global x, y
if x < drawing.tk.winfo_width() :
drawing.line(x, y, x + 1, y)
x = x + 1
def move_up():
global x, y
if y > 0 :
drawing.line(x, y, x, y - 1)
y = y - 1
def move_down():
global x, y
if y < drawing.tk.winfo_height() :
drawing.line(x, y, x, y + 1)
y = y + 1
app = App()
drawing = Drawing(app, height="fill", width="fill")
drawing.bg="white"
bbox = Box(app, align="bottom")
lbtn = PushButton(bbox, align="left", command=move_left, text="Left")
ubtn = PushButton(bbox, align="left", command=move_up, text="Up")
cbtn = PushButton(bbox, align="left", command=clear_screen, text="Clear")
rbtn = PushButton(bbox, align="left", command=move_right, text="Right")
dbtn = PushButton(bbox, align="left", command=move_down, text="Down")
app.display()

python getting Segmentation fault: 11 on OS 10.13

Im getting Segmentation fault: 11 error on my mac 10.13.6
I'm running a virtualenv with Python 3.6.5 Anaconda
I'm running a pixel flood filling script
img = cv2.imread(image,1)
surface = cv2.Canny(img,100,200)
def floodfill(x, y, oldColor, newColor):
# assume surface is a 2D image and surface[x][y] is the color at x, y.
if surface[x][y] != oldColor: # the base case
return
surface[x][y] = newColor
floodfill(x + 1, y, oldColor, newColor) # right
floodfill(x - 1, y, oldColor, newColor) # left
floodfill(x, y + 1, oldColor, newColor) # down
floodfill(x, y - 1, oldColor, newColor) # up
floodfill(0, 0, 0, 100)
plt.imshow(edges, cmap='gray')
plt.show()
any suggestions?
I think the problem is that your code is recursively calling itself without the previous function ending, resulting in increasing numbers of copies of your function resting on the stack until you run out of memory (which triggers the segmentation fault). Each time Python calls a new function and places it on the stack, a stack frame is created which uses up some memory, even if you aren't creating any new objects within that function call. When the function returns, the garbage collector within python frees up the memory, but if there are a lot of values in your image with value 0, then then you can end up with a lot of copies of floodfill running at once. This is a little old and very in-depth and technical but if you want to know more, this is a good discussion.
To see an alternative approach to tackling the problem using lists of active nodes, have a look here:
https://rosettacode.org/wiki/Bitmap/Flood_fill#Python
As an aside, you have another issue that may be deliberate, in that your code treats the image as a sphere, in the sense that when it hits a border it will jump to the other side of the image and fill there too. This is because python supports negative indices, so when x=0 and you jump to x-1, you are looking at index -1 which is the last index in the array. To address this you could add some checks:
if x > 0: # left
floodfill(x - 1, y, oldColor, newColor) # left
if y > 0: # up
floodfill(x, y - 1, oldColor, newColor) # up
if x < surface.shape[0] - 1: # right
floodfill(x + 1, y, oldColor, newColor) # right
if y < surface.shape[1] - 1: # down
floodfill(x, y + 1, oldColor, newColor) # down
Your code works fine in general though. If you try it with a small toy example, you can see it in action (this is with the fix above):
surface_array = [[0 for i in range (0,10)] for j in range(0,10)]
surface_array[1][1] = 1
surface_array[0][1] = 1
surface_array[2][0] = 1
surface = np.array(surface_array)
print(surface)
floodfill(0, 0, 0, 100)
print(surface)
Here is an option on how to do the same thing without recursion for anyone interested. from http://inventwithpython.com/blog/2011/08/11/recursion-explained-with-the-flood-fill-algorithm-and-zombies-and-cats/
def floodfill(x, y, oldColor, newColor):
# assume surface is a 2D image and surface[x][y] is the color at x, y.
theStack = [ (x, y) ]
while (len(theStack) > 0):
x, y = theStack.pop()
if (x == 224):
continue
if (x == -1):
continue
if (y == -1):
continue
if (y == 224):
continue
if edges[x][y] != oldColor:
continue
edges[x][y] = newColor
theStack.append( (x + 1, y) ) # right
theStack.append( (x - 1, y) ) # left
theStack.append( (x, y + 1) ) # down
theStack.append( (x, y - 1) ) # up

Creating a symmetrical grid of random size squares in Python3/Tkinter

I have a question revolving around what would be a viable approach to placing out random-sized squares on a symmetrical, non-visible grid on a tkinter-canvas. I'm going to explain it quite thoroughly as it's a somewhat proprietary problem.
This far I've tried to solve it mostly mathematically. But I've found it to be quite a complex problem, and it seems reasonable that there would be a better approach to take it on than what I've tried.
In its most basic form the code looks like this:
while x_len > canvas_width:
xpos = x_len + margin
squares[i].place(x=xpos, y=ypos)
x_len += square_size + space
i += 1
x_len is the total width of all the squares on a given row, and resets when exiting the while-loop (eg. when x_len > window width), among with xpos (the position on X), as well as altering Y-axis to create a new row.
When placing same-size squares it looks like this:
So far so good.
However when the squares are of random-size it looks like this (at best):
The core problem, beyond that the layout can be quite unpredictable, is that the squares aren't centered to the "invisible grid" - because there is none.
So to solve this I've tried an approach where I use a fixed distance and a relative distance based on every given square. This yields satisficing results for the Y-axis on the first row, but not on the X-axis, nor the following rows on Y.
See example (where first row is centered on Y, but following rows and X is not):
So with this method I'm using a per-square alteration in both Y- and X-axis, based on variables that I fetch from a list that contain widths for all of the generated squares.
In it's entirety it looks like this (though it's work in progress so it's not very well optimized):
square_widths = [60, 75, 75, 45...]
space = square_size*0.5
margin = (square_size+space)/2
xmax = frame_width - margin - square_size
xmin = -1 + margin
def iterate(ypos, xpos, x_len):
y = ypos
x = xpos
z = x_len
i=0
m_ypos = 0
extra_x = 0
while len(squares) <= 100:
n=-1
# row_ypos alters y for every new row
row_ypos += 200-square_widths[n]/2
# this if-statement is not relevant to the question
if x < 0:
n=0
xpos = x
extra_x = x
x_len = z
while x_len < xmax:
ypos = row_ypos
extra_x += 100
ypos = row_ypos + (200-square_widths[n])/2
xpos = extra_x + (200-square_widths[n])/2
squares[i].place(x=xpos, y=ypos)
x_len = extra_x + 200
i += 1
n += 1
What's most relevant here is row_ypos, that alters Y for each row, as well as ypos, that alters Y for each square (I don't have a working calculation for X yet). What I would want to achieve is a similar result that I get for Y-axis on the first row; on all rows and columns (eg. both in X and Y). To create a symmetrical grid with squares of different sizes.
So my questions are:
Is this really best practice to solve this?
If so - Do you have any tips on decent calculations that would do the trick?
If not - How would you approach this?
A sidenote is that it has to be done "manually" and I can not use built-in functions of tkinter to solve it.
Why don't you just use the grid geometry manager?
COLUMNS = 5
ROWS = 5
for i in range(COLUMNS*ROWS):
row, col = divmod(i, COLUMNS)
l = tk.Label(self, text=i, font=('', randint(10,50)))
l.grid(row=row, column=col)
This will line everything up, but the randomness may make the rows and columns different sizes. You can adjust that with the row- and columnconfigure functions:
import tkinter as tk
from random import randint
COLUMNS = 10
ROWS = 5
class GUI(tk.Frame):
def __init__(self, master=None, **kwargs):
tk.Frame.__init__(self, master, **kwargs)
labels = []
for i in range(COLUMNS*ROWS):
row, col = divmod(i, COLUMNS)
l = tk.Label(self, text=i, font=('', randint(10,50)))
l.grid(row=row, column=col)
labels.append(l)
self.update() # draw everything
max_width = max(w.winfo_width() for w in labels)
max_height = max(w.winfo_height() for w in labels)
for column in range(self.grid_size()[0]):
self.columnconfigure(col, minsize=max_width) # set all columns to the max width
for row in range(self.grid_size()[1]):
self.rowconfigure(row, minsize=max_height) # set all rows to the max height
def main():
root = tk.Tk()
win = GUI(root)
win.pack()
root.mainloop()
if __name__ == "__main__":
main()
I found the culprit that made the results not turn out the way expected, and it wasn't due to the calculations. Rather it turned out that the list I created didn't put the squares in correct order (which I should know since before).
And so I fetched the width from the raw data itself, which makes a lot more sense than creating a list.
The function now looks something like this (again, it's still under refinement, but I just wanted to post this, so that people don't waste their time in coming up with solutions to an already solved problem :)):
def iterate(ypos, xpos, x_len):
y = ypos
x = xpos
z = x_len
i=0
while len(squares) <= 100:
n=0
if y > 1:
ypos -= max1 + 10
if y < 0:
if ypos < 0:
ypos=10
else:
ypos += max1 + 10 #+ (max1-min1)/2
if x < 0:
n=0
xc=0
xpos = x
x_len = z
while x_len < xmax:
yc = ypos + (max1-squares[i].winfo_width())/2
if xpos <= 0:
xpos = 10
else:
xpos += max1 + 10
xc = xpos + (max1-squares[i].winfo_width())/2
squares[i].place(x=xc, y=yc)
x_len += max1 + 10
print (x_len)
i += 1
n += 1

Colormapping the Mandelbrot set by iterations in python

I am using np.ogrid to create the x and y grid from which I am drawing my values. I have tried a number of different ways to color the scheme according to the iterations required for |z| >= 2 but nothing seems to work. Even when iterating 10,000 times just to be sure that I have a clear picture when zooming, I cannot figure out how to color the set according to iteration ranges. Here is the code I am using, some of the structure was borrowed from a tutorial. Any suggestions?
#I found this function and searched in numpy for best usage for this type of density plot
x_val, y_val = np.ogrid[-2:2:2000j, -2:2:2000j]
#Creating the values to work with during the iterations
c = x_val + 1j*y_val
z = 0
iter_num = int(input("Please enter the number of iterations:"))
for n in range(iter_num):
z = z**2 + c
if n%10 == 0:
print("Iterations left: ",iter_num - n)
#Creates the mask to filter out values of |z| > 2
z_mask = abs(z) < 2
proper_z_mask = z_mask - 255 #switches current black/white pallette
#Creating the figure and sizing for optimal viewing on a small laptop screen
plt.figure(1, figsize=(8,8))
plt.imshow(z_mask.T, extent=[-2, 2, -2, 2])
plt.gray()
plt.show()

TkInter python - creating points on a canvas to obtain a Sierpinsky triangle

I want to make a program which plots a Sierpinsky triangle (of any modulo). In order to do it I've used TkInter. The program generates the fractal by moving a point randomly, always keeping it in the sides. After repeating the process many times, the fractal appears.
However, there's a problem. I don't know how to plot points on a canvas in TkInter. The rest of the program is OK, but I had to "cheat" in order to plot the points by drawing small lines instead of points. It works more or less, but it doesn't have as much resolution as it could have.
Is there a function to plot points on a canvas, or another tool to do it (using Python)? Ideas for improving the rest of the program are also welcome.
Thanks. Here's what I have:
from tkinter import *
import random
import math
def plotpoint(x, y):
global canvas
point = canvas.create_line(x-1, y-1, x+1, y+1, fill = "#000000")
x = 0 #Initial coordinates
y = 0
#x and y will always be in the interval [0, 1]
mod = int(input("What is the modulo of the Sierpinsky triangle that you want to generate? "))
points = int(input("How many points do you want the triangle to have? "))
tkengine = Tk() #Window in which the triangle will be generated
window = Frame(tkengine)
window.pack()
canvas = Canvas(window, height = 700, width = 808, bg = "#FFFFFF") #The dimensions of the canvas make the triangle look equilateral
canvas.pack()
for t in range(points):
#Procedure for placing the points
while True:
#First, randomly choose one of the mod(mod+1)/2 triangles of the first step. a and b are two vectors which point to the chosen triangle. a goes one triangle to the right and b one up-right. The algorithm gives the same probability to every triangle, although it's not efficient.
a = random.randint(0,mod-1)
b = random.randint(0,mod-1)
if a + b < mod:
break
#The previous point is dilated towards the origin of coordinates so that the big triangle of step 0 becomes the small one at the bottom-left of step one (divide by modulus). Then the vectors are added in order to move the point to the same place in another triangle.
x = x / mod + a / mod + b / 2 / mod
y = y / mod + b / mod
#Coordinates [0,1] converted to pixels, for plotting in the canvas.
X = math.floor(x * 808)
Y = math.floor((1-y) * 700)
plotpoint(X, Y)
tkengine.mainloop()
If you are wanting to plot pixels, a canvas is probably the wrong choice. You can create a PhotoImage and modify individual pixels. It's a little slow if you plot each individual pixel, but you can get dramatic speedups if you only call the put method once for each row of the image.
Here's a complete example:
from tkinter import *
import random
import math
def plotpoint(x, y):
global the_image
the_image.put(('#000000',), to=(x,y))
x = 0
y = 0
mod = 3
points = 100000
tkengine = Tk() #Window in which the triangle will be generated
window = Frame(tkengine)
window.pack()
the_image = PhotoImage(width=809, height=700)
label = Label(window, image=the_image, borderwidth=2, relief="raised")
label.pack(fill="both", expand=True)
for t in range(points):
while True:
a = random.randint(0,mod-1)
b = random.randint(0,mod-1)
if a + b < mod:
break
x = x / mod + a / mod + b / 2 / mod
y = y / mod + b / mod
X = math.floor(x * 808)
Y = math.floor((1-y) * 700)
plotpoint(X, Y)
tkengine.mainloop()
You can use canvas.create_oval with the same coordinates for the two corners of the bounding box:
from tkinter import *
import random
import math
def plotpoint(x, y):
global canvas
# point = canvas.create_line(x-1, y-1, x+1, y+1, fill = "#000000")
point = canvas.create_oval(x, y, x, y, fill="#000000", outline="#000000")
x = 0 #Initial coordinates
y = 0
#x and y will always be in the interval [0, 1]
mod = int(input("What is the modulo of the Sierpinsky triangle that you want to generate? "))
points = int(input("How many points do you want the triangle to have? "))
tkengine = Tk() #Window in which the triangle will be generated
window = Frame(tkengine)
window.pack()
canvas = Canvas(window, height = 700, width = 808, bg = "#FFFFFF") #The dimensions of the canvas make the triangle look equilateral
canvas.pack()
for t in range(points):
#Procedure for placing the points
while True:
#First, randomly choose one of the mod(mod+1)/2 triangles of the first step. a and b are two vectors which point to the chosen triangle. a goes one triangle to the right and b one up-right. The algorithm gives the same probability to every triangle, although it's not efficient.
a = random.randint(0,mod-1)
b = random.randint(0,mod-1)
if a + b < mod:
break
#The previous point is dilated towards the origin of coordinates so that the big triangle of step 0 becomes the small one at the bottom-left of step one (divide by modulus). Then the vectors are added in order to move the point to the same place in another triangle.
x = x / mod + a / mod + b / 2 / mod
y = y / mod + b / mod
#Coordinates [0,1] converted to pixels, for plotting in the canvas.
X = math.floor(x * 808)
Y = math.floor((1-y) * 700)
plotpoint(X, Y)
tkengine.mainloop()
with a depth of 3 and 100,000 points, this gives:
Finally found a solution: if a 1x1 point is to be placed in pixel (x,y), a command which does it exactly is:
point = canvas.create_line(x, y, x+1, y+1, fill = "colour")
The oval is a good idea for 2x2 points.
Something remarkable about the original program is that it uses a lot of RAM if every point is treated as a separate object.

Resources