chore: normalize line endings (CRLF -> LF)
No content changes: git diff --ignore-all-space over these files is empty. The churn came from editing on Windows against a repo checked out with LF.
This commit is contained in:
1 parent
15566a6951
commit
caf8e98378
315 files changed
+86950
-86950
No files matched your search
@@ -1,112 +1,112 @@
|
||||
import 'dart:io';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:image/image.dart' as img;
|
||||
|
||||
class BlurResult {
|
||||
final double score; // Scaled score (0 - 100)
|
||||
final bool isBlur;
|
||||
final double rawScore;
|
||||
|
||||
BlurResult({required this.score, required this.isBlur, required this.rawScore});
|
||||
}
|
||||
|
||||
class BlurDetector {
|
||||
static BlurResult? mockResult;
|
||||
|
||||
/// We scale the raw variance of Laplacian score (0 - 500.0) to a scaled score (0 - 100.0).
|
||||
/// Center cropping and Gaussian blur isolate text sharpness.
|
||||
static const double maxRawScore = 500.0;
|
||||
static const double rawThreshold = 400.0; // Below this raw score is blurry (80% of 500.0)
|
||||
|
||||
static const double scaledThreshold = 80.0; // Corresponding scaled threshold (400 / 500 * 100)
|
||||
|
||||
static Future<BlurResult> checkBlur(String filePath) async {
|
||||
if (mockResult != null) {
|
||||
return mockResult!;
|
||||
}
|
||||
return await compute(_checkBlurIsolate, filePath);
|
||||
}
|
||||
|
||||
static BlurResult _checkBlurIsolate(String filePath) {
|
||||
try {
|
||||
final bytes = File(filePath).readAsBytesSync();
|
||||
final image = img.decodeImage(bytes);
|
||||
if (image == null) {
|
||||
return BlurResult(score: 0, isBlur: true, rawScore: 0);
|
||||
}
|
||||
|
||||
// 1. Resize to 300 width for fast calculation (reduces isolate time to <100ms)
|
||||
final resized = img.copyResize(image, width: 300);
|
||||
|
||||
// 2. Crop the center 60% area to focus purely on text and ignore document borders / backgrounds
|
||||
final cropWidth = (resized.width * 0.60).toInt();
|
||||
final cropHeight = (resized.height * 0.60).toInt();
|
||||
final startX = (resized.width - cropWidth) ~/ 2;
|
||||
final startY = (resized.height - cropHeight) ~/ 2;
|
||||
final cropped = img.copyCrop(resized, x: startX, y: startY, width: cropWidth, height: cropHeight);
|
||||
|
||||
// 3. Apply Gaussian Blur to filter out sensor noise/camera shaking noise
|
||||
final smoothed = img.gaussianBlur(cropped, radius: 1);
|
||||
|
||||
// 4. Convert to Grayscale
|
||||
final gray = img.grayscale(smoothed);
|
||||
|
||||
final width = gray.width;
|
||||
final height = gray.height;
|
||||
|
||||
// 5. Compute Laplacian values
|
||||
final laplacianValues = <double>[];
|
||||
double sum = 0.0;
|
||||
|
||||
for (int y = 1; y < height - 1; y++) {
|
||||
for (int x = 1; x < width - 1; x++) {
|
||||
final p = gray.getPixel(x, y);
|
||||
final pLeft = gray.getPixel(x - 1, y);
|
||||
final pRight = gray.getPixel(x + 1, y);
|
||||
final pTop = gray.getPixel(x, y - 1);
|
||||
final pBottom = gray.getPixel(x, y + 1);
|
||||
|
||||
// Get grayscale values directly (r = g = b)
|
||||
final lum = p.r.toDouble();
|
||||
final lumLeft = pLeft.r.toDouble();
|
||||
final lumRight = pRight.r.toDouble();
|
||||
final lumTop = pTop.r.toDouble();
|
||||
final lumBottom = pBottom.r.toDouble();
|
||||
|
||||
// 3x3 Laplacian: 4 * center - left - right - top - bottom
|
||||
final laplacian = (4 * lum) - lumLeft - lumRight - lumTop - lumBottom;
|
||||
laplacianValues.add(laplacian);
|
||||
sum += laplacian;
|
||||
}
|
||||
}
|
||||
|
||||
final count = laplacianValues.length;
|
||||
if (count == 0) return BlurResult(score: 0, isBlur: true, rawScore: 0);
|
||||
|
||||
final mean = sum / count;
|
||||
|
||||
// 6. Calculate Variance of Laplacian
|
||||
double varianceSum = 0.0;
|
||||
for (final val in laplacianValues) {
|
||||
varianceSum += (val - mean) * (val - mean);
|
||||
}
|
||||
|
||||
final rawScore = varianceSum / count;
|
||||
|
||||
// 7. Scale score to 0-100 range
|
||||
final scaledScore = (rawScore / maxRawScore * 100).clamp(0.0, 100.0);
|
||||
final isBlur = scaledScore < scaledThreshold;
|
||||
|
||||
debugPrint('Blur detection: Raw Score: $rawScore, Scaled Score: $scaledScore, IsBlur: $isBlur');
|
||||
|
||||
return BlurResult(
|
||||
score: scaledScore,
|
||||
isBlur: isBlur,
|
||||
rawScore: rawScore,
|
||||
);
|
||||
} catch (e) {
|
||||
debugPrint('Error detecting blur in isolate: $e');
|
||||
return BlurResult(score: 0, isBlur: true, rawScore: 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
import 'dart:io';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:image/image.dart' as img;
|
||||
|
||||
class BlurResult {
|
||||
final double score; // Scaled score (0 - 100)
|
||||
final bool isBlur;
|
||||
final double rawScore;
|
||||
|
||||
BlurResult({required this.score, required this.isBlur, required this.rawScore});
|
||||
}
|
||||
|
||||
class BlurDetector {
|
||||
static BlurResult? mockResult;
|
||||
|
||||
/// We scale the raw variance of Laplacian score (0 - 500.0) to a scaled score (0 - 100.0).
|
||||
/// Center cropping and Gaussian blur isolate text sharpness.
|
||||
static const double maxRawScore = 500.0;
|
||||
static const double rawThreshold = 400.0; // Below this raw score is blurry (80% of 500.0)
|
||||
|
||||
static const double scaledThreshold = 80.0; // Corresponding scaled threshold (400 / 500 * 100)
|
||||
|
||||
static Future<BlurResult> checkBlur(String filePath) async {
|
||||
if (mockResult != null) {
|
||||
return mockResult!;
|
||||
}
|
||||
return await compute(_checkBlurIsolate, filePath);
|
||||
}
|
||||
|
||||
static BlurResult _checkBlurIsolate(String filePath) {
|
||||
try {
|
||||
final bytes = File(filePath).readAsBytesSync();
|
||||
final image = img.decodeImage(bytes);
|
||||
if (image == null) {
|
||||
return BlurResult(score: 0, isBlur: true, rawScore: 0);
|
||||
}
|
||||
|
||||
// 1. Resize to 300 width for fast calculation (reduces isolate time to <100ms)
|
||||
final resized = img.copyResize(image, width: 300);
|
||||
|
||||
// 2. Crop the center 60% area to focus purely on text and ignore document borders / backgrounds
|
||||
final cropWidth = (resized.width * 0.60).toInt();
|
||||
final cropHeight = (resized.height * 0.60).toInt();
|
||||
final startX = (resized.width - cropWidth) ~/ 2;
|
||||
final startY = (resized.height - cropHeight) ~/ 2;
|
||||
final cropped = img.copyCrop(resized, x: startX, y: startY, width: cropWidth, height: cropHeight);
|
||||
|
||||
// 3. Apply Gaussian Blur to filter out sensor noise/camera shaking noise
|
||||
final smoothed = img.gaussianBlur(cropped, radius: 1);
|
||||
|
||||
// 4. Convert to Grayscale
|
||||
final gray = img.grayscale(smoothed);
|
||||
|
||||
final width = gray.width;
|
||||
final height = gray.height;
|
||||
|
||||
// 5. Compute Laplacian values
|
||||
final laplacianValues = <double>[];
|
||||
double sum = 0.0;
|
||||
|
||||
for (int y = 1; y < height - 1; y++) {
|
||||
for (int x = 1; x < width - 1; x++) {
|
||||
final p = gray.getPixel(x, y);
|
||||
final pLeft = gray.getPixel(x - 1, y);
|
||||
final pRight = gray.getPixel(x + 1, y);
|
||||
final pTop = gray.getPixel(x, y - 1);
|
||||
final pBottom = gray.getPixel(x, y + 1);
|
||||
|
||||
// Get grayscale values directly (r = g = b)
|
||||
final lum = p.r.toDouble();
|
||||
final lumLeft = pLeft.r.toDouble();
|
||||
final lumRight = pRight.r.toDouble();
|
||||
final lumTop = pTop.r.toDouble();
|
||||
final lumBottom = pBottom.r.toDouble();
|
||||
|
||||
// 3x3 Laplacian: 4 * center - left - right - top - bottom
|
||||
final laplacian = (4 * lum) - lumLeft - lumRight - lumTop - lumBottom;
|
||||
laplacianValues.add(laplacian);
|
||||
sum += laplacian;
|
||||
}
|
||||
}
|
||||
|
||||
final count = laplacianValues.length;
|
||||
if (count == 0) return BlurResult(score: 0, isBlur: true, rawScore: 0);
|
||||
|
||||
final mean = sum / count;
|
||||
|
||||
// 6. Calculate Variance of Laplacian
|
||||
double varianceSum = 0.0;
|
||||
for (final val in laplacianValues) {
|
||||
varianceSum += (val - mean) * (val - mean);
|
||||
}
|
||||
|
||||
final rawScore = varianceSum / count;
|
||||
|
||||
// 7. Scale score to 0-100 range
|
||||
final scaledScore = (rawScore / maxRawScore * 100).clamp(0.0, 100.0);
|
||||
final isBlur = scaledScore < scaledThreshold;
|
||||
|
||||
debugPrint('Blur detection: Raw Score: $rawScore, Scaled Score: $scaledScore, IsBlur: $isBlur');
|
||||
|
||||
return BlurResult(
|
||||
score: scaledScore,
|
||||
isBlur: isBlur,
|
||||
rawScore: rawScore,
|
||||
);
|
||||
} catch (e) {
|
||||
debugPrint('Error detecting blur in isolate: $e');
|
||||
return BlurResult(score: 0, isBlur: true, rawScore: 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user