<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged computervision</title>
<link>https://coderlegion.com/tag/computervision</link>
<description>Powered by Question2Answer</description>
<item>
<title>From Lab to Code #1: Exploring Classical Computer Vision with Python and OpenCV</title>
<link>https://coderlegion.com/32074/from-lab-to-code-1-exploring-classical-computer-vision-with-python-and-opencv</link>
<description>&lt;p&gt;Most conversations about computer vision today revolve around deep learning, neural networks, and increasingly sophisticated AI models.&lt;/p&gt;
&lt;p&gt;But not every image processing problem requires a trained model.&lt;/p&gt;
&lt;p&gt;Sometimes, a Gaussian filter is enough to reduce noise. A threshold can separate objects from their background. A Fourier transform can reveal interference that is difficult to understand in the spatial domain.&lt;/p&gt;
&lt;p&gt;These techniques are relatively simple, mathematically interpretable, and still useful in practical applications.&lt;/p&gt;
&lt;p&gt;Recently, while exploring image and signal processing concepts in my computer engineering studies, I decided to start a small programming series called &lt;strong&gt;From Lab to Code&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The idea is simple: take engineering concepts I encounter, build independent implementations, visualize the results, and share what I learn.&lt;/p&gt;
&lt;p&gt;For the first project, I built a small interactive image processing toolkit using Python, OpenCV, NumPy, and Streamlit.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;GitHub repository:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Ertugrulmutlu/from-lab-to-code&quot;&gt;https://github.com/Ertugrulmutlu/from-lab-to-code&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Project 01 — Image Processing:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Ertugrulmutlu/from-lab-to-code/tree/main/01-image-processing&quot;&gt;https://github.com/Ertugrulmutlu/from-lab-to-code/tree/main/01-image-processing&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;what-are-we-building-&quot;&gt;What Are We Building?&lt;/h3&gt;
&lt;p&gt;The application explores four classical computer vision techniques:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Image denoising&lt;/strong&gt; using Gaussian and median filters.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Object counting&lt;/strong&gt; using illumination correction, thresholding, and connected components.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Periodic noise removal&lt;/strong&gt; using the Fast Fourier Transform (FFT).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Color clustering&lt;/strong&gt; using K-means.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The application provides interactive controls, generated example images, and visual comparisons.&lt;/p&gt;
&lt;p&gt;No pretrained models, API calls, or external datasets are required.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/Ertugrulmutlu/from-lab-to-code/main/01-image-processing/results/overview.png&quot; alt=&quot;Image processing examples&quot;&gt;&lt;/p&gt;
&lt;p&gt;All bundled demonstration images are generated programmatically.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;1-image-denoising-gaussian-vs-median-filtering&quot;&gt;1. Image Denoising: Gaussian vs. Median Filtering&lt;/h3&gt;
&lt;p&gt;Digital images are often affected by noise.&lt;/p&gt;
&lt;p&gt;Noise can originate from camera sensors, poor lighting conditions, signal transmission, or image acquisition hardware.&lt;/p&gt;
&lt;p&gt;Two common types are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Gaussian noise:&lt;/strong&gt; Random variations in pixel intensity, often modeled using a normal distribution.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Salt-and-pepper noise:&lt;/strong&gt; Individual pixels unexpectedly taking very dark or very bright values.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Different noise distributions require different filtering strategies.&lt;/p&gt;
&lt;h4 id=&quot;gaussian-filtering&quot;&gt;Gaussian Filtering&lt;/h4&gt;
&lt;p&gt;A Gaussian filter performs a weighted average of neighboring pixels.&lt;/p&gt;
&lt;p&gt;Pixels closer to the center receive higher weights, following a Gaussian distribution.&lt;/p&gt;
&lt;p&gt;Mathematically, the two-dimensional Gaussian kernel can be written as:&lt;/p&gt;
&lt;p&gt;G(x, y) = (1 / 2πσ²) × exp(-(x² + y²) / 2σ²)&lt;/p&gt;
&lt;p&gt;The parameter σ controls the amount of smoothing.&lt;/p&gt;
&lt;p&gt;A larger σ produces stronger smoothing but can also remove fine image details.&lt;/p&gt;
&lt;p&gt;In OpenCV, Gaussian filtering is straightforward:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import cv2

gaussian = cv2.GaussianBlur(
    image, (5, 5), 0
)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;median-filtering&quot;&gt;Median Filtering&lt;/h4&gt;
&lt;p&gt;Unlike Gaussian filtering, a median filter replaces the center pixel with the median value of its neighborhood.&lt;/p&gt;
&lt;p&gt;Consider these pixel values:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;[10, 11, 12, 13, 255]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Their average is 60.2, but their median is 12.&lt;/p&gt;
&lt;p&gt;If the value 255 represents an isolated noise spike, the median is much closer to the surrounding pixel values.&lt;/p&gt;
&lt;p&gt;This makes median filtering particularly useful for salt-and-pepper noise.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;median = cv2.medianBlur(image, 5)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;comparing-the-results&quot;&gt;Comparing the Results&lt;/h4&gt;
&lt;p&gt;The application applies both filters to the same noisy image.&lt;/p&gt;
&lt;p&gt;It also calculates the Mean Squared Error (MSE) against the original image when a reference is available.&lt;/p&gt;
&lt;p&gt;MSE = (1/N) × Σ(original - processed)²&lt;/p&gt;
&lt;p&gt;A lower MSE indicates a smaller average squared difference from the reference image.&lt;/p&gt;
&lt;p&gt;However, MSE alone does not fully describe perceptual image quality.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Key takeaway:&lt;/strong&gt; Gaussian filtering is useful for smoothing distributed noise, while median filtering is often more effective against isolated intensity spikes.&lt;/p&gt;
&lt;p&gt;Neither method is universally superior.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;2-object-counting-from-pixels-to-objects&quot;&gt;2. Object Counting: From Pixels to Objects&lt;/h3&gt;
&lt;p&gt;Suppose we want to count small objects placed on a table.&lt;/p&gt;
&lt;p&gt;At first, this sounds like a simple thresholding problem.&lt;/p&gt;
&lt;p&gt;If the objects are darker than the background, we could classify pixels based on their intensity.&lt;/p&gt;
&lt;p&gt;But what happens when the background illumination is uneven?&lt;/p&gt;
&lt;p&gt;One side of the image might be noticeably darker than the other, making a single global threshold unreliable.&lt;/p&gt;
&lt;h4 id=&quot;step-1-estimate-the-background&quot;&gt;Step 1: Estimate the Background&lt;/h4&gt;
&lt;p&gt;We can approximate slowly changing illumination using a large Gaussian blur.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;background = cv2.GaussianBlur(
    gray, (0, 0), sigmaX=30
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Subtracting the original image from the estimated background helps emphasize darker foreground objects.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;corrected = cv2.subtract(
    background, gray
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This reduces the influence of gradual illumination changes.&lt;/p&gt;
&lt;h4 id=&quot;step-2-apply-otsu-thresholding&quot;&gt;Step 2: Apply Otsu Thresholding&lt;/h4&gt;
&lt;p&gt;Otsu's method automatically selects a threshold based on the image's intensity distribution.&lt;/p&gt;
&lt;p&gt;Instead of manually choosing a number such as 100 or 150, it searches for a threshold that separates the intensity classes according to their statistical variance.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;_, mask = cv2.threshold(
    corrected,
    0,
    255,
    cv2.THRESH_BINARY + cv2.THRESH_OTSU
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The output is a binary image containing foreground and background pixels.&lt;/p&gt;
&lt;h4 id=&quot;step-3-identify-connected-components&quot;&gt;Step 3: Identify Connected Components&lt;/h4&gt;
&lt;p&gt;After thresholding, neighboring foreground pixels can be grouped into connected regions.&lt;/p&gt;
&lt;p&gt;OpenCV provides a useful function for this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;count, labels, stats, centroids = (
    cv2.connectedComponentsWithStats(
        mask, connectivity=8
    )
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The function returns component labels and information such as bounding boxes and pixel areas.&lt;/p&gt;
&lt;p&gt;By excluding the background and removing regions below a minimum area, we can estimate the number of objects.&lt;/p&gt;
&lt;p&gt;The project also applies a small morphological opening operation before counting to remove isolated foreground noise.&lt;/p&gt;
&lt;h4 id=&quot;where-can-this-be-useful-&quot;&gt;Where Can This Be Useful?&lt;/h4&gt;
&lt;p&gt;This approach can be applied to controlled visual inspection tasks involving separate objects, such as small manufactured components.&lt;/p&gt;
&lt;p&gt;However, it comes with important assumptions.&lt;/p&gt;
&lt;p&gt;Objects should be sufficiently separated, and the contrast between objects and background should be reasonably clear.&lt;/p&gt;
&lt;p&gt;Touching objects may merge into a single connected component.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Key takeaway:&lt;/strong&gt; Reliable object counting often requires preprocessing, not just thresholding.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;3-fourier-transform-removing-periodic-noise&quot;&gt;3. Fourier Transform: Removing Periodic Noise&lt;/h3&gt;
&lt;p&gt;This was one of the most interesting parts of the project.&lt;/p&gt;
&lt;p&gt;Images are normally represented in the spatial domain, where each pixel corresponds to a position.&lt;/p&gt;
&lt;p&gt;But some image artifacts are easier to identify in the frequency domain.&lt;/p&gt;
&lt;p&gt;For example, imagine an image affected by regularly repeating stripes.&lt;/p&gt;
&lt;p&gt;Those stripes represent periodic variations in pixel intensity.&lt;/p&gt;
&lt;h4 id=&quot;moving-to-the-frequency-domain&quot;&gt;Moving to the Frequency Domain&lt;/h4&gt;
&lt;p&gt;The two-dimensional Discrete Fourier Transform decomposes an image into spatial frequency components.&lt;/p&gt;
&lt;p&gt;Each component represents a particular frequency and orientation.&lt;/p&gt;
&lt;p&gt;Using NumPy:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import numpy as np

spectrum = np.fft.fft2(gray)

shifted = np.fft.fftshift(spectrum)

magnitude = np.log1p(
    np.abs(shifted)
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;fft2()&lt;/code&gt; computes the two-dimensional Fourier transform.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fftshift()&lt;/code&gt; moves the zero-frequency component to the center.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;abs()&lt;/code&gt; obtains the magnitude.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;log1p()&lt;/code&gt; makes the magnitude spectrum easier to visualize.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Periodic interference may appear as distinct peaks in the Fourier spectrum.&lt;/p&gt;
&lt;h4 id=&quot;removing-specific-frequencies&quot;&gt;Removing Specific Frequencies&lt;/h4&gt;
&lt;p&gt;If we know which frequencies correspond to interference, we can suppress them using a notch filter.&lt;/p&gt;
&lt;p&gt;A notch filter removes selected frequency regions while preserving much of the remaining spectrum.&lt;/p&gt;
&lt;p&gt;For real-valued images, the Fourier spectrum has conjugate symmetry.&lt;/p&gt;
&lt;p&gt;This means a sinusoidal interference component generally produces a symmetric pair of frequency peaks.&lt;/p&gt;
&lt;p&gt;Both should be handled to preserve the symmetry of the filtered spectrum.&lt;/p&gt;
&lt;p&gt;The basic operation is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;filtered_spectrum = shifted * mask
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here, &lt;code&gt;mask&lt;/code&gt; contains zeros at the frequencies we want to suppress and ones elsewhere.&lt;/p&gt;
&lt;p&gt;We then reconstruct the image using the inverse Fourier transform.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;restored = np.fft.ifft2(
    np.fft.ifftshift(filtered_spectrum)
).real
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;an-important-limitation&quot;&gt;An Important Limitation&lt;/h4&gt;
&lt;p&gt;In this project, the periodic noise is generated with known frequencies.&lt;/p&gt;
&lt;p&gt;Therefore, the notch filter already knows where the unwanted frequency components are located.&lt;/p&gt;
&lt;p&gt;This is intentional: it lets us isolate and study the effect of frequency-domain filtering.&lt;/p&gt;
&lt;p&gt;Automatically identifying unknown interference frequencies in arbitrary photographs is a separate problem and is not implemented here.&lt;/p&gt;
&lt;p&gt;A notch filter can also remove useful image information if the rejected frequencies overlap with actual image content.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Key takeaway:&lt;/strong&gt; The Fourier transform provides another perspective on image processing, allowing us to manipulate structures according to their spatial frequencies rather than their pixel positions.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;4-k-means-unsupervised-color-clustering&quot;&gt;4. K-Means: Unsupervised Color Clustering&lt;/h3&gt;
&lt;p&gt;The final technique explores unsupervised learning.&lt;/p&gt;
&lt;p&gt;Unlike classification models, K-means does not require predefined class labels.&lt;/p&gt;
&lt;p&gt;Instead, it groups observations based on similarity.&lt;/p&gt;
&lt;p&gt;For an RGB image, each pixel can be represented as a three-dimensional vector:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Pixel = [Red, Green, Blue]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;K-means attempts to divide these vectors into K clusters by minimizing the squared distances between pixels and their assigned cluster centers.&lt;/p&gt;
&lt;p&gt;The objective function is:&lt;/p&gt;
&lt;p&gt;J = Σ ||xᵢ - μ(clusterᵢ)||²&lt;/p&gt;
&lt;p&gt;where xᵢ is a pixel vector and μ is the corresponding cluster center.&lt;/p&gt;
&lt;h4 id=&quot;implementing-k-means-with-opencv&quot;&gt;Implementing K-Means with OpenCV&lt;/h4&gt;
&lt;p&gt;First, reshape the image into a collection of RGB vectors:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;pixels = image.reshape(
    -1, 3
).astype(np.float32)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then run K-means:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;criteria = (
    cv2.TERM_CRITERIA_EPS +
    cv2.TERM_CRITERIA_MAX_ITER,
    25,
    0.2
)

_, labels, centers = cv2.kmeans(
    pixels,
    4,
    None,
    criteria,
    5,
    cv2.KMEANS_PP_CENTERS
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can reconstruct an image by replacing each pixel with the color of its assigned cluster center.&lt;/p&gt;
&lt;p&gt;This produces a simplified image containing a small number of representative colors.&lt;/p&gt;
&lt;h4 id=&quot;color-clustering-is-not-semantic-segmentation&quot;&gt;Color Clustering Is Not Semantic Segmentation&lt;/h4&gt;
&lt;p&gt;This distinction is important.&lt;/p&gt;
&lt;p&gt;K-means only sees RGB values in this implementation.&lt;/p&gt;
&lt;p&gt;It does not understand objects, scenes, or semantic categories.&lt;/p&gt;
&lt;p&gt;Two unrelated objects with similar colors may belong to the same cluster.&lt;/p&gt;
&lt;p&gt;Likewise, one object with multiple colors may be split across several clusters.&lt;/p&gt;
&lt;p&gt;Still, color clustering is useful for exploring image structure, reducing color complexity, and understanding unsupervised learning.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;running-the-project-locally&quot;&gt;Running the Project Locally&lt;/h3&gt;
&lt;p&gt;The project is available on GitHub:&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Ertugrulmutlu/from-lab-to-code/tree/main/01-image-processing&quot;&gt;https://github.com/Ertugrulmutlu/from-lab-to-code/tree/main/01-image-processing&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;You will need Python 3.10 or newer.&lt;/p&gt;
&lt;p&gt;Clone the repository:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git clone https://github.com/Ertugrulmutlu/from-lab-to-code.git
cd from-lab-to-code/01-image-processing
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create a virtual environment:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;python -m venv .venv
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Activate it on Windows PowerShell:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-powershell&quot;&gt;.\.venv\Scripts\Activate.ps1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Install the dependencies:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;python -m pip install -r requirements.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Start the application:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;python -m streamlit run app.py
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Streamlit will open an interactive application in your browser.&lt;/p&gt;
&lt;p&gt;You can switch between the four techniques, adjust processing parameters, and compare the results.&lt;/p&gt;
&lt;p&gt;Each mode includes a programmatically generated example image. You can also upload an image of your own.&lt;/p&gt;
&lt;p&gt;To regenerate the demonstration gallery:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;python demo.py
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To run the included tests:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;python -m unittest discover -s tests -v
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The processing algorithms are implemented in &lt;code&gt;processing.py&lt;/code&gt;, while &lt;code&gt;synthetic.py&lt;/code&gt; generates the example images and &lt;code&gt;app.py&lt;/code&gt; contains the Streamlit interface.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-i-learned&quot;&gt;What I Learned&lt;/h3&gt;
&lt;p&gt;Building this project reinforced several important ideas.&lt;/p&gt;
&lt;p&gt;First, &lt;strong&gt;understanding the characteristics of a problem matters as much as selecting an algorithm&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Gaussian filtering and median filtering may look similar at first, but they behave differently depending on the noise distribution.&lt;/p&gt;
&lt;p&gt;Second, preprocessing can determine whether a simple computer vision algorithm succeeds or fails.&lt;/p&gt;
&lt;p&gt;In the object counting example, background illumination correction can be just as important as the thresholding operation itself.&lt;/p&gt;
&lt;p&gt;Third, the frequency domain provides a different way to reason about images.&lt;/p&gt;
&lt;p&gt;Some patterns that are difficult to isolate spatially become much easier to analyze after a Fourier transform.&lt;/p&gt;
&lt;p&gt;Finally, unsupervised clustering demonstrates that useful structure can be extracted from images without labeled training data, even though grouping colors is not the same as understanding a scene.&lt;/p&gt;
&lt;h3 id=&quot;why-i-started-from-lab-to-code&quot;&gt;Why I Started From Lab to Code&lt;/h3&gt;
&lt;p&gt;I wanted a lightweight way to turn engineering concepts into practical software projects.&lt;/p&gt;
&lt;p&gt;Not every project needs to become a startup, a large framework, or a long-term product.&lt;/p&gt;
&lt;p&gt;Sometimes, a small, well-explained implementation is enough to explore an idea and demonstrate what you have learned.&lt;/p&gt;
&lt;p&gt;That is the philosophy behind &lt;strong&gt;From Lab to Code&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Each entry will focus on an engineering topic, build a small independent implementation, and document the underlying concepts and results.&lt;/p&gt;
&lt;p&gt;The projects are independently implemented, with original demonstration materials rather than redistributed university coursework.&lt;/p&gt;
&lt;p&gt;This first project focuses on classical image processing. Future entries may explore other engineering topics as I encounter them.&lt;/p&gt;
&lt;p&gt;If you are learning computer vision or signal processing, I hope the code and explanations help you experiment with these concepts yourself.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Explore the series:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Ertugrulmutlu/from-lab-to-code&quot;&gt;https://github.com/Ertugrulmutlu/from-lab-to-code&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Explore the image processing project:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Ertugrulmutlu/from-lab-to-code/tree/main/01-image-processing&quot;&gt;https://github.com/Ertugrulmutlu/from-lab-to-code/tree/main/01-image-processing&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Thanks for reading!&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/32074/from-lab-to-code-1-exploring-classical-computer-vision-with-python-and-opencv</guid>
<pubDate>Thu, 22 Oct 2026 12:53:42 +0000</pubDate>
</item>
<item>
<title>RIVET — Environmental Incident Investigator</title>
<link>https://coderlegion.com/31944/rivet-environmental-incident-investigator</link>
<description>&lt;p&gt;&lt;strong&gt;RIVET uses computer vision and water-quality sensors to investigate suspicious changes in waterways, verify evidence, and generate traceable environmental incident reports.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Inspiration&lt;/p&gt;
&lt;p&gt;Environmental changes in waterways can go unnoticed when monitoring depends on isolated measurements or occasional inspections. We wanted to explore how computer vision and intelligent investigation could help identify suspicious changes and direct attention toward situations that need further examination.&lt;/p&gt;
&lt;p&gt;What it does&lt;/p&gt;
&lt;p&gt;RIVET (Environmental Incident Investigator) is a proposed system that combines OpenCV-based video analysis with water-quality sensor readings. It aims to detect unusual changes, compare visual evidence with sensor measurements, investigate uncertainty through additional analysis, and generate traceable incident reports.&lt;/p&gt;
&lt;p&gt;How we built it&lt;/p&gt;
&lt;p&gt;The planned prototype uses Python and OpenCV to analyse video over time. Water-quality sensors will provide independent measurements, while an AI agent will select follow-up analyses when observations are incomplete or conflicting. AWS will support the data-processing and storage workflow.&lt;/p&gt;
&lt;p&gt;Challenges we expect&lt;/p&gt;
&lt;p&gt;Important challenges include distinguishing genuine environmental changes from lighting variations, synchronising video with sensor readings, collecting reliable test data, and reducing false alarms.&lt;/p&gt;
&lt;p&gt;Accomplishments we're aiming for&lt;/p&gt;
&lt;p&gt;Our goal is to build a working prototype that demonstrates evidence-driven environmental investigation, combines computer vision with sensor measurements, and evaluates its performance against simpler monitoring methods.&lt;/p&gt;
&lt;p&gt;What we aim to learn&lt;/p&gt;
&lt;p&gt;We aim to investigate whether adaptive visual analysis and sensor fusion can improve environmental anomaly detection, how uncertainty should influence an AI agent's next action, and how to make automated environmental monitoring more transparent and reliable.&lt;br&gt;
 &lt;a rel=&quot;nofollow&quot; href=&quot;rivet-environmental-investigator.streamlit.app&quot;&gt;&lt;/a&gt;&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/nanduanish2008-star/RIVET&quot;&gt;&lt;/a&gt;&lt;br&gt;
&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/x99z0n2dfivxamtdeqm8.png&quot; alt=&quot;Image description&quot;&gt;&lt;br&gt;
&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/krxdmcwq7u1186o0zxdf.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/31944/rivet-environmental-incident-investigator</guid>
<pubDate>Wed, 21 Oct 2026 09:04:48 +0000</pubDate>
</item>
<item>
<title>How to Build a Deep learning model to detect Ships</title>
<link>https://coderlegion.com/31753/how-to-build-a-deep-learning-model-to-detect-ships</link>
<description>&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=2248772476490076519&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;For anyone studying practical computer vision and deep learning deployment:&lt;br&gt;
A frequent challenge in applied vision projects is balancing spatial accuracy against computational overhead during training. Selecting an architecture variant like YOLOv8l provides the parameter capacity necessary to resolve multi-scale objects in clustered environments, but success depends entirely on proper configuration and validation monitoring. This tutorial breaks down the mechanics of training a custom detector from scratch, detailing how loss convergence curves and validation checkpoints guide the selection of deployable weights.&lt;/p&gt;
&lt;p&gt;The progression moves methodically from environment initialization to final model inference. It covers dataset re-indexing, parsing raw coordinate tags into standardized label files, executing supervised training runs while monitoring loss metrics, and implementing an OpenCV inference script to handle bounding box coordinates, class labels, and confidence thresholds on unseen imagery.&lt;/p&gt;
&lt;p&gt;The Resources:&lt;/p&gt;
&lt;p&gt;Reading on Medium: &lt;a rel=&quot;nofollow&quot; href=&quot;https://medium.com/object-detection-tutorials/yolov8-custom-object-detection-full-code-walkthrough-23fa476f834e&quot;&gt;https://medium.com/object-detection-tutorials/yolov8-custom-object-detection-full-code-walkthrough-23fa476f834e&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Detailed written explanation and source code: &lt;a rel=&quot;nofollow&quot; href=&quot;https://eranfeit.net/yolov8-custom-object-detection-full-code-walkthrough/&quot;&gt;https://eranfeit.net/yolov8-custom-object-detection-full-code-walkthrough/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Join my Newsletter : &lt;a rel=&quot;nofollow&quot; href=&quot;https://eranfeit.net/advance-your-skills-in-computer-vision-ai/&quot;&gt;https://eranfeit.net/advance-your-skills-in-computer-vision-ai/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Deep-dive video walkthrough: &lt;a rel=&quot;nofollow&quot; href=&quot;https://youtu.be/zAyPboTjcAA&quot;&gt;https://youtu.be/zAyPboTjcAA&lt;/a&gt;&lt;br&gt;
This material is published solely for educational purposes. Feel free to leave constructive feedback or questions about threshold tuning and batch performance below.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/31753/how-to-build-a-deep-learning-model-to-detect-ships</guid>
<pubDate>Mon, 19 Oct 2026 16:30:01 +0000</pubDate>
</item>
<item>
<title>The adaptive threshold that deleted the thing we were looking for</title>
<link>https://coderlegion.com/28864/the-adaptive-threshold-that-deleted-the-thing-we-were-looking-for</link>
<description>&lt;p&gt;Finding the four corners of a page in a phone photo is the step that turns a picture of a document into a copy of one. Everything downstream — the perspective warp, the flattening, the PDF — is easy once you know where the page is.&lt;/p&gt;
&lt;p&gt;We do that client-side, in a browser tab, on a grayscale copy of the photo scaled to a long edge of 640 px. The pipeline is the textbook one:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;grayscale → blur → Sobel → adaptive edge map → Hough lines → best convex quad
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two near-horizontal lines, two near-vertical lines, score every candidate quadrilateral, keep the best. Disclosure up front: this is from &lt;a rel=&quot;nofollow&quot; href=&quot;https://lensup.ai/&quot;&gt;LensUp&lt;/a&gt;, which is ours. What follows is the part where the textbook pipeline quietly failed, and the one-line change that nearly doubled how often it works.&lt;/p&gt;
&lt;h3 id=&quot;the-failure-the-page-fills-the-frame-and-detection-gets-worse&quot;&gt;The failure: the page fills the frame and detection gets &lt;em&gt;worse&lt;/em&gt;&lt;/h3&gt;
&lt;p&gt;The adaptive edge threshold is the standard trick. Take the histogram of Sobel magnitudes, pick the 90th percentile, call everything above it an edge. It adapts to lighting, exposure and camera noise for free.&lt;/p&gt;
&lt;p&gt;It also has a property nobody mentions: &lt;strong&gt;it is set by whatever has the strongest gradients in the image.&lt;/strong&gt; On a photo of a document, that is not the page border. It is the letter strokes. Printed text is black-on-white at high spatial frequency — it produces the sharpest gradients in the frame by a wide margin.&lt;/p&gt;
&lt;p&gt;The paper-to-desk step, meanwhile, is weak. Measured on SmartDoc 2015, a page lying on light wood clears the surrounding surface by roughly &lt;strong&gt;14 luma levels&lt;/strong&gt;. That is the signal you actually need, and it is an order of magnitude quieter than the text sitting in the middle of it.&lt;/p&gt;
&lt;p&gt;So the adaptive threshold, doing exactly what it was designed to do, sets itself by the text and prices the page border out of the edge map. And it does this &lt;em&gt;worst&lt;/em&gt; in the case you most want to work: when the user fills the frame with the page, so text dominates the histogram and there is barely any desk left to argue for a lower threshold.&lt;/p&gt;
&lt;p&gt;The fix is not clever. Cap it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;edgePercentile: 0.9,     // gradient magnitudes above this percentile are edges …
minEdgeMagnitude: 14,    // … but never below this …
maxEdgeThreshold: 30,    // … and never above this: print must not price the page edge out
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Measured on 300 real frames, with every other guard unchanged:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;threshold cap &lt;/th&gt;&lt;th&gt;pages located&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;220 &lt;/td&gt;&lt;td&gt;23.3 %&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;30&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;&lt;strong&gt;41.0 %&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;An adaptive threshold with a hard ceiling is no longer fully adaptive, which felt wrong when we wrote it. It is also the single highest-value line in the file.&lt;/p&gt;
&lt;h3 id=&quot;telling-paper-from-a-block-of-printed-text&quot;&gt;Telling paper from a block of printed text&lt;/h3&gt;
&lt;p&gt;Capping the threshold gets the page border back into the edge map. It also lets a lot of rectangles in that are not pages — and the most dangerous one is a dark block of printed text, because it is genuinely a bright-surrounded rectangle with strong edges.&lt;/p&gt;
&lt;p&gt;The first instinct is a contrast magnitude: require the inside of the quad to be meaningfully brighter than the outside. We had that set at 14 gray levels, which is the median paper-on-light-wood step from above.&lt;/p&gt;
&lt;p&gt;That was the wrong knob. A threshold at the &lt;em&gt;median&lt;/em&gt; of true pages rejects about half of the true pages. What actually discriminates is not how big the step is but which way it points:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;minContrast: 8,   // 40th percentile of (inside − outside) across each side, in gray levels.
                  // A page on light wood measures 14 at the median, so 14 rejected half of them;
                  // the polarity rule is what keeps text blocks out, not this magnitude.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The polarity rule is the actual test: a quad is a page only if it is &lt;strong&gt;brighter inside than outside, sampled at two depths, on all four sides&lt;/strong&gt;. A text block fails that on the sides where more text continues past it. A page on a desk passes it everywhere.&lt;/p&gt;
&lt;p&gt;There is still a case that beats local evidence: a large dark region of print can look like &quot;the desk&quot; if you only sample a few pixels out. So before applying a detection automatically — as opposed to just pre-positioning the handles — we probe farther outside, looking for the page continuing past that edge. If the page continues, the edge was not the page edge.&lt;/p&gt;
&lt;h3 id=&quot;the-guards-that-are-product-decisions-wearing-algorithm-clothes&quot;&gt;The guards that are product decisions wearing algorithm clothes&lt;/h3&gt;
&lt;p&gt;Three of the constants in that file are not computer vision. They are decisions about what to do when we are unsure, and they are the ones I would port to any similar project:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;borderMargin: 0.02,        // a side within 2% of the frame edge is &quot;on the border&quot;
applyMinAreaRatio: 0.25,   // a smaller page is only proposed, never applied
minSideSupport: 0.42,      // every side needs this much edge support to be applied
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;A quad with a side lying on the image border is rejected.&lt;/strong&gt; If the &quot;page edge&quot; is the edge of the photo, the page is not fully inside the frame, and the honest default is the whole frame rather than a crop that silently cuts off whatever was outside it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The largest well-supported quad wins.&lt;/strong&gt; This sounds like a tie-breaker and is actually a correctness rule: it makes an inner rectangle — a photo printed on the page, a bordered table — lose to the page around it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Below a quarter of the frame, a detection is only a proposal.&lt;/strong&gt; It pre-positions the four draggable handles and waits. Auto-applying a small quad is how you produce the single worst outcome in this whole category: a confident crop that removes two thirds of somebody's passport.&lt;/p&gt;
&lt;p&gt;That last one generalises. The result of this module is a proposal and never the truth; every handle stays draggable, and the caller decides from a confidence value whether to apply it silently. When detection fails outright it returns &lt;code&gt;null&lt;/code&gt;, and &lt;code&gt;null&lt;/code&gt; means &lt;strong&gt;keep the entire original image&lt;/strong&gt;. A scanner that crops wrong is worse than a scanner that does not crop.&lt;/p&gt;
&lt;h3 id=&quot;two-features-that-ship-turned-off&quot;&gt;Two features that ship turned off&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;weakEdgeRecovery: 0,   // Experimental: desktop cost is not yet justified by coverage gains.
contourRecovery: 0,    // Correct proposals improved, but the measured latency gate failed.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Both of these work. Both improve the numbers. Both are disabled in the shipped defaults because they did not clear a latency budget on the hardware people actually hold. Leaving them in the file at &lt;code&gt;0&lt;/code&gt;, with the reason written next to them, has been more useful than deleting them — the next person to look at this does not have to rediscover that the idea was tried and why it is not on.&lt;/p&gt;
&lt;h3 id=&quot;what-i-would-take-away-from-this&quot;&gt;What I would take away from this&lt;/h3&gt;
&lt;p&gt;The general shape of the bug is worth more than the specific fix: &lt;strong&gt;an adaptive parameter adapts to the strongest thing in your input, and the strongest thing is often not your signal.&lt;/strong&gt; Text beats page borders. Specular highlights beat document edges. The loudest object in the frame sets your threshold, and if your target is quiet, it gets deleted.&lt;/p&gt;
&lt;p&gt;When an adaptive method underperforms, it is worth checking whether it is adapting to something you did not intend before reaching for a better algorithm. In our case the better algorithm was a ceiling of 30.&lt;/p&gt;
&lt;p&gt;If you want to watch this run on your own photo, it is at &lt;a rel=&quot;nofollow&quot; href=&quot;https://lensup.ai/cam-scanner/&quot;&gt;a browser-based document scanner&lt;/a&gt; — the detection happens in the tab and your files are never uploaded, so the Network panel is a fair way to check that claim.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/28864/the-adaptive-threshold-that-deleted-the-thing-we-were-looking-for</guid>
<pubDate>Mon, 28 Sep 2026 05:03:07 +0000</pubDate>
</item>
<item>
<title>I Built a Real-Time Perception Stack on CARLA. Here Is What Actually Happened.</title>
<link>https://coderlegion.com/25804/i-built-a-real-time-perception-stack-on-carla-here-is-what-actually-happened</link>
<description>&lt;p&gt;I want to tell you about the three weeks I spent making YOLO and ROS 2 talk to each other inside a simulator, and why it was nothing like the tutorials made it look.&lt;/p&gt;
&lt;p&gt;This is not a clean architecture post. This is a &quot;here is what broke, here is what I changed, here is what I actually learned&quot; post.&lt;/p&gt;
&lt;p&gt;If you are building robotics perception systems, or you are just curious what it looks like to wire YOLOv11, ByteTrack, StrongSORT, CARLA, and ROS 2 into something that runs in real time, this one is for you.&lt;/p&gt;
&lt;h3 id=&quot;what-i-was-building-and-why&quot;&gt;What I was building and why&lt;/h3&gt;
&lt;p&gt;The goal was a real-time robotic perception system that could:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;detect objects in a moving vehicle simulation&lt;/li&gt;
&lt;li&gt;track those objects across frames with stable identities&lt;/li&gt;
&lt;li&gt;feed structured perception data into a ROS 2 navigation stack&lt;/li&gt;
&lt;li&gt;run fast enough to be genuinely useful for autonomous decision making&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;CARLA was the simulator. YOLOv11 was the detector. ByteTrack and StrongSORT handled multi-object tracking. ROS 2 was the middleware. ONNX and TensorRT handled edge optimisation.&lt;/p&gt;
&lt;p&gt;On paper this is a well-defined problem. In practice, every one of those components has opinions about data formats, timing, and threading that you only discover by running them together.&lt;/p&gt;
&lt;h3 id=&quot;problem-one-getting-camera-data-out-of-carla-cleanly&quot;&gt;Problem one: getting camera data out of CARLA cleanly&lt;/h3&gt;
&lt;p&gt;CARLA has its own Python API and its own event-driven callback system. ROS 2 has its own message types, publisher model, and executor. Getting them to cooperate without dropping frames or blocking the main thread is not automatic.&lt;/p&gt;
&lt;p&gt;Here is a simplified version of how I handled the camera subscription and published frames into ROS 2:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import carla
import rclpy
from rclpy.node import Node
from sensor_msgs.msg import Image
from cv_bridge import CvBridge
import numpy as np

class CARLACameraNode(Node):
    def __init__(self):
        super().__init__('carla_camera_node')
        self.publisher = self.create_publisher(Image, '/carla/camera/rgb', 10)
        self.bridge = CvBridge()

    def camera_callback(self, image):
        array = np.frombuffer(image.raw_data, dtype=np.uint8)
        array = array.reshape((image.height, image.width, 4))
        bgr = array[:, :, :3]
        msg = self.bridge.cv2_to_imgmsg(bgr, encoding='bgr8')
        msg.header.stamp = self.get_clock().now().to_msg()
        self.publisher.publish(msg)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The key thing I got wrong initially: I was not stamping the header with the ROS 2 clock.&lt;/p&gt;
&lt;p&gt;That one missing line caused timestamp mismatches downstream that made the tracker behave erratically. ByteTrack and StrongSORT both calculate displacement and velocity across frames using time deltas. If those deltas are wrong or inconsistent, the association algorithm produces noisy, unstable tracks that look like a model problem but are actually a data problem.&lt;/p&gt;
&lt;p&gt;That bug cost me two days.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Add the timestamp. Always. It is not optional in robotics.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;problem-two-yolo-runs-fast-ros-2-callbacks-do-not-wait&quot;&gt;Problem two: YOLO runs fast, ROS 2 callbacks do not wait&lt;/h3&gt;
&lt;p&gt;YOLOv11 at a reasonable resolution on a GPU is quick. But &quot;quick&quot; in model benchmarking terms and &quot;quick&quot; in robotics middleware terms are genuinely different things.&lt;/p&gt;
&lt;p&gt;ROS 2 uses a callback-based execution model. If your inference call is blocking the subscription callback thread, you are missing messages while you compute. The subscriber queue fills. You start processing frames that are already stale by the time inference finishes. Your tracker is now seeing the world as it was, not as it is.&lt;/p&gt;
&lt;p&gt;The fix was decoupling ingestion from inference using a bounded queue and a dedicated inference thread:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import threading
import queue
from rclpy.node import Node
from sensor_msgs.msg import Image

class PerceptionNode(Node):
    def __init__(self):
        super().__init__('perception_node')
        self.frame_queue = queue.Queue(maxsize=5)
        self.subscription = self.create_subscription(
            Image,
            '/carla/camera/rgb',
            self.image_callback,
            10
        )
        self.inference_thread = threading.Thread(
            target=self.run_inference,
            daemon=True
        )
        self.inference_thread.start()

    def image_callback(self, msg):
        if not self.frame_queue.full():
            self.frame_queue.put(msg)

    def run_inference(self):
        while rclpy.ok():
            msg = self.frame_queue.get()
            # run YOLOv11 inference here
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;maxsize=5&lt;/code&gt; on the queue is deliberate. When inference cannot keep up, I drop old frames rather than accumulate a growing backlog of stale images.&lt;/p&gt;
&lt;p&gt;This is a real-time systems principle worth internalising: &lt;strong&gt;a stale frame processed late is usually worse than a dropped frame.&lt;/strong&gt; Timeliness matters as much as completeness in a live perception pipeline.&lt;/p&gt;
&lt;h3 id=&quot;problem-three-tracking-across-frames-is-not-free&quot;&gt;Problem three: tracking across frames is not free&lt;/h3&gt;
&lt;p&gt;ByteTrack and StrongSORT both work by associating new detections to existing tracks across frames using motion models and, in StrongSORT's case, appearance embeddings. This sounds clean until you hit occlusion, rapidly moving objects, or a detector that briefly misses something due to a lighting change or partial obstruction.&lt;/p&gt;
&lt;p&gt;What I observed specifically in CARLA: when the ego vehicle turned sharply, vehicles and pedestrians would leave the camera frustum and re-enter within a few frames. The tracker sometimes assigned fresh IDs to those returning objects, breaking the continuity that downstream navigation reasoning depended on.&lt;/p&gt;
&lt;p&gt;The parameters that mattered most here were:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;tracker_config = {
    &quot;max_age&quot;: 30,        # frames a track survives without a detection match
    &quot;min_hits&quot;: 3,        # detections needed before a track is confirmed
    &quot;iou_threshold&quot;: 0.3  # minimum overlap for detection-to-track association
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Increase &lt;code&gt;max_age&lt;/code&gt; too much and you get ghost tracks for objects that have genuinely left the scene. Decrease it too much and you lose real tracks during brief occlusions. Tune &lt;code&gt;iou_threshold&lt;/code&gt; too high and the tracker drops tracks during fast motion. Too low and it starts merging distinct objects.&lt;/p&gt;
&lt;p&gt;There is no universally correct set of values. They depend on your scene density, your detector confidence, your frame rate, and what your downstream system needs from the tracker. The only way to find the right values is to run scenarios, observe failures, and adjust deliberately.&lt;/p&gt;
&lt;h3 id=&quot;problem-four-edge-hardware-changes-the-problem-completely&quot;&gt;Problem four: edge hardware changes the problem completely&lt;/h3&gt;
&lt;p&gt;Everything above ran well on my development GPU. Then I exported the model and tested closer to constrained hardware conditions.&lt;/p&gt;
&lt;p&gt;The frame rate dropped. The tracker fell behind. The pipeline that felt smooth in development started introducing the kind of latency that makes real-time perception meaningless.&lt;/p&gt;
&lt;p&gt;The fix was ONNX export followed by TensorRT optimisation:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import torch

model = load_yolov11(&quot;yolov11n.pt&quot;)
model.eval()

dummy_input = torch.randn(1, 3, 640, 640).cuda()

torch.onnx.export(
    model,
    dummy_input,
    &quot;perception.onnx&quot;,
    opset_version=17,
    input_names=[&quot;images&quot;],
    output_names=[&quot;output0&quot;],
    dynamic_axes={&quot;images&quot;: {0: &quot;batch&quot;}}
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After TensorRT conversion and INT8 quantisation, inference time dropped enough to bring the full pipeline back into a usable range. But the deeper lesson is not about TensorRT specifically. It is that &lt;strong&gt;edge deployment is a different problem to development, and discovering that at the end of the project means redesigning under pressure.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Test on target hardware early. Not day one necessarily, but not month three either.&lt;/p&gt;
&lt;h3 id=&quot;problem-five-your-confidence-threshold-is-a-system-design-decision&quot;&gt;Problem five: your confidence threshold is a system design decision&lt;/h3&gt;
&lt;p&gt;This one is subtle and I did not fully appreciate it until I had the full pipeline running.&lt;/p&gt;
&lt;p&gt;YOLO outputs detections with confidence scores. It is tempting to pick a threshold like 0.5 and move on. But in a live robotics system, that threshold is not a model hyperparameter. It is an architectural decision about when your robot is allowed to act on what it sees.&lt;/p&gt;
&lt;p&gt;A threshold that is too low sends noisy, unreliable detections to the tracker. The tracker generates unstable tracks. The navigation stack gets bad inputs. The robot behaves erratically.&lt;/p&gt;
&lt;p&gt;A threshold that is too high causes the detector to miss real objects in challenging conditions. Tracks are lost. The robot navigates as if obstacles do not exist.&lt;/p&gt;
&lt;p&gt;What helped was treating the threshold not as a single number but as part of a validation layer:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def is_valid_detection(detection, track_age, platform_stable):
    if detection.confidence &amp;lt; CONFIDENCE_THRESHOLD:
        return False
    if track_age &amp;lt; MIN_TRACK_AGE:
        return False
    if not platform_stable:
        return False
    return True
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Multiple signals need to agree before a detection influences navigation. This pattern, requiring consensus across confidence, temporal stability, and platform state, is something I now consider non-negotiable in any perception pipeline with real-world consequences.&lt;/p&gt;
&lt;h3 id=&quot;five-things-i-know-now-that-i-did-not-know-at-the-start&quot;&gt;Five things I know now that I did not know at the start&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Timestamps are not optional.&lt;/strong&gt; Misaligned timestamps produce bugs that look like model bugs and take days to diagnose.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Decouple your threads early.&lt;/strong&gt; Blocking the callback thread is an easy mistake with expensive downstream consequences.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The tracker is part of your perception system.&lt;/strong&gt; Not an add-on you configure at the end. Design around it from day one.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Test on target hardware before you think you are done.&lt;/strong&gt; Edge deployment is a different engineering problem to development.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Log everything during development.&lt;/strong&gt; You cannot replay failures you did not record, and in robotics you will need to replay failures.&lt;/p&gt;
&lt;h3 id=&quot;closing-thought&quot;&gt;Closing thought&lt;/h3&gt;
&lt;p&gt;Building this system taught me that robotics perception is not a model problem. It is a systems problem where the model is one moving part among many.&lt;/p&gt;
&lt;p&gt;The interesting engineering is in how you connect those parts, how you handle failures at the boundaries between them, and how you make the full pipeline behave reliably when nothing goes quite according to plan.&lt;/p&gt;
&lt;p&gt;Which is always.&lt;/p&gt;
&lt;p&gt;If you are building something similar or want to dig into any part of this, drop a comment. I am always happy to talk robotics and perception systems.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/25804/i-built-a-real-time-perception-stack-on-carla-here-is-what-actually-happened</guid>
<pubDate>Sun, 30 Aug 2026 08:57:09 +0000</pubDate>
</item>
<item>
<title>Can AI Remember What It Sees?</title>
<link>https://coderlegion.com/25319/can-ai-remember-what-it-sees</link>
<description>&lt;p&gt;So, I have been asking myself this question for quite a while, Can AI actually remember what it sees? and tell a user later that &quot;I saw X at place Y last&quot;, or for instance when a person goes to a cafe and forgets something. e.g let's say they actually forgot their charger. The system should be able to say, &quot;Hey, I think you came here with a charger and now you are leaving without one, or you forgot your charger&quot;.&lt;/p&gt;
&lt;p&gt;I think that will be really helpful, and it can also help a person when it observes that they skipped a step, then the system proceed to say, &quot;I think you skipped step 5 in this process&quot;. &lt;/p&gt;
&lt;p&gt;So the vision is: can AI maintain enough continuity about the physical world to notice when something important has been forgotten, misplaced, changed, or left unfinished?&lt;/p&gt;
&lt;p&gt;I am exploring these four questions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Perception: what exactly is in the scene? &lt;/li&gt;
&lt;li&gt;Identity: is this the same physical object as before? &lt;/li&gt;
&lt;li&gt;Memory: what happened to it over time?&lt;/li&gt;
&lt;li&gt;Reasoning: does the current situation look wrong or incomplete?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is Day 1 for me to actually take a step towards creating this, btw I did my research and found some amazing projects around this, so I am actually going to be looking at them (I already started). &lt;/p&gt;
&lt;p&gt;What I did today, I just created a simple Python program that calls a simple YOLO model, and then lets it detect objects, I focused on detecting bottles, cause I wanted to just see if the system can remember the last time it actually saw the object and it did, it actually remembered the last time it saw the object, like the exact time.  &lt;/p&gt;
&lt;p&gt;My bottle demo has basically answered the perception + primitive memory question.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/wzqds3x46mzu613vaj9k.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/isbggdghkypsri5ml1eb.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;It shows that the system can actually see what is in a scene and then remember it and when last it saw it. So now I will be working on identity and reasoning, and testing it, I will also be documenting it all.&lt;/p&gt;
&lt;p&gt;Thank you, I hope you follow the build process.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/25319/can-ai-remember-what-it-sees</guid>
<pubDate>Mon, 24 Aug 2026 23:26:09 +0000</pubDate>
</item>
<item>
<title>How to build yolov8 dental object detection model</title>
<link>https://coderlegion.com/25067/how-to-build-yolov8-dental-object-detection-model</link>
<description>&lt;p&gt;For anyone studying computer vision and medical image analysis, applying deep learning to dental radiography presents distinct challenges in feature extraction and anatomical localization.&lt;br&gt;
Dental radiographs often feature dense, overlapping structures, low contrast margins, and significant variation across imaging hardware. Manual landmark identification remains labor-intensive and susceptible to inter-observer variability. This tutorial utilizes YOLOv8 as a single-stage detector because it delivers high mean Average Precision (mAP) on closely packed, low-contrast bounding boxes while maintaining real-time inference speeds suitable for practical clinical pipelines and batch processing.&lt;/p&gt;
&lt;p&gt;Detailed written explanation and source code: &lt;a rel=&quot;nofollow&quot; href=&quot;https://eranfeit.net/how-to-build-yolov8-dental-object-detection-model/&quot;&gt;https://eranfeit.net/how-to-build-yolov8-dental-object-detection-model/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The end-to-end workflow begins by establishing an isolated PyTorch environment with CUDA acceleration. The dataset is structured into normalized YOLO-format annotations paired with a custom dataset configuration file. Before initiating model training, normalized bounding box coordinates are converted to pixel dimensions and rendered using OpenCV to verify annotation alignment against raw X-ray data. Once validated, the YOLOv8 architecture is trained on GPU, and the resulting weights are evaluated by comparing predicted bounding boxes against ground-truth annotations in a side-by-side visual analysis.&lt;/p&gt;
&lt;p&gt;Reading on Medium: &lt;a rel=&quot;nofollow&quot; href=&quot;https://medium.com/object-detection-tutorials/how-to-build-yolov8-dental-object-detection-model-07ee6ee36296&quot;&gt;https://medium.com/object-detection-tutorials/how-to-build-yolov8-dental-object-detection-model-07ee6ee36296&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Join my Newsletter : &lt;a rel=&quot;nofollow&quot; href=&quot;https://eranfeit.net/advance-your-skills-in-computer-vision-ai/&quot;&gt;https://eranfeit.net/advance-your-skills-in-computer-vision-ai/&lt;/a&gt;&lt;br&gt;
Deep-dive video walkthrough: &lt;a rel=&quot;nofollow&quot; href=&quot;https://youtu.be/mfv1ps-tHDk&quot;&gt;https://youtu.be/mfv1ps-tHDk&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This content is intended for educational purposes only. Constructive feedback, technical questions, and discussions regarding custom dataset training or model optimization are welcome in the comments.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/25067/how-to-build-yolov8-dental-object-detection-model</guid>
<pubDate>Fri, 21 Aug 2026 08:03:06 +0000</pubDate>
</item>
<item>
<title>EigenCAM YOLOv5 Explained: Understanding What YOLOv5 Sees</title>
<link>https://coderlegion.com/24685/eigencam-yolov5-explained-understanding-what-yolov5-sees</link>
<description>&lt;p&gt;For anyone studying Image Heat Maps...&lt;br&gt;
Understanding model interpretability in deep learning object detection presents distinct challenges compared to standard image classification. Traditional attribution methods like Grad-CAM rely heavily on backpropagated class gradients, which can yield noisy or incomplete visualizations when applied to object detection architectures like YOLOv5 due to multi-scale feature maps and simultaneous localization-classification outputs. EigenCAM resolves this limitation by applying Principal Component Analysis (PCA) directly to the learned feature activations of the convolutional layers. By computing the primary eigenvector of the feature representations without requiring gradient calculations or specific target class selection, EigenCAM produces clean, class-agnostic heatmaps that clearly delineate spatial attention regions.&lt;br&gt;
The implementation workflow begins by loading a pretrained YOLOv5 model using PyTorch and identifying key target layers—such as specific feature extraction bottlenecks—within the backbone or neck architecture. After preprocessing the input image to match the network requirements, forward inference is executed to capture feature maps at the target layer. EigenCAM then computes the first principal component across these activation channels, normalizes the resulting matrix, and overlays the heatmap onto the original image using OpenCV. This systematic process provides a deterministic view into which visual features most strongly influence the detector's focus.&lt;br&gt;
Reading on Medium: &lt;a rel=&quot;nofollow&quot; href=&quot;https://medium.com/object-detection-tutorials/how-to-use-eigencam-for-yolov5-object-detection-07e4a386e567&quot;&gt;https://medium.com/object-detection-tutorials/how-to-use-eigencam-for-yolov5-object-detection-07e4a386e567&lt;/a&gt;&lt;br&gt;
Detailed written explanation and source code: &lt;a rel=&quot;nofollow&quot; href=&quot;https://eranfeit.net/how-to-use-eigencam-for-yolov5-object-detection/&quot;&gt;https://eranfeit.net/how-to-use-eigencam-for-yolov5-object-detection/&lt;/a&gt;&lt;br&gt;
Join my Newsletter : &lt;a rel=&quot;nofollow&quot; href=&quot;https://eranfeit.net/advance-your-skills-in-computer-vision-ai/&quot;&gt;https://eranfeit.net/advance-your-skills-in-computer-vision-ai/&lt;/a&gt;&lt;br&gt;
Deep-dive video walkthrough: &lt;a rel=&quot;nofollow&quot; href=&quot;https://youtu.be/pcgvcIJuKnI&quot;&gt;https://youtu.be/pcgvcIJuKnI&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This content is prepared strictly for educational purposes. Technical inquiries, implementation questions, and constructive feedback regarding visual explainability techniques are welcome in the comments.&lt;br&gt;
Enjoy , Eran&lt;/p&gt;
&lt;h2 id=&quot;yolov5-heatmaps-objectdetection&quot;&gt;YOLOv5 #HeatMaps #ObjectDetection&lt;/h2&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/24685/eigencam-yolov5-explained-understanding-what-yolov5-sees</guid>
<pubDate>Sat, 15 Aug 2026 18:12:04 +0000</pubDate>
</item>
<item>
<title>The &quot;Physical NFT&quot; is Dead. Long Live the Verifiable Digital Twin</title>
<link>https://coderlegion.com/15679/the-physical-nft-is-dead-long-live-the-verifiable-digital-twin</link>
<description>&lt;p&gt;We’ve spent years putting &quot;Monkey JPEGs&quot; on the blockchain. But as a system architect, I’m less interested in speculative art and more interested in the mathematical reality of physical objects. Can we bridge the gap between a raw river rock and a Hedera hashgraph?&lt;/p&gt;
&lt;h3 id=&quot;the-reality-photogrammetry-museum-grade-ingestion-&quot;&gt;The Reality: Photogrammetry (Museum-Grade Ingestion)&lt;/h3&gt;
&lt;p&gt;Before we talk about ledgers, we have to talk about the data. Photogrammetry is the process of extracting 3D information from 2D photographs. By taking hundreds of overlapping photos, we can reconstruct a dense point cloud and eventually a high-resolution 3D mesh.&lt;/p&gt;
&lt;p&gt;This isn't just a &quot;3D model&quot;; it’s a forensic record. For example, check out this &lt;a rel=&quot;nofollow&quot; href=&quot;https://sketchfab.com/3d-models/granite-head-of-amenemhat-iii-64d0b7662b59417986e9d693624de97a&quot;&gt;Granite Head of Amenemhat III&lt;/a&gt; on Sketchfab. That level of detail—the micro-textures of the stone—is what we are aiming to &quot;anchor.&quot;&lt;/p&gt;
&lt;h3 id=&quot;the-ledger-why-hedera-and-the-dag-&quot;&gt;The Ledger: Why Hedera and the DAG?&lt;/h3&gt;
&lt;p&gt;For a project involving thousands of physical items (like my current Backyard Quarry experiment), a traditional, slow, expensive blockchain is a non-starter. I’ve been looking into &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.hedera.com&quot;&gt;Hedera&lt;/a&gt; specifically because of its Directed Acyclic Graph (DAG) structure.&lt;/p&gt;
&lt;p&gt;Unlike a linear chain, a DAG allows transactions to happen in parallel via a &quot;Gossip Protocol.&quot; This means finality in seconds for a fraction of a penny ($0.0001). If I want to anchor the provenance of a single rock, it has to be financially viable.&lt;/p&gt;
&lt;h3 id=&quot;the-concept-the-decentralized-scribe&quot;&gt;The Concept: The Decentralized Scribe&lt;/h3&gt;
&lt;p&gt;In my work, I define a &lt;strong&gt;Decentralized Scribe&lt;/strong&gt; as a high-integrity agent—an automated system that sits between the physical world and the ledger. It:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Observes:&lt;/strong&gt; Ingests the photogrammetry data.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hashes:&lt;/strong&gt; Creates a geometric &quot;fingerprint&quot; of the 3D mesh.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Signs:&lt;/strong&gt; Anchors that fingerprint to the Hedera Consensus Service (HCS).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The Scribe is the bridge of trust. It proves that the 3D model on your screen matches the physical object in my hand at a specific point in history.&lt;/p&gt;
&lt;h3 id=&quot;connecting-the-dots-the-ask-&quot;&gt;Connecting the Dots: The &quot;Ask&quot;&lt;/h3&gt;
&lt;p&gt;I’m currently exploring this intersection, conceptually in my &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.kenwalger.com/blog/software-engineering/the-backyard-quarry-turning-rocks-into-data/&quot;&gt;Rock Quarry&lt;/a&gt; series, and I’d love to hear from this community:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Geometric Hashing:&lt;/strong&gt; Is a hash of a 3D mesh stable enough? If I re-scan the object next month, will the &quot;fingerprint&quot; stay the same?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Oracle Problem:&lt;/strong&gt; How do we decentralize the &quot;Trust&quot; that the scan actually matches the item?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Utility:&lt;/strong&gt; Beyond collectibles, where does &quot;Verifiable Provenance&quot; actually solve a pain point for you?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Is this the bridge we’ve been waiting for, or is the &quot;Oracle Problem&quot; too high of a wall to climb?&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/15679/the-physical-nft-is-dead-long-live-the-verifiable-digital-twin</guid>
<pubDate>Thu, 30 Apr 2026 16:06:00 +0000</pubDate>
</item>
</channel>
</rss>