xbe.at

Computer vision

3D Graphics Foundations Rendering Triangles in Python

14 min read, 15 sections

Contents
  1. Understanding 3D Coordinate Systems in Python
  2. Basic Matrix Transformations
  3. Vector-Matrix Multiplication Implementation
  4. Building a Mesh Class
  5. Implementing Rotation Transformations
  6. Projection Matrix Implementation
  7. Implementing the Graphics Pipeline
  8. Matrix Performance Optimization
  9. Implementing Model View Projection (MVP)
  10. GPU-like Parallel Processing Simulation
  11. Real-time Animation Pipeline
  12. Optimized Triangle Rasterization
  13. Implementing Face Culling and Clipping
  14. Scene Graph Implementation
  15. Additional Resources

Understanding 3D Coordinate Systems in Python

The foundation of 3D graphics begins with understanding coordinate systems. In computer graphics, we represent points in 3D space using vectors containing X, Y, and Z coordinates. Python's numpy library provides efficient tools for handling these coordinate systems.

import numpy as np

class Point3D:
    def __init__(self, x, y, z):
        self.coords = np.array([x, y, z])
    
    def get_coordinates(self):
        return self.coords

# Create a triangle in 3D space
triangle = [
    Point3D(1, 0, 0),
    Point3D(0, 1, 0),
    Point3D(0, 0, 1)
]

# Display triangle vertices
for i, point in enumerate(triangle):
    print(f"Vertex {i + 1}: {point.get_coordinates()}")

# Output:
# Vertex 1: [1 0 0]
# Vertex 2: [0 1 0]
# Vertex 3: [0 0 1]

Basic Matrix Transformations

Matrix transformations are fundamental operations in 3D graphics. They allow us to rotate, scale, and translate objects in 3D space. These transformations are represented as 4x4 matrices using homogeneous coordinates.

import numpy as np

def create_rotation_matrix(angle, axis='x'):
    """Create rotation matrix for specified axis and angle (in radians)"""
    c, s = np.cos(angle), np.sin(angle)
    if axis.lower() == 'x':
        return np.array([
            [1, 0,  0, 0],
            [0, c, -s, 0],
            [0, s,  c, 0],
            [0, 0,  0, 1]
        ])
    elif axis.lower() == 'y':
        return np.array([
            [ c, 0, s, 0],
            [ 0, 1, 0, 0],
            [-s, 0, c, 0],
            [ 0, 0, 0, 1]
        ])
    else:  # z-axis
        return np.array([
            [c, -s, 0, 0],
            [s,  c, 0, 0],
            [0,  0, 1, 0],
            [0,  0, 0, 1]
        ])

# Example rotation of 45 degrees around X axis
angle = np.pi/4  # 45 degrees
rotation_matrix = create_rotation_matrix(angle, 'x')
print("Rotation Matrix (45° around X):\n", rotation_matrix)

Vector-Matrix Multiplication Implementation

Understanding how vectors and matrices multiply is crucial for 3D transformations. We implement a custom class that handles vector-matrix multiplication for 3D graphics calculations efficiently.

import numpy as np

class Transform3D:
    def __init__(self, matrix=None):
        self.matrix = matrix if matrix is not None else np.eye(4)
    
    def apply_to_point(self, point):
        # Convert 3D point to homogeneous coordinates
        homogeneous_point = np.append(point, 1)
        # Apply transformation
        transformed = np.dot(self.matrix, homogeneous_point)
        # Convert back to 3D coordinates
        return transformed[:3] / transformed[3]

# Example usage
point = np.array([1, 0, 0])
rotation = create_rotation_matrix(np.pi/4, 'z')
transform = Transform3D(rotation)

rotated_point = transform.apply_to_point(point)
print(f"Original point: {point}")
print(f"Rotated point: {rotated_point}")

Building a Mesh Class

The Mesh class serves as the foundation for representing 3D objects. It manages collections of vertices and faces, providing methods for transformation and manipulation of 3D geometry.

class Mesh:
    def __init__(self, vertices, faces):
        self.vertices = np.array(vertices)
        self.faces = np.array(faces)
        
    def transform(self, transformation_matrix):
        # Create homogeneous coordinates
        homogeneous_vertices = np.hstack((
            self.vertices, 
            np.ones((len(self.vertices), 1))
        ))
        
        # Apply transformation
        transformed = np.dot(homogeneous_vertices, 
                           transformation_matrix.T)
        
        # Convert back to 3D coordinates
        self.vertices = transformed[:, :3] / transformed[:, 3:]
        
    def get_triangles(self):
        return [self.vertices[face] for face in self.faces]

# Create a simple pyramid
vertices = np.array([
    [0, 0, 0],  # base
    [1, 0, 0],
    [1, 1, 0],
    [0, 1, 0],
    [0.5, 0.5, 1]  # apex
])

faces = np.array([
    [0, 1, 4],  # triangular faces
    [1, 2, 4],
    [2, 3, 4],
    [3, 0, 4]
])

pyramid = Mesh(vertices, faces)

Implementing Rotation Transformations

A comprehensive implementation of rotation transformations requires handling Euler angles and quaternions. This implementation demonstrates how to create and combine multiple rotation transformations for complex 3D movements.

import numpy as np
from math import cos, sin

class Rotation3D:
    @staticmethod
    def from_euler(phi, theta, psi):
        # Create rotation matrices for each axis
        Rx = np.array([
            [1, 0, 0],
            [0, cos(phi), -sin(phi)],
            [0, sin(phi), cos(phi)]
        ])
        
        Ry = np.array([
            [cos(theta), 0, sin(theta)],
            [0, 1, 0],
            [-sin(theta), 0, cos(theta)]
        ])
        
        Rz = np.array([
            [cos(psi), -sin(psi), 0],
            [sin(psi), cos(psi), 0],
            [0, 0, 1]
        ])
        
        # Combine rotations (order: Z * Y * X)
        return np.dot(Rz, np.dot(Ry, Rx))

# Example usage
angles = [np.pi/4, np.pi/6, np.pi/3]  # 45°, 30°, 60°
combined_rotation = Rotation3D.from_euler(*angles)
print("Combined rotation matrix:\n", combined_rotation)

Projection Matrix Implementation

The projection matrix transforms 3D coordinates into 2D screen coordinates, simulating perspective. This implementation includes both perspective and orthographic projection matrices commonly used in computer graphics.

import numpy as np

class ProjectionMatrix:
    @staticmethod
    def perspective(fov, aspect, near, far):
        f = 1.0 / np.tan(fov / 2)
        return np.array([
            [f/aspect, 0, 0, 0],
            [0, f, 0, 0],
            [0, 0, (far+near)/(near-far), (2*far*near)/(near-far)],
            [0, 0, -1, 0]
        ])
    
    @staticmethod
    def orthographic(left, right, bottom, top, near, far):
        return np.array([
            [2/(right-left), 0, 0, -(right+left)/(right-left)],
            [0, 2/(top-bottom), 0, -(top+bottom)/(top-bottom)],
            [0, 0, -2/(far-near), -(far+near)/(far-near)],
            [0, 0, 0, 1]
        ])

# Example usage
perspective = ProjectionMatrix.perspective(
    fov=np.pi/4,    # 45 degrees
    aspect=16/9,    # Widescreen aspect ratio
    near=0.1,       # Near clipping plane
    far=100.0       # Far clipping plane
)

print("Perspective projection matrix:\n", perspective)

Implementing the Graphics Pipeline

The graphics pipeline transforms 3D vertices through multiple stages: model transformation, view transformation, and projection. This implementation demonstrates the complete pipeline used in modern graphics engines.

class GraphicsPipeline:
    def __init__(self, width, height):
        self.width = width
        self.height = height
        self.model_matrix = np.eye(4)
        self.view_matrix = np.eye(4)
        self.projection_matrix = ProjectionMatrix.perspective(
            np.pi/4, width/height, 0.1, 100.0
        )
    
    def set_camera(self, position, target, up):
        forward = target - position
        forward = forward / np.linalg.norm(forward)
        right = np.cross(forward, up)
        right = right / np.linalg.norm(right)
        up = np.cross(right, forward)
        
        self.view_matrix = np.array([
            [right[0], right[1], right[2], -np.dot(right, position)],
            [up[0], up[1], up[2], -np.dot(up, position)],
            [-forward[0], -forward[1], -forward[2], np.dot(forward, position)],
            [0, 0, 0, 1]
        ])
    
    def transform_vertex(self, vertex):
        # Convert to homogeneous coordinates
        v = np.append(vertex, 1)
        
        # Apply transformations
        v = np.dot(self.model_matrix, v)
        v = np.dot(self.view_matrix, v)
        v = np.dot(self.projection_matrix, v)
        
        # Perspective divide
        if v[3] != 0:
            v = v / v[3]
        
        # Convert to screen coordinates
        screen_x = int((v[0] + 1) * self.width / 2)
        screen_y = int((1 - v[1]) * self.height / 2)
        
        return np.array([screen_x, screen_y])

# Example usage
pipeline = GraphicsPipeline(1920, 1080)
pipeline.set_camera(
    position=np.array([0, 0, 5]),
    target=np.array([0, 0, 0]),
    up=np.array([0, 1, 0])
)

# Transform a vertex
vertex = np.array([1, 1, 1])
screen_coords = pipeline.transform_vertex(vertex)
print(f"Screen coordinates: {screen_coords}")

Matrix Performance Optimization

Understanding matrix multiplication performance is crucial for graphics applications. This implementation demonstrates various optimization techniques including vectorization and cache-friendly memory access patterns.

import numpy as np
import time

class OptimizedMatrixOps:
    @staticmethod
    def naive_multiply(A, B):
        n = len(A)
        result = np.zeros((n, n))
        for i in range(n):
            for j in range(n):
                for k in range(n):
                    result[i][j] += A[i][k] * B[k][j]
        return result
    
    @staticmethod
    def optimized_multiply(A, B):
        # Using numpy's optimized dot product
        return np.dot(A, B)
    
    @staticmethod
    def benchmark(size=1000):
        A = np.random.rand(size, size)
        B = np.random.rand(size, size)
        
        start = time.time()
        _ = OptimizedMatrixOps.optimized_multiply(A, B)
        optimized_time = time.time() - start
        
        if size <= 100:  # Only run naive for small matrices
            start = time.time()
            _ = OptimizedMatrixOps.naive_multiply(A, B)
            naive_time = time.time() - start
            print(f"Naive multiplication time: {naive_time:.4f}s")
            
        print(f"Optimized multiplication time: {optimized_time:.4f}s")

# Run benchmark
OptimizedMatrixOps.benchmark(size=100)

Implementing Model View Projection (MVP)

The Model View Projection matrix combines object transformation, camera positioning, and perspective projection. This implementation shows how to construct and apply the complete MVP transformation pipeline.

class MVPTransform:
    def __init__(self):
        self.model = np.eye(4)
        self.view = np.eye(4)
        self.projection = np.eye(4)
    
    def set_model_transform(self, position, rotation, scale):
        # Translation matrix
        translation = np.array([
            [1, 0, 0, position[0]],
            [0, 1, 0, position[1]],
            [0, 0, 1, position[2]],
            [0, 0, 0, 1]
        ])
        
        # Scale matrix
        scale_matrix = np.array([
            [scale[0], 0, 0, 0],
            [0, scale[1], 0, 0],
            [0, 0, scale[2], 0],
            [0, 0, 0, 1]
        ])
        
        # Get rotation matrix using previous Rotation3D class
        rotation_matrix = np.eye(4)
        rotation_matrix[:3, :3] = Rotation3D.from_euler(*rotation)
        
        # Combine transformations
        self.model = translation @ rotation_matrix @ scale_matrix
    
    def get_mvp(self):
        return self.projection @ self.view @ self.model
    
    def transform_vertices(self, vertices):
        # Convert vertices to homogeneous coordinates
        homogeneous = np.hstack((vertices, np.ones((len(vertices), 1))))
        
        # Apply MVP transformation
        mvp = self.get_mvp()
        transformed = homogeneous @ mvp.T
        
        # Perspective division
        transformed = transformed[:, :3] / transformed[:, 3:]
        return transformed

# Example usage
mvp = MVPTransform()
mvp.set_model_transform(
    position=np.array([0, 0, -5]),
    rotation=np.array([0, np.pi/4, 0]),
    scale=np.array([1, 1, 1])
)

# Transform vertices of a cube
cube_vertices = np.array([
    [-1, -1, -1], [1, -1, -1], [1, 1, -1], [-1, 1, -1],
    [-1, -1, 1], [1, -1, 1], [1, 1, 1], [-1, 1, 1]
])

transformed_vertices = mvp.transform_vertices(cube_vertices)
print("Transformed vertices:\n", transformed_vertices)

GPU-like Parallel Processing Simulation

This implementation simulates GPU-like parallel processing for matrix operations using Python's multiprocessing capabilities, demonstrating how GPUs accelerate graphics computations.

import multiprocessing as mp
from functools import partial
import numpy as np

class GPUSimulator:
    def __init__(self, num_processors=None):
        self.num_processors = num_processors or mp.cpu_count()
    
    def parallel_matrix_multiply(self, A, B):
        pool = mp.Pool(self.num_processors)
        result = np.zeros_like(A)
        
        def process_row(row_idx):
            return np.dot(A[row_idx], B)
        
        # Parallel processing of matrix rows
        results = pool.map(process_row, range(len(A)))
        pool.close()
        pool.join()
        
        return np.array(results)
    
    def batch_transform_vertices(self, vertices, transformation_matrix):
        # Split vertices into batches
        batch_size = len(vertices) // self.num_processors
        batches = [vertices[i:i + batch_size] for i in range(0, len(vertices), batch_size)]
        
        # Process batches in parallel
        pool = mp.Pool(self.num_processors)
        transform_func = partial(np.dot, b=transformation_matrix)
        results = pool.map(transform_func, batches)
        pool.close()
        pool.join()
        
        return np.vstack(results)

# Example usage
gpu_sim = GPUSimulator()
A = np.random.rand(1000, 1000)
B = np.random.rand(1000, 1000)

# Compare execution times
start = time.time()
result_parallel = gpu_sim.parallel_matrix_multiply(A, B)
parallel_time = time.time() - start

start = time.time()
result_numpy = np.dot(A, B)
numpy_time = time.time() - start

print(f"Parallel execution time: {parallel_time:.4f}s")
print(f"NumPy execution time: {numpy_time:.4f}s")

Real-time Animation Pipeline

This implementation demonstrates a complete animation pipeline for real-time 3D graphics, including interpolation between keyframes and smooth transformations for continuous motion.

import numpy as np
from dataclasses import dataclass
from typing import List
import time

@dataclass
class Keyframe:
    position: np.ndarray
    rotation: np.ndarray
    scale: np.ndarray
    time: float

class AnimationSystem:
    def __init__(self):
        self.keyframes: List[Keyframe] = []
        self.current_time = 0.0
    
    def add_keyframe(self, keyframe: Keyframe):
        self.keyframes.append(keyframe)
        # Sort keyframes by time
        self.keyframes.sort(key=lambda x: x.time)
    
    def interpolate(self, t: float) -> Keyframe:
        # Find surrounding keyframes
        next_idx = next((i for i, kf in enumerate(self.keyframes) 
                        if kf.time > t), len(self.keyframes))
        if next_idx == 0:
            return self.keyframes[0]
        if next_idx == len(self.keyframes):
            return self.keyframes[-1]
            
        prev_kf = self.keyframes[next_idx - 1]
        next_kf = self.keyframes[next_idx]
        
        # Calculate interpolation factor
        alpha = ((t - prev_kf.time) / 
                (next_kf.time - prev_kf.time))
        
        # Linear interpolation
        return Keyframe(
            position=prev_kf.position * (1-alpha) + next_kf.position * alpha,
            rotation=prev_kf.rotation * (1-alpha) + next_kf.rotation * alpha,
            scale=prev_kf.scale * (1-alpha) + next_kf.scale * alpha,
            time=t
        )
    
    def update(self, delta_time: float):
        self.current_time += delta_time
        return self.interpolate(self.current_time)

# Example usage
animation = AnimationSystem()

# Add keyframes for a simple rotation animation
animation.add_keyframe(Keyframe(
    position=np.array([0, 0, 0]),
    rotation=np.array([0, 0, 0]),
    scale=np.array([1, 1, 1]),
    time=0.0
))

animation.add_keyframe(Keyframe(
    position=np.array([0, 0, 0]),
    rotation=np.array([0, np.pi, 0]),
    scale=np.array([1, 1, 1]),
    time=2.0
))

# Simulate animation updates
for _ in range(5):
    frame = animation.update(0.5)
    print(f"Time: {animation.current_time:.1f}s, "
          f"Rotation: {frame.rotation}")

Optimized Triangle Rasterization

A crucial part of the graphics pipeline is converting 3D triangles into pixels. This implementation shows an efficient scanline algorithm for triangle rasterization with edge walking.

import numpy as np
from typing import Tuple, List

class Rasterizer:
    def __init__(self, width: int, height: int):
        self.width = width
        self.height = height
        self.framebuffer = np.zeros((height, width), dtype=np.float32)
        self.zbuffer = np.full((height, width), np.inf)
    
    def edge_function(self, a: np.ndarray, b: np.ndarray, 
                     c: np.ndarray) -> float:
        return ((c[0] - a[0]) * (b[1] - a[1]) - 
                (c[1] - a[1]) * (b[0] - a[0]))
    
    def rasterize_triangle(self, vertices: List[np.ndarray], 
                          color: float = 1.0):
        # Compute bounding box
        min_x = max(0, int(min(v[0] for v in vertices)))
        max_x = min(self.width - 1, int(max(v[0] for v in vertices)))
        min_y = max(0, int(min(v[1] for v in vertices)))
        max_y = min(self.height - 1, int(max(v[1] for v in vertices)))
        
        # Precompute edge functions
        area = self.edge_function(vertices[0], vertices[1], vertices[2])
        if area == 0:
            return
        
        # Rasterize
        for y in range(min_y, max_y + 1):
            for x in range(min_x, max_x + 1):
                point = np.array([x + 0.5, y + 0.5])
                
                # Compute barycentric coordinates
                w0 = self.edge_function(vertices[1], vertices[2], point)
                w1 = self.edge_function(vertices[2], vertices[0], point)
                w2 = self.edge_function(vertices[0], vertices[1], point)
                
                # Check if point is inside triangle
                if w0 >= 0 and w1 >= 0 and w2 >= 0:
                    # Normalize barycentric coordinates
                    w0 /= area
                    w1 /= area
                    w2 /= area
                    
                    # Interpolate z-value
                    z = (w0 * vertices[0][2] + 
                         w1 * vertices[1][2] + 
                         w2 * vertices[2][2])
                    
                    # Z-buffer test
                    if z < self.zbuffer[y, x]:
                        self.zbuffer[y, x] = z
                        self.framebuffer[y, x] = color

# Example usage
rasterizer = Rasterizer(800, 600)
triangle = [
    np.array([100, 100, 0]),
    np.array([700, 100, 0]),
    np.array([400, 500, 0])
]
rasterizer.rasterize_triangle(triangle)

print("Number of pixels rasterized:", 
      np.sum(rasterizer.framebuffer > 0))

Implementing Face Culling and Clipping

Face culling and clipping are essential optimizations in 3D graphics. This implementation demonstrates how to efficiently remove hidden faces and clip geometry against the view frustum.

import numpy as np
from dataclasses import dataclass
from typing import List, Tuple

@dataclass
class Plane:
    normal: np.ndarray
    distance: float

class FrustumClipper:
    def __init__(self):
        # Define view frustum planes (near, far, left, right, top, bottom)
        self.frustum_planes = [
            Plane(np.array([0, 0, 1]), -0.1),  # Near
            Plane(np.array([0, 0, -1]), 100),  # Far
            Plane(np.array([1, 0, 0]), 1),     # Left
            Plane(np.array([-1, 0, 0]), 1),    # Right
            Plane(np.array([0, 1, 0]), 1),     # Bottom
            Plane(np.array([0, -1, 0]), 1)     # Top
        ]
    
    def is_face_visible(self, vertices: np.ndarray) -> bool:
        # Calculate face normal using cross product
        v1 = vertices[1] - vertices[0]
        v2 = vertices[2] - vertices[0]
        normal = np.cross(v1, v2)
        
        # Check if face is facing camera (basic back-face culling)
        return np.dot(normal, vertices[0]) < 0
    
    def clip_triangle(self, vertices: np.ndarray) -> List[np.ndarray]:
        if not self.is_face_visible(vertices):
            return []
        
        current_vertices = vertices.tolist()
        
        # Clip against each frustum plane
        for plane in self.frustum_planes:
            if not current_vertices:
                return []
            
            next_vertices = []
            
            # Process each edge of the polygon
            for i in range(len(current_vertices)):
                current = np.array(current_vertices[i])
                next = np.array(current_vertices[(i + 1) % len(current_vertices)])
                
                current_inside = (np.dot(plane.normal, current) + plane.distance) > 0
                next_inside = (np.dot(plane.normal, next) + plane.distance) > 0
                
                if current_inside:
                    next_vertices.append(current)
                
                if current_inside != next_inside:
                    # Calculate intersection point
                    t = (-plane.distance - np.dot(plane.normal, current)) / \
                        np.dot(plane.normal, next - current)
                    intersection = current + t * (next - current)
                    next_vertices.append(intersection)
            
            current_vertices = next_vertices
        
        return current_vertices

# Example usage
clipper = FrustumClipper()

# Test triangle
triangle = np.array([
    [-0.5, -0.5, -1],
    [0.5, -0.5, -1],
    [0, 0.5, -1]
])

# Clip triangle
clipped_vertices = clipper.clip_triangle(triangle)
print(f"Original vertices: {len(triangle)}")
print(f"Clipped vertices: {len(clipped_vertices)}")

Scene Graph Implementation

A scene graph organizes 3D objects hierarchically, allowing for complex transformations and relationships between objects. This implementation shows how to build and traverse a scene graph efficiently.

from typing import Optional, List
import numpy as np

class SceneNode:
    def __init__(self, name: str):
        self.name = name
        self.local_transform = np.eye(4)
        self.world_transform = np.eye(4)
        self.parent: Optional[SceneNode] = None
        self.children: List[SceneNode] = []
        self.mesh: Optional[np.ndarray] = None
    
    def add_child(self, child: 'SceneNode'):
        child.parent = self
        self.children.append(child)
    
    def set_transform(self, translation: np.ndarray, 
                     rotation: np.ndarray, scale: np.ndarray):
        # Create transformation matrix
        T = np.eye(4)
        T[:3, 3] = translation
        
        R = np.eye(4)
        R[:3, :3] = Rotation3D.from_euler(*rotation)
        
        S = np.eye(4)
        np.fill_diagonal(S[:3, :3], scale)
        
        self.local_transform = T @ R @ S
        self.update_world_transform()
    
    def update_world_transform(self):
        if self.parent is None:
            self.world_transform = self.local_transform
        else:
            self.world_transform = (self.parent.world_transform @ 
                                  self.local_transform)
        
        # Update children
        for child in self.children:
            child.update_world_transform()
    
    def traverse(self, callback):
        callback(self)
        for child in self.children:
            child.traverse(callback)

# Example usage
def create_robot_arm():
    root = SceneNode("root")
    base = SceneNode("base")
    upper_arm = SceneNode("upper_arm")
    forearm = SceneNode("forearm")
    hand = SceneNode("hand")
    
    # Build hierarchy
    root.add_child(base)
    base.add_child(upper_arm)
    upper_arm.add_child(forearm)
    forearm.add_child(hand)
    
    # Set transforms
    base.set_transform(
        translation=np.array([0, 0, 0]),
        rotation=np.array([0, 0, 0]),
        scale=np.array([1, 1, 1])
    )
    
    upper_arm.set_transform(
        translation=np.array([0, 1, 0]),
        rotation=np.array([0, 0, np.pi/4]),
        scale=np.array([1, 2, 1])
    )
    
    return root

# Create and traverse scene graph
robot = create_robot_arm()
def print_node(node):
    print(f"Node: {node.name}")
    print(f"World transform:\n{node.world_transform}")

robot.traverse(print_node)

Additional Resources

Note: The URLs provided are examples and may not correspond to actual papers, as I cannot verify their existence.

More in Computer vision

  1. 7 Transfer Learning Models for Convolutional Neural Networks12 min

    Transfer learning is a powerful technique in deep learning where knowledge gained from training on one task is applied to a different but related task. In...

  2. Active Shape Model for Face Image Generation8 min

    Active Shape Models (ASM) are statistical models of shape that can deform to fit new examples. They are particularly useful in face recognition and image...

  3. Advanced ECG Analysis Using Transfer Learning and CNNs7 min

    This project focuses on classifying ECG images using advanced deep learning techniques. We'll explore data preparation, model development, and the...

  4. Best Face Recognition Models in Python10 min

    Face recognition is a biometric technology that identifies or verifies a person's identity using their facial features. This technology has gained...

  5. Comparing 2-Layer CNN Models on CIFAR-108 min

    Two 2-layer CNN models were trained on the CIFAR-10 dataset, resulting in different accuracies. Model A achieved 70% accuracy, while Model B reached 74%....

  6. Convolutional Neural Network Fundamentals13 min

    A convolutional neural network implementation focusing on the fundamental building blocks using NumPy. This base architecture demonstrates the core concepts...