Added scanning barcodes with a camera

This commit is contained in:
2026-03-08 16:59:33 +00:00
parent b4f8489834
commit 5a37e5dd5f
404 changed files with 224181 additions and 0 deletions

View File

@@ -0,0 +1,254 @@
# Working with Box Coordinates {#working-with-box-coordinates}
This guide explains how Quagga2's coordinate system works and how to properly use `box`, `boxes`, and `line` coordinates for overlay rendering, especially when using `inputStream.size` to scale processing.
## Understanding the Coordinate System {#understanding-coordinate-system}
Quagga2 returns `box`, `boxes`, and `line` coordinates in **processed canvas coordinates**, not original image/video coordinates. This is important to understand when:
- Drawing overlay boxes on a video element
- Using `inputStream.size` to reduce processing resolution
- Cropping detected barcode regions from the original image
### Key Concepts {#key-concepts}
| Term | Description |
|------|-------------|
| **Real Size** | The actual dimensions of the source image/video |
| **Processed Size** | The scaled dimensions used for barcode detection (controlled by `inputStream.size`) |
| **Canvas Size** | The dimensions of the processing canvas (typically matches processed size) |
### How Coordinates are Generated {#how-coordinates-generated}
1. **Image Scaling**: When `inputStream.size` is set, the image is scaled so the longest side equals that value
2. **Localization**: Barcode regions are found in the scaled image
3. **Box Coordinates**: Returned coordinates are relative to the scaled/processed image
4. **halfSample Adjustment**: If `halfSample: true`, coordinates are automatically scaled 2x
## Converting Coordinates to Original Image Space {#converting-coordinates}
When you need to draw boxes on the original video/image (not the processed canvas), you must scale the coordinates.
### For Live Video Streams {#live-video-streams}
```javascript
Quagga.onDetected(function(result) {
if (!result.box) return;
// Get the video element
const video = document.querySelector('video');
const videoWidth = video.videoWidth; // Real video dimensions
const videoHeight = video.videoHeight;
// Get processed dimensions from Quagga
const canvas = Quagga.canvas.dom.image;
const processedWidth = canvas.width;
const processedHeight = canvas.height;
// Calculate scale factors
const scaleX = videoWidth / processedWidth;
const scaleY = videoHeight / processedHeight;
// Convert box coordinates to video space
const scaledBox = result.box.map(function(point) {
return [
point[0] * scaleX,
point[1] * scaleY
];
});
// Now use scaledBox for drawing on video overlay
drawBoxOnVideo(scaledBox);
});
```
### For Static Images with decodeSingle {#static-images-decodesingle}
```javascript
Quagga.decodeSingle({
src: './barcode.jpg',
inputStream: {
size: 800 // Process at 800px max dimension
},
// ... other config
}, function(result) {
if (!result || !result.box) return;
// Load original image to get real dimensions
const img = new Image();
img.onload = function() {
const realWidth = img.naturalWidth;
const realHeight = img.naturalHeight;
// Calculate what the processed size was
const aspectRatio = realWidth / realHeight;
let processedWidth, processedHeight;
if (aspectRatio > 1) {
// Landscape: width is the longest side
processedWidth = 800;
processedHeight = Math.floor(800 / aspectRatio);
} else {
// Portrait: height is the longest side
processedHeight = 800;
processedWidth = Math.floor(800 * aspectRatio);
}
// Calculate scale factors
const scaleX = realWidth / processedWidth;
const scaleY = realHeight / processedHeight;
// Convert coordinates
const scaledBox = result.box.map(function(point) {
return [
point[0] * scaleX,
point[1] * scaleY
];
});
// Use scaledBox for original image operations
cropBarcodeFromOriginal(scaledBox);
};
img.src = './barcode.jpg';
});
```
## Complete Example: Drawing Boxes on Live Video {#complete-example}
Here's a complete example showing how to draw accurate bounding boxes on a live video stream:
```javascript
// Initialize Quagga with reduced processing size for performance
Quagga.init({
inputStream: {
name: "Live",
type: "LiveStream",
target: document.querySelector('#scanner-container'),
constraints: {
width: 1280,
height: 720,
facingMode: "environment"
},
size: 640 // Process at 640px for better performance
},
locator: {
patchSize: "medium",
halfSample: true
},
decoder: {
readers: ["code_128_reader", "ean_reader"]
}
}, function(err) {
if (err) {
console.error(err);
return;
}
Quagga.start();
});
// Handle detections with coordinate scaling
Quagga.onDetected(function(result) {
const video = document.querySelector('video');
const overlay = document.querySelector('#overlay-canvas');
const ctx = overlay.getContext('2d');
// Match overlay to video size
overlay.width = video.videoWidth;
overlay.height = video.videoHeight;
// Get processed canvas size
const processedCanvas = Quagga.canvas.dom.image;
// Calculate scale factors
const scaleX = video.videoWidth / processedCanvas.width;
const scaleY = video.videoHeight / processedCanvas.height;
// Clear previous drawings
ctx.clearRect(0, 0, overlay.width, overlay.height);
// Draw all detected boxes
if (result.boxes) {
result.boxes.forEach(function(box) {
drawScaledBox(ctx, box, scaleX, scaleY, '#00ff00');
});
}
// Highlight the successfully decoded box
if (result.box) {
drawScaledBox(ctx, result.box, scaleX, scaleY, '#ff0000');
}
});
function drawScaledBox(ctx, box, scaleX, scaleY, color) {
ctx.strokeStyle = color;
ctx.lineWidth = 2;
ctx.beginPath();
// Scale and draw each point
const scaledPoints = box.map(p => [p[0] * scaleX, p[1] * scaleY]);
ctx.moveTo(scaledPoints[0][0], scaledPoints[0][1]);
for (let i = 1; i < scaledPoints.length; i++) {
ctx.lineTo(scaledPoints[i][0], scaledPoints[i][1]);
}
ctx.closePath();
ctx.stroke();
}
```
## When Coordinates Don't Need Scaling {#no-scaling-needed}
If you're drawing on Quagga's own overlay canvas (`Quagga.canvas.dom.overlay`), coordinates are already in the correct space:
```javascript
Quagga.onDetected(function(result) {
const ctx = Quagga.canvas.ctx.overlay;
const canvas = Quagga.canvas.dom.overlay;
ctx.clearRect(0, 0, canvas.width, canvas.height);
// No scaling needed - coordinates match the overlay canvas
if (result.box) {
Quagga.ImageDebug.drawPath(result.box, { x: 0, y: 1 }, ctx, {
color: "#00ff00",
lineWidth: 2
});
}
});
```
## Common Pitfalls {#common-pitfalls}
### 1. Forgetting halfSample Adjustment {#forgetting-halfsample}
If you're manually calculating processed size, remember that `halfSample: true` doesn't affect the returned coordinates (they're already adjusted).
### 2. Using Wrong Canvas Reference {#wrong-canvas-reference}
```javascript
// ❌ Wrong - using overlay canvas for scale calculation
const wrongWidth = Quagga.canvas.dom.overlay.width;
// ✅ Correct - using image canvas for scale calculation
const correctWidth = Quagga.canvas.dom.image.width;
```
### 3. Assuming Square Pixels {#assuming-square-pixels}
Always calculate scaleX and scaleY separately, as aspect ratios may differ:
```javascript
// ❌ Wrong - using single scale factor
const scale = videoWidth / canvasWidth;
// ✅ Correct - separate scale factors
const scaleX = videoWidth / canvasWidth;
const scaleY = videoHeight / canvasHeight;
```
## Performance Tips {#performance-tips}
1. **Use smaller `inputStream.size`** (e.g., 640-800) for live video to reduce CPU usage
2. **Cache scale factors** - recalculate only when video dimensions change
3. **Use requestAnimationFrame** for smooth overlay rendering
4. **Consider using Quagga's built-in overlay** when possible to avoid manual scaling