sevenlayercookie / OnScreenRuler

On Screen Calipers for EKGS
0 stars 0 forks source link

Automatic calibration #18

Open sevenlayercookie opened 1 year ago

sevenlayercookie commented 1 year ago

Likely can use OpenCV (Computer Vision)

Probably only feasible if an EKG with gridlines. Should be able to reasonably detect these straight lines.

Maybe should involve:

Things to look into: Hough Line Transform (OpenCV)

If no grid lines (such as telemetry):

sevenlayercookie commented 1 month ago

some working python code. this works great to measure the smallest gridlines. can even straighten images (adds just a bit of processing time). Just need to translate this to C#.

works by counting all pixels in each column of pixels, then uses Fourier to find repeating frequencies

NOTE: this still doesn't account for telemetry without gridlines main.py


import cv2
import numpy as np
import matplotlib.pyplot as plt
from scipy.fft import fft, fftfreq
import time

from image_straightener import straighten_image

start_time = time.time()

input = 'testImages/test.png'

# Load the image
image = cv2.imread(input)

#image = straighten_image(input)
#cv2.imwrite('straightened.png', straightened)

# Convert to grayscale
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

# Save the grayscale image
#cv2.imwrite('grayscale.png', gray)

# Apply Otsu's thresholding
#_, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)

# Apply fixed thresholding with a lower threshold value
threshold_value = 254  # You can adjust this value as needed
_, binary = cv2.threshold(gray, threshold_value, 255, cv2.THRESH_BINARY)

# Save the binary image
#cv2.imwrite('binary.png', binary)

# Convert binary image to NumPy array (if not already)
binary = np.array(binary)

# Count black pixels in each column
columnPixels = np.sum(binary == 0, axis=0)

# Count black pixels in each row
rowPixels = np.sum(binary == 0, axis=1)

# Plotting the column pixel counts
#plt.figure(figsize=(12, 6))

#plt.subplot(1, 2, 1)
#plt.plot(columnPixels, color='black')
#plt.title('Black Pixel Count per Column')
#plt.xlabel('Column Index')
#plt.ylabel('Number of Black Pixels')

# Save the column pixel count plot
#plt.savefig('column_pixel_count.png')

# Plotting the row pixel counts
#plt.subplot(1, 2, 2)
#plt.plot(rowPixels, color='black')
#plt.title('Black Pixel Count per Row')
#plt.xlabel('Row Index')
#plt.ylabel('Number of Black Pixels')

# Save the row pixel count plot
#plt.savefig('row_pixel_count.png')

#plt.tight_layout()
#plt.show()

# Perform FFT on the column pixel counts
column_fft = fft(columnPixels)
column_freq = fftfreq(len(columnPixels))

# Plot the magnitude of the FFT
plt.figure(figsize=(12, 6))
plt.plot(column_freq, np.abs(column_fft))
plt.title('Frequency Analysis of Column Pixel Counts')
plt.xlabel('Frequency')
plt.ylabel('Magnitude')
plt.grid(True)
plt.show()

# Save the frequency analysis plot
plt.savefig('frequency_analysis.png')

# Find the highest frequency of significant magnitude
magnitude = np.abs(column_fft)
half_length = len(magnitude) // 2  # only consider the first half of the spectrum
peak_idx = np.argmax(magnitude[1:half_length]) + 1  # ignore the zero frequency component

# Highest significant frequency
highest_frequency = column_freq[peak_idx]
print(f"Highest significant frequency: {highest_frequency}")

# Determine the number of columns in each cycle
if highest_frequency != 0:
    cycle_length = 1 / highest_frequency
else:
    cycle_length = np.inf

print(f"Number of columns in each cycle: {cycle_length}")
# this represents number of pixels per 0.04 ms (small box)
# so # of pixels per .2 ms (big box) = cycle_length * 5
# and # of pixels per second = cycle_length * 25
# pixels per ms = cycle_length / 0.04

print(f"Pixels per small box: {cycle_length}")
print(f"Pixels per big box: {cycle_length * 5}")

pixels_per_ms = cycle_length / 40
pixels_per_sec = pixels_per_ms * 1000
print(f"Pixels per second: {pixels_per_sec}")
print(f" ")
print(f"Pixels per ms: {pixels_per_ms}")

end_time = time.time()
elapsed_time = end_time - start_time
print(f"Execution time: {elapsed_time} seconds")

image_straightener.py

import cv2
import numpy as np

def straighten_image(image_path, lines_overlay_path = 'detectedlines.png'):
    # Load the image
    image = cv2.imread(image_path)

    # Convert the image to grayscale
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

    # Use Canny edge detection
    edges = cv2.Canny(gray, 50, 150, apertureSize=3)

    # Detect lines using Hough Line Transform
    lines = cv2.HoughLines(edges, 1, np.pi/180, 200)

    # Draw the detected lines on the image
    lines_image = image.copy()
    if lines is not None:
        for rho, theta in lines[0]:
            a = np.cos(theta)
            b = np.sin(theta)
            x0 = a * rho
            y0 = b * rho
            x1 = int(x0 + 1000 * (-b))
            y1 = int(y0 + 1000 * (a))
            x2 = int(x0 - 1000 * (-b))
            y2 = int(y0 - 1000 * (a))
            cv2.line(lines_image, (x1, y1), (x2, y2), (0, 0, 255), 2)

    # Save the image with lines overlaid
    cv2.imwrite(lines_overlay_path, lines_image)

    # Find the angle of the lines
    angle = 0
    if lines is not None:
        for rho, theta in lines[0]:
            angle = (theta * 180 / np.pi) - 90  # Convert from radians to degrees and adjust angle

    # Rotate the image to straighten it
    (h, w) = image.shape[:2]
    center = (w // 2, h // 2)
    M = cv2.getRotationMatrix2D(center, angle, 1.0)
    rotated = cv2.warpAffine(image, M, (w, h))

    return rotated