OpenCV (abreviatura de Open Source Computer Vision) es una biblioteca para visión por computadora, aprendizaje automático y procesamiento de imágenes. Se puede utilizar para identificar patrones en una imagen, extraer características y realizar operaciones matemáticas en ella. El primer paso es procesar la captura de pantalla del rompecabezas usando OpenCV. Repasemos rápidamente los conceptos básicos del procesamiento de imágenes OpenCV.
Instale el paquete Python OpenCV
pip install opencv-python
Cómo cargar una imagen
cv.imread
lee un archivo de imagen y lo convierte en una matriz OpenCV. Si la imagen no se puede leer porque es posible que falte el archivo o esté en un formato que OpenCV no puede entender, se devuelve una matriz vacía. La matriz OpenCV se puede convertir en una imagen usando cv.imshow
función.
import cv2 as cv
import numpy as np# Reading an image
original = cv.imread("<file_name">)
cv.imshow("original", original)
Cómo dibujar una línea, un círculo, un rectángulo o un texto en la misma imagen
Una vez que detectamos la cuadrícula, necesitamos recrearla usando líneas y colocar Q
usando texto. Veamos un breve fragmento para dibujar una línea, un círculo, un rectángulo y un texto en la matriz de lectura anterior.
# Drawing a line
line = cv.line(original, (original.shape[1]//2, original.shape[0]//2), (0,0) , (0,255,0), thickness=2)
cv.imshow("line", line)# Drawing other shapes
circle = cv.circle(line, (line.shape[1]//2, line.shape[0]//2), 50, (0,0,255), thickness=2)
rect = cv.rectangle(circle, (10,10), (circle.shape[1]//2, circle.shape[0]//2), (255,0,0), thickness=2)
text = cv.putText(rect, "Hi", (rect.shape[1]//2, rect.shape[0]//2), cv.FONT_HERSHEY_SIMPLEX, 1, (255,255,255), thickness=2)
cv.imshow("all shapes", text)
Cómo detectar contornos
Los contornos son simplemente una forma que une todos los puntos de color e intensidad similares en un límite continuo. Son útiles para detectar formas y analizar el contorno de objetos. Dibujaremos nuestra cuadrícula de rompecabezas detectando las celdas individuales.
# Its best to convert image to grayscale
# and add a bit of blur for better contour detections
# since our image is mostly a grid we dont need blur# by default OpenCV reads images as BGR
# as opposed to traditional RGB
gray = cv.cvtConvert(original, cv.COLOR_BGR2GRAY)
contours, _ = cv.findContours(gray, cv.RETR_TREE, cv.CHAIN_APPROX_NONE)
Por defecto, OpenCV lee las imágenes como BGRa diferencia de lo tradicional RGB
Recortar una imagen
Para que podamos eliminar áreas innecesarias de las capturas de pantalla y reducir el ruido, una vez que hayamos detectado nuestros contornos.
# its essentially selecting the pixels we need from the entire image
cropped = original[0:original.shape[1]//2, 0:original.shape[0]//2]
cv.imshow("cropped", cropped)
Primero, comenzamos cargando la imagen en la memoria y convirtiéndola a escala de grises. Esto ayuda a simplificar la detección de contornos, un paso general que siempre se sigue ya que reduce la complejidad de la imagen. A continuación, buscamos contornos, los ordenamos y seleccionamos el más grande. Normalmente, el primer contorno es el cuadro encuadernado de la imagen original, por lo que utilizamos el segundo contorno más grande para aislar la cuadrícula del rompecabezas. Luego, recortamos la imagen sólo para obtener la cuadrícula y nada más. Volvemos a encontrar contornos, ya que ahora se reduce el ruido, detectará mejor la rejilla. Determinamos el número de celdas dentro de la cuadrícula e iteramos sobre cada celda, tomamos el color promedio y asignamos un número de cada color, lo que nos da la matriz 2D de nuestro rompecabezas.
# Read the input image and save the original
original = cv.imread(file_name)
cv.imwrite("solution/original.png", original)# Convert the image to grayscale
gray = cv.cvtColor(original, cv.COLOR_BGR2GRAY)
# Find contours in the grayscale image and sort them by area
contours, _ = cv.findContours(gray, cv.RETR_TREE, cv.CHAIN_APPROX_NONE)
contours = sorted(contours, key=cv.contourArea, reverse=True)
# Extract the bounding box of the puzzle grid (using the second largest contour)
x, y, w, h = cv.boundingRect(contours[1])
# Crop the grid area from the original image
grid = original[y:y+h, x:x+w]
cv.imwrite("solution/grid.png", grid)
# Convert the cropped grid to grayscale
gray = cv.cvtColor(grid, cv.COLOR_BGR2GRAY)
cv.imwrite("solution/gray-grid.png", gray)
# Find contours again in the cropped grayscale grid
contours, _ = cv.findContours(gray, cv.RETR_TREE, cv.CHAIN_APPROX_NONE)
contours = sorted(contours, key=cv.contourArea)
# Determine the total number of cells in the grid
total_cells = len(contours) - 2
grid_size = int(math.sqrt(total_cells))
# Check if the detected cells form a complete square grid
if total_cells != grid_size**2:
print("Unable to detect full grid! Aborting")
# Calculate individual cell dimensions
cell_width = w // grid_size
cell_height = h // grid_size
# Initialize color mappings and board representation
colors = []
board = []
color_index = 1
color_map = {}
reverse_color_map = {}
padding = 10
# Iterate through each cell in the grid
for i in range(grid_size):
row = []
for j in range(grid_size):
# Calculate cell coordinates with padding
cell_x = j * cell_width
cell_y = i * cell_height
padding = 15
cell = grid[cell_y+padding:cell_y+cell_height-padding, cell_x+padding:cell_x+cell_width-padding]
# Get the average color of the cell
avg_color = cell.mean(axis=0).mean(axis=0)
avg_color = avg_color.astype(int)
avg_color = tuple(avg_color)
# Map the color to a unique index if not already mapped
if avg_color not in color_map:
color_map[avg_color] = str(color_index)
reverse_color_map[str(color_index)] = avg_color
color_index += 1
# Add the color index to the row
row.append(color_map[avg_color])
# Add the row to the board
board.append(row)