Files
foliate-js/panel-detection/opencv.js
T
john-okeefe ffaceaf962 Add panel detection module with multi-tier fallback system
Implement a comprehensive panel detection system for manga and comics
with automatic fallback chain for maximum compatibility.

Core detector (detector.js):
- PanelDetector class with in-memory caching
- Lazy-loading of OpenCV and TensorFlow.js
- Validation logic to filter poor detections
- Cache management to avoid re-detection

OpenCV edge detection (opencv.js):
- Canny edge detection for panel boundaries
- Contour finding with bounding box extraction
- Size and aspect ratio filtering
- Reading order sorting (top-to-bottom, left-to-right)

ML-based detection (coco-ssd.js):
- COCO-SSD pre-trained model integration
- Object detection for irregular panel layouts
- Rectangular filtering for panel-like regions
- Handles edge cases where edge detection fails

Grid-based fallback (grid.js):
- Lightweight 3x3 grid detection
- Empty cell detection via alpha channel analysis
- Adjacent panel merging algorithm
- Always works as final fallback

The detection pipeline tries OpenCV first (fast, accurate),
falls back to ML detection if validation fails,
and uses grid detection as ultimate baseline.
2026-04-13 16:43:25 -04:00

62 lines
1.7 KiB
JavaScript

// panel-detection/opencv.js
// OpenCV-based edge detection for panel boundaries
export async function detectPanelsOpenCV(imageData, cv) {
const src = cv.matFromImageData(imageData);
const gray = new cv.Mat();
const blurred = new cv.Mat();
const edges = new cv.Mat();
const contours = new cv.Mat();
const hierarchy = new cv.Mat();
try {
cv.cvtColor(src, gray, cv.COLOR_RGBA2GRAY, 0);
cv.GaussianBlur(gray, blurred, new cv.Size(5, 5), 0, 0, cv.BORDER_DEFAULT);
cv.Canny(blurred, edges, 50, 150, 3, false);
cv.findContours(
edges,
contours,
hierarchy,
cv.RETR_EXTERNAL,
cv.CHAIN_APPROX_SIMPLE,
);
const panels = [];
const imgWidth = imageData.width;
const imgHeight = imageData.height;
for (let i = 0; i < contours.size(); i++) {
const rect = cv.boundingRect(contours.get(i));
const minSize = Math.min(imgWidth, imgHeight) * 0.08;
const aspectRatio = rect.width / rect.height;
if (rect.width < minSize || rect.height < minSize) continue;
if (aspectRatio < 0.2 || aspectRatio > 8) continue;
panels.push({
id: `opencv-${i}`,
x: (rect.x / imgWidth) * 100,
y: (rect.y / imgHeight) * 100,
width: (rect.width / imgWidth) * 100,
height: (rect.height / imgHeight) * 100,
reading_order: i,
});
}
panels.sort((a, b) => {
const rowA = Math.floor(a.y / 20);
const rowB = Math.floor(b.y / 20);
if (rowA !== rowB) return rowA - rowB;
return a.x - b.x;
});
return panels.map((p, i) => ({ ...p, reading_order: i }));
} finally {
src.delete();
gray.delete();
blurred.delete();
edges.delete();
contours.delete();
hierarchy.delete();
}
}