I am wanting to:
Open the texture from an image via cv2 instead of via ModernGL's load_texture_2d method.
Save the resulting image (in the write method) via cv2 rather than Pillow.
I currently have the following code:
from pathlib import Path
from array import array
import cv2
import numpy as np
from PIL import Image
import moderngl
import moderngl_window
class ImageProcessing(moderngl_window.WindowConfig):
window_size = 3840 // 2, 2160 // 2
resource_dir = Path(__file__).parent.resolve()
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.image_processing = ImageTransformer(self.ctx, self.window_size)
# Not working:
#img = cv2.imread("test6.png")
#self.texture = img.astype('f4')
self.texture = self.load_texture_2d("test6.png")
def render(self, time, frame_time):
# View in Window
self.image_processing.render(self.texture, target=self.ctx.screen)
# Headless
self.image_processing.render(self.texture)
self.image_processing.write("output.png")
class ImageTransformer:
def __init__(self, ctx, size, program=None):
self.ctx = ctx
self.size = size
self.program = None
self.fbo = self.ctx.framebuffer(
color_attachments=[self.ctx.texture(self.size, 4)]
)
# Create some default program if needed
if not program:
self.program = self.ctx.program(
vertex_shader="""
#version 330
in vec2 in_position;
in vec2 in_uv;
out vec2 uv;
void main() {
gl_Position = vec4(in_position, 0.0, 1.0);
uv = in_uv;
}
""",
fragment_shader = """
#version 330
uniform sampler2D image;
in vec2 uv;
out vec4 out_color;
void main() {
vec4 color = texture(image, uv);
// do something with color here
out_color = vec4(color.r, 0, 0, color.a);
}
""",
)
# Fullscreen quad in NDC
self.vertices = self.ctx.buffer(
array(
'f',
[
# Triangle strip creating a fullscreen quad
# x, y, u, v
-1, 1, 0, 1, # upper left
-1, -1, 0, 0, # lower left
1, 1, 1, 1, # upper right
1, -1, 1, 0, # lower right
]
)
)
self.quad = self.ctx.vertex_array(
self.program,
[
(self.vertices, '2f 2f', 'in_position', 'in_uv'),
]
)
def render(self, texture, target=None):
if target:
target.use()
else:
self.fbo.use()
texture.use(0)
self.quad.render(mode=moderngl.TRIANGLE_STRIP)
def write(self, name):
# This doesn't work:
raw = self.fbo.read(components=4, dtype='f4')
buf = np.frombuffer(raw, dtype='f4')
cv2.imwrite("OUTPUT_IMAGE.png", buf)
# But this does:
## image = Image.frombytes("RGBA", self.fbo.size, self.fbo.read())
## image = image.transpose(Image.FLIP_TOP_BOTTOM)
## image.save(name, format="png")
if __name__ == "__main__":
ImageProcessing.run()
Currently, when the code is run as-is, no image is saved whatsoever. The window just hangs and nothing happens. I am not sure if I have something wrong in my code or if the datatypes are wrong.
The pillow code (if you uncomment it) works to save it, but please note: While I could convert to a numpy array from Pillow, I would prefer not to in my use-case.
Clarification: The window loads and shows the image result just fine, but doesn't save correctly in the write method.
There is som code missing in your application
The method load_texture_2d creates a moderngl.Texture object. Hence the method loads the file, creates a texture object and loads the texture image from the CPU to the GPU.
cv2.imread just load the image file to a NumPy array, but it doesn't create a moderngl.Texture object.
You have to generate a moderngl.Texture object from the NumPy array:
img = cv2.imread("test6.png")
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # optional
img = np.flip(img, 0).copy(order='C') # optional
self.texture = self.ctx.texture(img.shape[1::-1], img.shape[2], img)
Before writing the buffer into an image, you must reshape the NumPy array according to the image format. For instance:
raw = self.fbo.read(components=4, dtype='f1')
buf = np.frombuffer(raw, dtype='uint8').reshape((*self.fbo.size[1::-1], 4))
cv2.imwrite("OUTPUT_IMAGE.png", buf)
can you tell, in the write() method, what buf.shape is? I think it's a 1-d array at that point and it probably is height * width * 4 elements long.
imwrite() needs it shaped right. try this before imwrite():
buf.shape = (self.size[1], self.size[0], 4)
that should reshape the data as (height, width, 4) and then the imwrite() should accept it.
Related
I currently use Asus TUF FX505G that has both integrated and 1050Ti graphic unit. While trying to render array of points to screen, I found out that I would get two different results depeding on which GPU is in use. On 1050Ti the code works not intended. Python version 3.10.8
Main.py
import glfw
import Shader
from OpenGL.GL import *
from Math_3d import Vector3f
class Window:
def __init__(self, width: int, height: int, title: str):
# Exit keyboard flag
self.exitNow = False
# Vertex Buffer Object
# Create point vertex data
self.v3f = Vector3f.data
if not glfw.init():
raise Exception("glfw can not be initialized")
# OpenGL 4.0 version for shader subroutines and Dynamically uniform expression support
glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, 3)
glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 2)
glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE)
self._win = glfw.create_window(width, height, title, None, None)
if not self._win:
glfw.terminate()
raise Exception("glfw window can not be created")
glfw.set_window_pos(self._win, 400, 200)
glfw.make_context_current(self._win)
# Enable key events
# glfw.set_input_mode(self._win, glfw.STICKY_KEYS, GL_TRUE)
# Enable key event callback from keyboard
# glfw.set_key_callback(self._win, self.onKeyboard)
self.program = glCreateProgram()
self.buffer = glGenBuffers(1)
def initialize(self):
# Request program and shader slots from the GPU
vertex = glCreateShader(GL_VERTEX_SHADER)
fragment = glCreateShader(GL_FRAGMENT_SHADER)
# Set shader sources
glShaderSource(vertex, Shader.vertex_code)
glShaderSource(fragment, Shader.fragment_code)
# Compile shaders and check that they have compiled
glCompileShader(vertex)
glCompileShader(fragment)
if not glGetShaderiv(vertex, GL_COMPILE_STATUS):
report_shader = glGetShaderInfoLog(vertex)
print(report_shader)
raise RuntimeError("Vertex shader compilation error")
if not glGetShaderiv(fragment, GL_COMPILE_STATUS):
report_frag = glGetShaderInfoLog(fragment)
print(report_frag)
raise RuntimeError("Fragment shader compilation error")
# Link shaders to program
glAttachShader(self.program, vertex)
glAttachShader(self.program, fragment)
glLinkProgram(self.program)
if not glGetProgramiv(self.program, GL_LINK_STATUS):
print(glGetProgramInfoLog(self.program))
raise RuntimeError('Linking error')
# Get rid of shaders
glDetachShader(self.program, vertex)
glDetachShader(self.program, fragment)
# Make default program to run
glUseProgram(self.program)
# Request a buffer slot from GPU
# buffer = glGenBuffers(1)
# Make this buffer the default one
glBindBuffer(GL_ARRAY_BUFFER, self.buffer)
# Vertex Array Buffer
vao = glGenVertexArrays(1)
glBindVertexArray(vao)
loc = glGetAttribLocation(self.program, 'position')
glEnableVertexAttribArray(loc)
glVertexAttribPointer(loc, self.v3f.itemsize, GL_FLOAT, False, self.v3f.data.strides[0], None)
glBufferData(GL_ARRAY_BUFFER, self.v3f.data.nbytes, self.v3f.data, GL_DYNAMIC_DRAW)
def renderscene(self):
while not glfw.window_should_close(self._win) and not self.exitNow:
glClear(GL_COLOR_BUFFER_BIT)
glPointSize(30)
glDrawArrays(GL_POINTS, 0, self.v3f.itemsize)
glfw.swap_buffers(self._win)
# Do not refresh screen before receiving event
glfw.wait_events()
glDisableVertexAttribArray(0)
glUseProgram(0)
glfw.terminate()
if __name__ == '__main__':
win = Window(1024, 768, "GLFW Window - Rotate glPoints")
win.initialize() # Create and initialize shaders and initialize Vertex Buffer Object
win.renderscene() # Swap buffer and render scene
Shader.py
vertex_code = """
attribute vec3 position;
void main()
{
gl_Position = vec4(position, 1.0);
}
"""
fragment_code = """
void main()
{
gl_FragColor = vec4(0.5, 0.5, 0.5, 1.0);
}
"""
Math_3d.py
from OpenGL.GL import *
import numpy as np
np.set_printoptions(precision=3, suppress=True)
class Vector3f:
data = np.array([[0.5, 0.5, 0],
[-0.5, 0.5, 0],
[-0.5, -0.5, 0],
[0.5, -0.5, 0]], dtype=np.float32)
def Rotate(self):
pass
if __name__ == '__main__':
vec3 = Vector3f
print(vec3.data)
print("Item size: ", vec3.data.itemsize)
print("Strides: ", vec3.data.strides[0])
print("Bytes:", vec3.data.nbytes)
Ran the code while using both gpus. There should be no apparent buffer overflow.
The problem is here:
glVertexAttribPointer(loc, self.v3f.itemsize, GL_FLOAT, False, self.v3f.data.strides[0], None)
The 2nd argument of glVertexAttribPointer is the number of components of the attribute (x, y, z), which is 3. However, itemsize is the number of bytes of an item, which is 4 for an item of type float.
Correct is:
glVertexAttribPointer(loc, 3, GL_FLOAT, False, self.v3f.data.strides[0], None)
I've followed the End-to-End image classification tutorial for tensorflow lite and have created and saved my model as '/path/to/model.tflite'.
What I haven't been able to figure out is how to load it.
I'm looking for some kind of syntax that is similar to this:
from tflite_model_maker import image_classifier
from tflite_model_maker.image_classifier import DataLoader
model = image_classifier.Load('/path/to/model.tflite')
I'm sure I'm missing something obvious here. This is definitely not the first place I've looked at. This seems to be the best place for me to find what I need, but the syntax used confuses me.
What do I want to be able to do with the model?
test = DataLoader.from_folder('/path/to/testImages')
loss, accuracy = model.evaluate(test)
# A helper function that returns 'red'/'black' depending on if its two input
# parameter matches or not.
def get_label_color(val1, val2):
if val1 == val2:
return 'black'
else:
return 'red'
# Then plot 100 test images and their predicted labels.
# If a prediction result is different from the label provided label in "test"
# dataset, we will highlight it in red color.
test_data = data
plt.figure(figsize=(20, 20))
predicts = model.predict_top_k(test_data)
for i, (image, label) in enumerate(test_data.gen_dataset().unbatch().take(100)):
ax = plt.subplot(10, 10, i+1)
plt.xticks([])
plt.yticks([])
plt.grid(False)
plt.imshow(image.numpy(), cmap=plt.cm.gray)
predict_label = predicts[i][0][0]
color = get_label_color(predict_label,
test_data.index_to_label[label.numpy()])
ax.xaxis.label.set_color(color)
plt.xlabel('Predicted: %s' % predict_label)
plt.show()
From the syntax above it seems the model isn't just a file but is a type/class/method depending on what name is most suitable for python.
Feels like this should only take one line of code but I haven't been able to find it anywhere.
Managed to do a simple version of it. The images coming up as a stream doesn't work for me using cv2 with Windows as it does for the pi. So instead I created a webpage in the same directory as this script. This generates an image with the bounding box, using a specified tflite model. This is in no way ideal.
It uses a webcam to get the image and saves the image to the directory the script is run in. It then renames the file so it can be viewed by the webpage I setup to view it.
The majority of this code comes from the TFLite Object Detection Raspberry Pi sample.
import time, os
from PIL import Image
from tflite_support import metadata
import platform
from typing import List, NamedTuple
import json
import cv2 as cv2
import numpy as np
import tensorflow as tf
from matplotlib import pyplot as plt
Interpreter = tf.lite.Interpreter
load_delegate = tf.lite.experimental.load_delegate
class ObjectDetectorOptions(NamedTuple):
"""A config to initialize an object detector."""
enable_edgetpu: bool = False
"""Enable the model to run on EdgeTPU."""
label_allow_list: List[str] = None
"""The optional allow list of labels."""
label_deny_list: List[str] = None
"""The optional deny list of labels."""
max_results: int = -1
"""The maximum number of top-scored detection results to return."""
num_threads: int = 1
"""The number of CPU threads to be used."""
score_threshold: float = 0.0
"""The score threshold of detection results to return."""
class Rect(NamedTuple):
"""A rectangle in 2D space."""
left: float
top: float
right: float
bottom: float
class Category(NamedTuple):
"""A result of a classification task."""
label: str
score: float
index: int
class Detection(NamedTuple):
"""A detected object as the result of an ObjectDetector."""
bounding_box: Rect
categories: List[Category]
def edgetpu_lib_name():
"""Returns the library name of EdgeTPU in the current platform."""
return {
'Darwin': 'libedgetpu.1.dylib',
'Linux': 'libedgetpu.so.1',
'Windows': 'edgetpu.dll',
}.get(platform.system(), None)
class ObjectDetector:
"""A wrapper class for a TFLite object detection model."""
_OUTPUT_LOCATION_NAME = 'location'
_OUTPUT_CATEGORY_NAME = 'category'
_OUTPUT_SCORE_NAME = 'score'
_OUTPUT_NUMBER_NAME = 'number of detections'
def __init__(
self,
model_path: str,
options: ObjectDetectorOptions = ObjectDetectorOptions()
) -> None:
"""Initialize a TFLite object detection model.
Args:
model_path: Path to the TFLite model.
options: The config to initialize an object detector. (Optional)
Raises:
ValueError: If the TFLite model is invalid.
OSError: If the current OS isn't supported by EdgeTPU.
"""
# Load metadata from model.
displayer = metadata.MetadataDisplayer.with_model_file(model_path)
# Save model metadata for preprocessing later.
model_metadata = json.loads(displayer.get_metadata_json())
process_units = model_metadata['subgraph_metadata'][0]['input_tensor_metadata'][0]['process_units']
mean = 0.0
std = 1.0
for option in process_units:
if option['options_type'] == 'NormalizationOptions':
mean = option['options']['mean'][0]
std = option['options']['std'][0]
self._mean = mean
self._std = std
# Load label list from metadata.
file_name = displayer.get_packed_associated_file_list()[0]
label_map_file = displayer.get_associated_file_buffer(file_name).decode()
label_list = list(filter(lambda x: len(x) > 0, label_map_file.splitlines()))
self._label_list = label_list
# Initialize TFLite model.
if options.enable_edgetpu:
if edgetpu_lib_name() is None:
raise OSError("The current OS isn't supported by Coral EdgeTPU.")
interpreter = Interpreter(
model_path=model_path,
experimental_delegates=[load_delegate(edgetpu_lib_name())],
num_threads=options.num_threads)
else:
interpreter = Interpreter(
model_path=model_path, num_threads=options.num_threads)
interpreter.allocate_tensors()
input_detail = interpreter.get_input_details()[0]
# From TensorFlow 2.6, the order of the outputs become undefined.
# Therefore we need to sort the tensor indices of TFLite outputs and to know
# exactly the meaning of each output tensor. For example, if
# output indices are [601, 599, 598, 600], tensor names and indices aligned
# are:
# - location: 598
# - category: 599
# - score: 600
# - detection_count: 601
# because of the op's ports of TFLITE_DETECTION_POST_PROCESS
# (https://github.com/tensorflow/tensorflow/blob/a4fe268ea084e7d323133ed7b986e0ae259a2bc7/tensorflow/lite/kernels/detection_postprocess.cc#L47-L50).
sorted_output_indices = sorted(
[output['index'] for output in interpreter.get_output_details()])
self._output_indices = {
self._OUTPUT_LOCATION_NAME: sorted_output_indices[0],
self._OUTPUT_CATEGORY_NAME: sorted_output_indices[1],
self._OUTPUT_SCORE_NAME: sorted_output_indices[2],
self._OUTPUT_NUMBER_NAME: sorted_output_indices[3],
}
self._input_size = input_detail['shape'][2], input_detail['shape'][1]
self._is_quantized_input = input_detail['dtype'] == np.uint8
self._interpreter = interpreter
self._options = options
def detect(self, input_image: np.ndarray) -> List[Detection]:
"""Run detection on an input image.
Args:
input_image: A [height, width, 3] RGB image. Note that height and width
can be anything since the image will be immediately resized according
to the needs of the model within this function.
Returns:
A Person instance.
"""
image_height, image_width, _ = input_image.shape
input_tensor = self._preprocess(input_image)
self._set_input_tensor(input_tensor)
self._interpreter.invoke()
# Get all output details
boxes = self._get_output_tensor(self._OUTPUT_LOCATION_NAME)
classes = self._get_output_tensor(self._OUTPUT_CATEGORY_NAME)
scores = self._get_output_tensor(self._OUTPUT_SCORE_NAME)
count = int(self._get_output_tensor(self._OUTPUT_NUMBER_NAME))
return self._postprocess(boxes, classes, scores, count, image_width,
image_height)
def _preprocess(self, input_image: np.ndarray) -> np.ndarray:
"""Preprocess the input image as required by the TFLite model."""
# Resize the input
input_tensor = cv2.resize(input_image, self._input_size)
# Normalize the input if it's a float model (aka. not quantized)
if not self._is_quantized_input:
input_tensor = (np.float32(input_tensor) - self._mean) / self._std
# Add batch dimension
input_tensor = np.expand_dims(input_tensor, axis=0)
return input_tensor
def _set_input_tensor(self, image):
"""Sets the input tensor."""
tensor_index = self._interpreter.get_input_details()[0]['index']
input_tensor = self._interpreter.tensor(tensor_index)()[0]
input_tensor[:, :] = image
def _get_output_tensor(self, name):
"""Returns the output tensor at the given index."""
output_index = self._output_indices[name]
tensor = np.squeeze(self._interpreter.get_tensor(output_index))
return tensor
def _postprocess(self, boxes: np.ndarray, classes: np.ndarray,
scores: np.ndarray, count: int, image_width: int,
image_height: int) -> List[Detection]:
"""Post-process the output of TFLite model into a list of Detection objects.
Args:
boxes: Bounding boxes of detected objects from the TFLite model.
classes: Class index of the detected objects from the TFLite model.
scores: Confidence scores of the detected objects from the TFLite model.
count: Number of detected objects from the TFLite model.
image_width: Width of the input image.
image_height: Height of the input image.
Returns:
A list of Detection objects detected by the TFLite model.
"""
results = []
# Parse the model output into a list of Detection entities.
for i in range(count):
if scores[i] >= self._options.score_threshold:
y_min, x_min, y_max, x_max = boxes[i]
bounding_box = Rect(
top=int(y_min * image_height),
left=int(x_min * image_width),
bottom=int(y_max * image_height),
right=int(x_max * image_width))
class_id = int(classes[i])
category = Category(
score=scores[i],
label=self._label_list[class_id], # 0 is reserved for background
index=class_id)
result = Detection(bounding_box=bounding_box, categories=[category])
results.append(result)
# Sort detection results by score ascending
sorted_results = sorted(
results,
key=lambda detection: detection.categories[0].score,
reverse=True)
# Filter out detections in deny list
filtered_results = sorted_results
if self._options.label_deny_list is not None:
filtered_results = list(
filter(
lambda detection: detection.categories[0].label not in self.
_options.label_deny_list, filtered_results))
# Keep only detections in allow list
if self._options.label_allow_list is not None:
filtered_results = list(
filter(
lambda detection: detection.categories[0].label in self._options.
label_allow_list, filtered_results))
# Only return maximum of max_results detection.
if self._options.max_results > 0:
result_count = min(len(filtered_results), self._options.max_results)
filtered_results = filtered_results[:result_count]
return filtered_results
_MARGIN = 10 # pixels
_ROW_SIZE = 10 # pixels
_FONT_SIZE = 1
_FONT_THICKNESS = 1
_TEXT_COLOR = (0, 0, 255) # red
def visualize(
image: np.ndarray,
detections: List[Detection],
) -> np.ndarray:
"""Draws bounding boxes on the input image and return it.
Args:
image: The input RGB image.
detections: The list of all "Detection" entities to be visualize.
Returns:
Image with bounding boxes.
"""
for detection in detections:
# Draw bounding_box
start_point = detection.bounding_box.left, detection.bounding_box.top
end_point = detection.bounding_box.right, detection.bounding_box.bottom
cv2.rectangle(image, start_point, end_point, _TEXT_COLOR, 3)
# Draw label and score
category = detection.categories[0]
class_name = category.label
probability = round(category.score, 2)
result_text = class_name + ' (' + str(probability) + ')'
text_location = (_MARGIN + detection.bounding_box.left,
_MARGIN + _ROW_SIZE + detection.bounding_box.top)
cv2.putText(image, result_text, text_location, cv2.FONT_HERSHEY_PLAIN,
_FONT_SIZE, _TEXT_COLOR, _FONT_THICKNESS)
return image
# ---------------------------------- #
# This is where the custom code starts
# ---------------------------------- #
# Load the TFLite model
TFLITE_MODEL_PATH='object.tflite'
DETECTION_THRESHOLD = 0.5 # 50% threshold required before identifying
options = ObjectDetectorOptions(
num_threads=4,
score_threshold=DETECTION_THRESHOLD,
)
# Close camera if already open
try:
cap.release()
except:
print("",end="") # do nothing
detector = ObjectDetector(model_path=TFLITE_MODEL_PATH, options=options)
cap = cv2.VideoCapture(0) #webcam
counter = 0 # Store many times model has run
while cap.isOpened():
success, image = cap.read()
if not success:
sys.exit(
'ERROR: Unable to read from webcam. Please verify your webcam settings.'
)
image = cv2.flip(image, 1)
# Convert the image from BGR to RGB as required by the TFLite model.
rgb_image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
#image.thumbnail((512, 512), Image.ANTIALIAS)
image_np = np.asarray(image)
# Run object detection estimation using the model.
detections = detector.detect(image_np)
# Draw keypoints and edges on input image
image_np = visualize(image_np, detections)
if counter == 10: # <- Change this to decide how many iterations
cap.release()
break
image_np = cv2.cvtColor(image_np, cv2.COLOR_BGR2RGB)
plt.imsave('tmp.jpg',image_np) # Saves the image
os.replace("tmp.jpg", "web.jpg",) # Renames it for the webpage
counter += 1
print(counter)
cap.release()
Here's the HTML for the document placed in the same directory as the python file, I saved it as index.html and opened in the browser while running the python script above.
<!DOCTYPE html>
<html>
<head>
<title>Object Detection</title>
</head>
<body>
<h1>Object Detection</h1>
<p>This displays images saved during detection process</p>
<canvas id="x" width="700px" height="500px"></canvas>
<script>
var newImage = new Image();
newImage.src = "web.jpg";
var canvas = document.getElementById("x");
var context = canvas.getContext("2d");
newImage.onload = function() {
context.drawImage(newImage, 0, 0);
console.log("trigger")
setTimeout(timedRefresh, 1000);
};
function timedRefresh() {
// just change src attribute, will always trigger the onload callback
try {
newImage.src = ("web.jpg#" + new Date().getTime());
}catch(e){
console.log(e);
}
}
setTimeout(timedRefresh, 100);
</script>
</body>
</html>
It's incredibly slow, not ideal in many ways and probably breaks many good coding conventions. It was only used locally, would definitely not use this for a production environment nor recommend its use. Just needed a quick proof of concept and this worked for that.
I'm making an image viewer interface for a camera I have. The backend is written in python and works like this:
Acquire image as numpy array.
Convert numpy array to jpeg.
Convert jpeg to base64 string.
Send string over websocket.
def image_to_bytes(image):
print('into: ', image.shape)
buf = cv2.imencode('.jpg', image)[1]
dec = cv2.imdecode(buf, cv2.IMREAD_COLOR)
print('outa: ', dec.shape)
return base64.b64encode(buf).decode('utf-8')
async def send_image(websocket: WebSocket):
cam = Camera()
for im in cam:
w, h = im.shape[:2]
resized = cv2.resize(im, (w // 4, h // 4), interpolation=cv2.INTER_LINEAR)
await websocket.send_bytes(image_to_bytes(resized))
However when the javascript frontend receives the image the dimensions are swapped which distorts the image.
socket.onmessage = function(event) {
let im = new Image();
const buf = event.data;
im.src = 'data:image/jpeg;base64,' + buf;
im.onload = function() {
context.drawImage(im, 0, 0);
console.log('w=' + im.width + ', h=' + im.height);
};
};
I know that the dimensions are swapped because I checked the dimensions before I encoded the image. Then I decoded it again to make sure the ecoding process didn't swap width and height. Finally I check the dimension on the JS side and width and height are reversed.
Any idea why the dimensions are getting swapped?
From the documentation of OpenCV:
The shape of an image is accessed by img.shape. It returns a tuple of the number of rows, columns, and channels (if the image is color):
Hence instead of w, h = im.shape[:2] you need h, w = im.shape[:2].
I just started learning pyOpenGL and ran into an issue, my first project is very simple: I'm trying to open a window and draw a simple triangle using shaders. I'm using glfw to create the window and everything compiles properly, but the Triangle isn't being drawn.
Is something wrong with my main loop or my use of a vertex buffer? My shaders and program objects (seem to) work.
Any help would be greatly appreciated.
import OpenGL.GL
import OpenGL
import glfw
import numpy
def createAndCompileShader(type, source):
shader = OpenGL.GL.glCreateShader(type)
OpenGL.GL.glShaderSource(shader, source)
OpenGL.GL.glCompileShader(shader)
result = OpenGL.GL.glGetShaderiv(shader, OpenGL.GL.GL_COMPILE_STATUS)
if result != 1:
raise Exception("Shader didn't compile properly\nShader compilation Log:\n" + str(OpenGL.GL.glGetShaderInfoLog(shader)))
return shader
def createProgramWithShaders(shaders):
ProgramIdentification = OpenGL.GL.glCreateProgram()
for s in shaders:
OpenGL.GL.glAttachShader(ProgramIdentification, s)
OpenGL.GL.glLinkProgram(ProgramIdentification)
linkStatus = OpenGL.GL.glGetProgramiv(ProgramIdentification, OpenGL.GL.GL_LINK_STATUS)
infoLogLength = OpenGL.GL.glGetProgramiv(ProgramIdentification, OpenGL.GL.GL_INFO_LOG_LENGTH)
for s in shaders:
OpenGL.GL.glDetachShader(ProgramIdentification, s)
for s in shaders:
OpenGL.GL.glDeleteShader(s)
return ProgramIdentification
glfw.init()
glfw.window_hint(glfw.SAMPLES, 4)
glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, 3)
glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 3)
glfw.window_hint(glfw.OPENGL_FORWARD_COMPAT, OpenGL.GL.GL_TRUE)
glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE)
window = glfw.create_window(1000,800,"TESTING GLFW", None, None)
glfw.make_context_current(window)
glfw.set_input_mode(window,glfw.STICKY_KEYS, OpenGL.GL.GL_TRUE)
VAO = OpenGL.GL.glGenVertexArrays(1)
OpenGL.GL.glBindVertexArray(VAO)
vektoren = numpy.array([-1.0, -1.0, 0.0, 1.0, -1.0, 0.0, 0.0, 1.0, 0.0], dtype='float32')
vertexBuffer = OpenGL.GL.glGenBuffers(1)
OpenGL.GL.glBindBuffer(OpenGL.GL.GL_ARRAY_BUFFER, vertexBuffer)
OpenGL.GL.glBufferData(OpenGL.GL.GL_ARRAY_BUFFER,len(vektoren), vektoren, OpenGL.GL.GL_STATIC_DRAW)
VertexSource = open("simpleVertexShader.glsl")
FragmentSource = open("simpleFragmentShader.glsl")
shaders = [createAndCompileShader(OpenGL.GL.GL_VERTEX_SHADER, VertexSource.read()), createAndCompileShader(OpenGL.GL.GL_FRAGMENT_SHADER, FragmentSource.read())]
VertexSource.close()
FragmentSource.close()
ProgramID = createProgramWithShaders(shaders)
OpenGL.GL.glClearColor(0.0,1.0,0.0,1.0)
while not glfw.window_should_close(window) and glfw.get_key(window, glfw.KEY_ESCAPE) != glfw.PRESS:
OpenGL.GL.glClear( OpenGL.GL.GL_COLOR_BUFFER_BIT)
OpenGL.GL.glUseProgram(ProgramID)
OpenGL.GL.glEnableVertexAttribArray(0)
OpenGL.GL.glBindBuffer(OpenGL.GL.GL_ARRAY_BUFFER, vertexBuffer)
OpenGL.GL.glVertexAttribPointer(0, 3, OpenGL.GL.GL_FLOAT, OpenGL.GL.GL_FALSE, 0, None)
OpenGL.GL.glDrawArrays(OpenGL.GL.GL_TRIANGLES, 0, 3)
OpenGL.GL.glDisableVertexAttribArray(0)
glfw.swap_buffers(window)
glfw.poll_events()
glfw.terminate()
simpleVertexShader.glsl:
#version 330 core
layout(location = 0) in vec3 vertexposition_modelspace;
void main(){
gl_Position.xyz = vertexposition_modelspace;
gl_Position.w = 1.0;
}
simpleFragmentShader.glsl:
#version 330 core
out vec3 color;
void main(){
color = vec3(1,0,0);
}
The 2nd parameter glBufferData has to be the size of the buffer in bytes and not the number of elements of the array:
Calculate the size of the buffer in bytes, to solve your issue:
buffer_size = len(vektoren) * vektoren.itemsize
OpenGL.GL.glBufferData(OpenGL.GL.GL_ARRAY_BUFFER, buffer_size, vektoren, OpenGL.GL.GL_STATIC_DRAW)
Note, the array can be passed to glBufferData without setting the size explicitly:
OpenGL.GL.glBufferData(OpenGL.GL.GL_ARRAY_BUFFER, vektoren, OpenGL.GL.GL_STATIC_DRAW)
I recently started to learn OpenGL through Python thanks to several tutorial (especially the Nicolas P. Rougier one: http://www.labri.fr/perso/nrougier/teaching/opengl/).
I am now switching to 3D and I am trying to draw a cube.
Thus, I manage to get some triangles which do not render a cube (this seems to be normal as I do not duplicate my vertices and I use the glDrawArrays function).
However, after, I build an index "vector" to further use the glDrawElements function to render my cube. As a result, I do not get any error but nothing appears on screen.
I hope you could be of some help!
Here is my code:
#! /usr/bin/env python
# -*- coding: utf-8 -*-
import sys
import ctypes
import numpy as np
import OpenGL.GL as gl
import OpenGL.GLUT as glut
vertex_code = """
uniform float scale;
uniform mat4 matCam;
attribute vec4 color;
attribute vec3 position;
varying vec4 v_color;
void main()
{
gl_Position = matCam*vec4(scale*position, 1.0);
v_color = color;
} """
fragment_code = """
varying vec4 v_color;
void main()
{
gl_FragColor = v_color;
} """
def display():
gl.glClear(gl.GL_COLOR_BUFFER_BIT)
#gl.glDrawArrays(gl.GL_TRIANGLES, 0, 12)
gl.glDrawElements(gl.GL_TRIANGLES, len(index), gl.GL_UNSIGNED_INT, index) # render nothing (i.e. only the background color)
glut.glutSwapBuffers()
def reshape(width,height):
gl.glViewport(0, 0, width, height)
def keyboard( key, x, y ):
if key == '\033':
sys.exit( )
def timer(fps):
global clock
clock += 0.0005*1000.0/fps
print(clock)
# eye = np.array([0,0,1])
# center = np.array([0,clock,0])
# up = np.array([0,1,0])
# mat = computeLookAtMatrix(eye, center, up)
theta = clock;
mat = np.array([[np.cos(theta), 0, np.sin(theta), 0],
[0, 1, 0, 0],
[-np.sin(theta), 0, np.cos(theta), 0],
[0, 0, 0, 1]])
loc = gl.glGetUniformLocation(program, "matCam")
gl.glUniformMatrix4fv(loc, 1, False, mat)
glut.glutTimerFunc(1000/fps, timer, fps)
glut.glutPostRedisplay()
# GLUT init
# --------------------------------------
glut.glutInit()
glut.glutInitDisplayMode(glut.GLUT_DOUBLE | glut.GLUT_RGBA)
glut.glutCreateWindow('Hello world!')
glut.glutReshapeWindow(512,512)
glut.glutReshapeFunc(reshape)
glut.glutDisplayFunc(display)
glut.glutKeyboardFunc(keyboard)
glut.glutTimerFunc(1000/60, timer, 60)
# Build data
# --------------------------------------
data = np.zeros(8, [("position", np.float32, 3),
("color", np.float32, 4)])
data['color'] = [ (1,0,0,1), (0,1,0,1), (0,0,1,1), (1,1,0,1),
(1,0,0,1), (0,1,0,1), (0,0,1,1), (1,1,0,1) ]
data['position'] = [ (-1,-1,1),
(1,-1,1),
(1,1,1),
(-1,1,1),
(-1,-1,-1),
(1,-1,-1),
(1,1,-1),
(-1,1,-1)]
index = np.array([0,1,2,
2,3,0,
1,5,6,
6,2,1,
7,6,5,
5,4,7,
4,0,3,
3,7,4,
4,5,1,
1,0,4,
3,2,6,
6,7,3])
# Build & activate program
# --------------------------------------
# Request a program and shader slots from GPU
program = gl.glCreateProgram()
vertex = gl.glCreateShader(gl.GL_VERTEX_SHADER)
fragment = gl.glCreateShader(gl.GL_FRAGMENT_SHADER)
# Set shaders source
gl.glShaderSource(vertex, vertex_code)
gl.glShaderSource(fragment, fragment_code)
# Compile shaders
gl.glCompileShader(vertex)
gl.glCompileShader(fragment)
# Attach shader objects to the program
gl.glAttachShader(program, vertex)
gl.glAttachShader(program, fragment)
# Build program
gl.glLinkProgram(program)
# Get rid of shaders (no more needed)
gl.glDetachShader(program, vertex)
gl.glDetachShader(program, fragment)
# Make program the default program
gl.glUseProgram(program)
# Build buffer
# --------------------------------------
# Request a buffer slot from GPU
buffer = gl.glGenBuffers(1)
# Make this buffer the default one
gl.glBindBuffer(gl.GL_ARRAY_BUFFER, buffer)
# Upload data
gl.glBufferData(gl.GL_ARRAY_BUFFER, data.nbytes, data, gl.GL_DYNAMIC_DRAW)
# same for index buffer
buffer_index= gl.glGenBuffers(1)
gl.glBindBuffer(gl.GL_ELEMENT_ARRAY_BUFFER, buffer_index)
gl.glBufferData(gl.GL_ELEMENT_ARRAY_BUFFER, index.nbytes, index, gl.GL_STATIC_DRAW)
# Bind attributes
# --------------------------------------
stride = data.strides[0]
offset = ctypes.c_void_p(0)
loc = gl.glGetAttribLocation(program, "position")
gl.glEnableVertexAttribArray(loc)
gl.glBindBuffer(gl.GL_ARRAY_BUFFER, buffer)
gl.glVertexAttribPointer(loc, 3, gl.GL_FLOAT, False, stride, offset)
offset = ctypes.c_void_p(data.dtype["position"].itemsize)
loc = gl.glGetAttribLocation(program, "color")
gl.glEnableVertexAttribArray(loc)
gl.glBindBuffer(gl.GL_ARRAY_BUFFER, buffer)
gl.glVertexAttribPointer(loc, 4, gl.GL_FLOAT, False, stride, offset)
gl.glBindBuffer(gl.GL_ELEMENT_ARRAY_BUFFER, buffer_index)
# Bind uniforms
# --------------------------------------
loc = gl.glGetUniformLocation(program, "scale")
gl.glUniform1f(loc, 0.5)
clock = 0
loc = gl.glGetUniformLocation(program, "matCam")
print(loc)
gl.glUniformMatrix4fv(loc, 1, False, np.eye(4))
# Enter mainloop
# --------------------------------------
glut.glutMainLoop()
gl.glDrawElements(gl.GL_TRIANGLES, len(index), gl.GL_UNSIGNED_INT, index)
The index argument has two different meanings depending on whether an ELEMENT_ARRAY_BUFFER is bound or not:
If no bound: Then it specifies a pointer to client memory where the indices are stored
If a ELEMENT_ARRAY_BUFFER is bound, then the index parameter specifies an offset into this buffer. This defines where in the buffer the indices start.
In your case, a buffer is bound, so you tell OpenGL to start somewhere in the buffer. But what you want is to start at the beginning of the buffer, thus you have to set index to 0.
gl.glDrawElements(gl.GL_TRIANGLES, len(index), gl.GL_UNSIGNED_INT, ctypes.c_void_p(0))