|
|
@@ -178,55 +178,401 @@ export class BitmapUtils {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 使用边缘密度作为清晰度指标(更适合大图)
|
|
|
+ * 基于边缘梯度的清晰度检测(对模糊更敏感)
|
|
|
*/
|
|
|
- static calculateEdgeDensity(bitmap: Bitmap): number {
|
|
|
+ static calculateEdgeGradientClarity(bitmap: Bitmap): number {
|
|
|
try {
|
|
|
const width = bitmap.getWidth()
|
|
|
const height = bitmap.getHeight()
|
|
|
|
|
|
- // 对于大图,使用适当的采样尺寸
|
|
|
- const sampleWidth = Math.min(width, 300)
|
|
|
- const sampleHeight = Math.min(height, (300 * height / width).toInt())
|
|
|
+ // 取中心区域
|
|
|
+ const centerRatio = 0.7
|
|
|
+ const centerWidth = (width * centerRatio).toInt()
|
|
|
+ const centerHeight = (height * centerRatio).toInt()
|
|
|
+ const startX = ((width - centerWidth) / 2).toInt()
|
|
|
+ const startY = ((height - centerHeight) / 2).toInt()
|
|
|
+
|
|
|
+ const centerBitmap = Bitmap.createBitmap(
|
|
|
+ bitmap,
|
|
|
+ startX,
|
|
|
+ startY,
|
|
|
+ centerWidth,
|
|
|
+ centerHeight
|
|
|
+ )
|
|
|
+
|
|
|
+ if (centerBitmap == null) {
|
|
|
+ return -1
|
|
|
+ }
|
|
|
+
|
|
|
+ // 缩放以提高性能
|
|
|
+ const sampleWidth = Math.min(centerWidth, 300)
|
|
|
+ const sampleHeight = Math.min(centerHeight, (300 * centerHeight / centerWidth).toInt())
|
|
|
+
|
|
|
+ const scaledBitmap = Bitmap.createScaledBitmap(
|
|
|
+ centerBitmap,
|
|
|
+ sampleWidth.toInt(),
|
|
|
+ sampleHeight.toInt(),
|
|
|
+ false
|
|
|
+ )
|
|
|
+
|
|
|
+ let totalGradient = 0.0
|
|
|
+ let gradientCount = 0
|
|
|
+
|
|
|
+ // 计算梯度(边缘强度)
|
|
|
+ for (let i: number = 1; i < sampleWidth - 1; i++) {
|
|
|
+ for (let j: number = 1; j < sampleHeight - 1; j++) {
|
|
|
+ const iInt = i.toInt()
|
|
|
+ const jInt = j.toInt()
|
|
|
+
|
|
|
+ // 获取3x3区域的像素
|
|
|
+ const p00 = this.getPixelBrightness(scaledBitmap.getPixel(iInt - 1, jInt - 1))
|
|
|
+ const p01 = this.getPixelBrightness(scaledBitmap.getPixel(iInt, jInt - 1))
|
|
|
+ const p02 = this.getPixelBrightness(scaledBitmap.getPixel(iInt + 1, jInt - 1))
|
|
|
+ const p10 = this.getPixelBrightness(scaledBitmap.getPixel(iInt - 1, jInt))
|
|
|
+ const p12 = this.getPixelBrightness(scaledBitmap.getPixel(iInt + 1, jInt))
|
|
|
+ const p20 = this.getPixelBrightness(scaledBitmap.getPixel(iInt - 1, jInt + 1))
|
|
|
+ const p21 = this.getPixelBrightness(scaledBitmap.getPixel(iInt, jInt + 1))
|
|
|
+ const p22 = this.getPixelBrightness(scaledBitmap.getPixel(iInt + 1, jInt + 1))
|
|
|
+
|
|
|
+ // 计算Sobel梯度
|
|
|
+ const gx = (p02 + 2 * p12 + p22) - (p00 + 2 * p10 + p20)
|
|
|
+ const gy = (p20 + 2 * p21 + p22) - (p00 + 2 * p01 + p02)
|
|
|
+ const gradient = Math.sqrt(gx * gx + gy * gy)
|
|
|
+
|
|
|
+ // 只考虑明显的边缘(避免噪声)
|
|
|
+ if (gradient > 10) {
|
|
|
+ totalGradient += gradient
|
|
|
+ gradientCount++
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const avgGradient = gradientCount > 0 ? totalGradient / gradientCount : 0
|
|
|
+
|
|
|
+ // 回收Bitmap
|
|
|
+ if (!scaledBitmap.isRecycled()) {
|
|
|
+ scaledBitmap.recycle()
|
|
|
+ }
|
|
|
+ if (!centerBitmap.isRecycled()) {
|
|
|
+ centerBitmap.recycle()
|
|
|
+ }
|
|
|
+
|
|
|
+ return avgGradient
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ console.error('边缘梯度清晰度计算失败:', error)
|
|
|
+ return -1
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 快速拉普拉斯方差 - 对模糊敏感且计算效率高
|
|
|
+ */
|
|
|
+ static calculateLaplacianVariance(bitmap: Bitmap): number {
|
|
|
+ try {
|
|
|
+ const width = bitmap.getWidth()
|
|
|
+ const height = bitmap.getHeight()
|
|
|
+
|
|
|
+ // 取中心区域,但使用更小的采样
|
|
|
+ const centerRatio = 0.6
|
|
|
+ const centerWidth = (width * centerRatio).toInt()
|
|
|
+ const centerHeight = (height * centerRatio).toInt()
|
|
|
+ const startX = ((width - centerWidth) / 2).toInt()
|
|
|
+ const startY = ((height - centerHeight) / 2).toInt()
|
|
|
+
|
|
|
+ const centerBitmap = Bitmap.createBitmap(
|
|
|
+ bitmap,
|
|
|
+ startX,
|
|
|
+ startY,
|
|
|
+ centerWidth,
|
|
|
+ centerHeight
|
|
|
+ )
|
|
|
+
|
|
|
+ if (centerBitmap == null) {
|
|
|
+ return -1
|
|
|
+ }
|
|
|
+
|
|
|
+ // 使用较小的采样尺寸
|
|
|
+ const sampleWidth = Math.min(centerWidth, 150)
|
|
|
+ const sampleHeight = Math.min(centerHeight, 100)
|
|
|
|
|
|
const scaledBitmap = Bitmap.createScaledBitmap(
|
|
|
- bitmap,
|
|
|
- sampleWidth.toInt(),
|
|
|
- sampleHeight.toInt(),
|
|
|
+ centerBitmap,
|
|
|
+ sampleWidth.toInt(),
|
|
|
+ sampleHeight.toInt(),
|
|
|
false
|
|
|
)
|
|
|
|
|
|
- let edgeCount = 0
|
|
|
- const totalPixels = sampleWidth * sampleHeight
|
|
|
+ let laplacianSum = 0.0
|
|
|
+ let pixelCount = 0
|
|
|
|
|
|
- // 简单的边缘检测
|
|
|
+ // 简化的拉普拉斯算子 - 只计算中心与四邻域的差异
|
|
|
for (let i: number = 1; i < sampleWidth - 1; i++) {
|
|
|
for (let j: number = 1; j < sampleHeight - 1; j++) {
|
|
|
- const center = this.getPixelBrightness(scaledBitmap.getPixel(i.toInt(), j.toInt()))
|
|
|
- const right = this.getPixelBrightness(scaledBitmap.getPixel((i + 1).toInt(), j.toInt()))
|
|
|
- const bottom = this.getPixelBrightness(scaledBitmap.getPixel(i.toInt(), (j + 1).toInt()))
|
|
|
+ const iInt = i.toInt()
|
|
|
+ const jInt = j.toInt()
|
|
|
+
|
|
|
+ const center = this.getPixelBrightness(scaledBitmap.getPixel(iInt, jInt))
|
|
|
+ const left = this.getPixelBrightness(scaledBitmap.getPixel(iInt - 1, jInt))
|
|
|
+ const right = this.getPixelBrightness(scaledBitmap.getPixel(iInt + 1, jInt))
|
|
|
+ const top = this.getPixelBrightness(scaledBitmap.getPixel(iInt, jInt - 1))
|
|
|
+ const bottom = this.getPixelBrightness(scaledBitmap.getPixel(iInt, jInt + 1))
|
|
|
|
|
|
- // 如果相邻像素亮度差异大,认为是边缘
|
|
|
- if (Math.abs(center - right) > 20 || Math.abs(center - bottom) > 20) {
|
|
|
- edgeCount++
|
|
|
+ // 拉普拉斯值 = 4*中心 - 上下左右
|
|
|
+ const laplacianValue = Math.abs(4 * center - left - right - top - bottom)
|
|
|
+
|
|
|
+ if (laplacianValue > 5) { // 忽略微小变化
|
|
|
+ laplacianSum += laplacianValue
|
|
|
+ pixelCount++
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- const edgeDensity = (edgeCount / totalPixels) * 1000
|
|
|
+ const avgLaplacian = pixelCount > 0 ? laplacianSum / pixelCount : 0
|
|
|
|
|
|
+ // 回收Bitmap
|
|
|
if (!scaledBitmap.isRecycled()) {
|
|
|
scaledBitmap.recycle()
|
|
|
}
|
|
|
+ if (!centerBitmap.isRecycled()) {
|
|
|
+ centerBitmap.recycle()
|
|
|
+ }
|
|
|
|
|
|
- return edgeDensity
|
|
|
+ return avgLaplacian
|
|
|
|
|
|
} catch (error) {
|
|
|
- console.error('边缘密度计算失败:', error)
|
|
|
+ console.error('拉普拉斯方差计算失败:', error)
|
|
|
return -1
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 快速局部对比度检测 - 优化性能
|
|
|
+ */
|
|
|
+ static calculateFastLocalContrast(bitmap: Bitmap): number {
|
|
|
+ try {
|
|
|
+ const width = bitmap.getWidth()
|
|
|
+ const height = bitmap.getHeight()
|
|
|
+
|
|
|
+ // 直接使用缩放后的整个图像,不裁剪中心区域
|
|
|
+ const sampleWidth = Math.min(width, 200)
|
|
|
+ const sampleHeight = Math.min(height, 150)
|
|
|
+
|
|
|
+ const scaledBitmap = Bitmap.createScaledBitmap(
|
|
|
+ bitmap,
|
|
|
+ sampleWidth.toInt(),
|
|
|
+ sampleHeight.toInt(),
|
|
|
+ false
|
|
|
+ )
|
|
|
+
|
|
|
+ let contrastSum = 0.0
|
|
|
+ let sampleCount = 0
|
|
|
+
|
|
|
+ // 使用步长采样,减少计算量
|
|
|
+ const step = 2
|
|
|
+
|
|
|
+ for (let i: number = 1; i < sampleWidth - 1; i += step) {
|
|
|
+ for (let j: number = 1; j < sampleHeight - 1; j += step) {
|
|
|
+ const iInt = i.toInt()
|
|
|
+ const jInt = j.toInt()
|
|
|
+
|
|
|
+ // 只检查水平和垂直方向的对比度
|
|
|
+ const center = this.getPixelBrightness(scaledBitmap.getPixel(iInt, jInt))
|
|
|
+ const left = this.getPixelBrightness(scaledBitmap.getPixel(iInt - 1, jInt))
|
|
|
+ const right = this.getPixelBrightness(scaledBitmap.getPixel(iInt + 1, jInt))
|
|
|
+ const top = this.getPixelBrightness(scaledBitmap.getPixel(iInt, jInt - 1))
|
|
|
+ const bottom = this.getPixelBrightness(scaledBitmap.getPixel(iInt, jInt + 1))
|
|
|
+
|
|
|
+ // 计算最大相邻对比度
|
|
|
+ const horizontalContrast = Math.max(Math.abs(center - left), Math.abs(center - right))
|
|
|
+ const verticalContrast = Math.max(Math.abs(center - top), Math.abs(center - bottom))
|
|
|
+ const maxContrast = Math.max(horizontalContrast, verticalContrast)
|
|
|
+
|
|
|
+ if (maxContrast > 15) { // 只考虑明显的对比度
|
|
|
+ contrastSum += maxContrast
|
|
|
+ sampleCount++
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const avgContrast = sampleCount > 0 ? contrastSum / sampleCount : 0
|
|
|
+
|
|
|
+ if (!scaledBitmap.isRecycled()) {
|
|
|
+ scaledBitmap.recycle()
|
|
|
+ }
|
|
|
+
|
|
|
+ return avgContrast
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ console.error('快速局部对比度计算失败:', error)
|
|
|
+ return -1
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 自适应阈值清晰度检测
|
|
|
+ * 根据图像内容动态调整判断标准
|
|
|
+ */
|
|
|
+ static calculateAdaptiveClarity(bitmap: Bitmap): number {
|
|
|
+ try {
|
|
|
+ const width = bitmap.getWidth()
|
|
|
+ const height = bitmap.getHeight()
|
|
|
+
|
|
|
+ // 使用小尺寸采样
|
|
|
+ const sampleWidth = Math.min(width, 150)
|
|
|
+ const sampleHeight = Math.min(height, 100)
|
|
|
+
|
|
|
+ const scaledBitmap = Bitmap.createScaledBitmap(
|
|
|
+ bitmap,
|
|
|
+ sampleWidth.toInt(),
|
|
|
+ sampleHeight.toInt(),
|
|
|
+ false
|
|
|
+ )
|
|
|
+
|
|
|
+ // 第一步:分析图像的整体对比度特征
|
|
|
+ let highContrastCount = 0
|
|
|
+ let totalSamples = 0
|
|
|
+
|
|
|
+ for (let i: number = 2; i < sampleWidth - 2; i += 2) {
|
|
|
+ for (let j: number = 2; j < sampleHeight - 2; j += 2) {
|
|
|
+ const iInt = i.toInt()
|
|
|
+ const jInt = j.toInt()
|
|
|
+
|
|
|
+ const center = this.getPixelBrightness(scaledBitmap.getPixel(iInt, jInt))
|
|
|
+ const neighbors = [
|
|
|
+ this.getPixelBrightness(scaledBitmap.getPixel(iInt - 1, jInt)),
|
|
|
+ this.getPixelBrightness(scaledBitmap.getPixel(iInt + 1, jInt)),
|
|
|
+ this.getPixelBrightness(scaledBitmap.getPixel(iInt, jInt - 1)),
|
|
|
+ this.getPixelBrightness(scaledBitmap.getPixel(iInt, jInt + 1))
|
|
|
+ ]
|
|
|
+
|
|
|
+ // 检查是否有高对比度边缘
|
|
|
+ for (const neighbor of neighbors) {
|
|
|
+ if (Math.abs(center - neighbor) > 25) {
|
|
|
+ highContrastCount++
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ totalSamples++
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const highContrastRatio = highContrastCount / totalSamples
|
|
|
+
|
|
|
+ // 第二步:基于对比度特征调整清晰度评分
|
|
|
+ let clarityScore: number
|
|
|
+
|
|
|
+ if (highContrastRatio < 0.05) {
|
|
|
+ // 低对比度图像 - 可能是模糊或单色
|
|
|
+ clarityScore = highContrastRatio * 500
|
|
|
+ } else if (highContrastRatio < 0.15) {
|
|
|
+ // 中等对比度
|
|
|
+ clarityScore = 25 + (highContrastRatio - 0.05) * 250
|
|
|
+ } else {
|
|
|
+ // 高对比度 - 可能是清晰图像
|
|
|
+ clarityScore = 50 + (highContrastRatio - 0.15) * 200
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!scaledBitmap.isRecycled()) {
|
|
|
+ scaledBitmap.recycle()
|
|
|
+ }
|
|
|
+
|
|
|
+ return clarityScore
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ console.error('自适应清晰度计算失败:', error)
|
|
|
+ return -1
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 优化的快速清晰度检测方案
|
|
|
+ * 平衡准确性和性能
|
|
|
+ */
|
|
|
+ static calculateOptimizedClarity(bitmap: Bitmap): number {
|
|
|
+ try {
|
|
|
+ // 使用两种快速方法,权重偏向对模糊更敏感的方法
|
|
|
+ const laplacianScore = this.calculateAdaptiveClarity(bitmap)
|
|
|
+ const contrastScore = this.calculateFastLocalContrast(bitmap)
|
|
|
+
|
|
|
+ // 如果两种方法都返回有效结果,取加权平均
|
|
|
+ if (laplacianScore > 0 && contrastScore > 0) {
|
|
|
+ // 自适应阈值清晰度检测
|
|
|
+ return laplacianScore * 0.3 + contrastScore * 0.7
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果只有一种方法有效,返回该结果
|
|
|
+ return Math.max(laplacianScore, contrastScore)
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ console.error('优化清晰度计算失败:', error)
|
|
|
+ return -1
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 超快速清晰度检测(最低计算量)
|
|
|
+ */
|
|
|
+ static calculateUltraFastClarity(bitmap: Bitmap): number {
|
|
|
+ try {
|
|
|
+ const width = bitmap.getWidth()
|
|
|
+ const height = bitmap.getHeight()
|
|
|
+
|
|
|
+ // 极小的采样尺寸
|
|
|
+ const sampleWidth = 80
|
|
|
+ const sampleHeight = 60
|
|
|
+
|
|
|
+ const scaledBitmap = Bitmap.createScaledBitmap(
|
|
|
+ bitmap,
|
|
|
+ sampleWidth.toInt(),
|
|
|
+ sampleHeight.toInt(),
|
|
|
+ false
|
|
|
+ )
|
|
|
+
|
|
|
+ let edgeStrength = 0.0
|
|
|
+ let sampleCount = 0
|
|
|
+
|
|
|
+ // 极简的边缘检测 - 只检查少数关键点
|
|
|
+ const checkPoints = [
|
|
|
+ { x: 20, y: 15 }, { x: 40, y: 15 }, { x: 60, y: 15 },
|
|
|
+ { x: 20, y: 30 }, { x: 40, y: 30 }, { x: 60, y: 30 },
|
|
|
+ { x: 20, y: 45 }, { x: 40, y: 45 }, { x: 60, y: 45 }
|
|
|
+ ]
|
|
|
+
|
|
|
+ for (const point of checkPoints) {
|
|
|
+ const x = point.x as number
|
|
|
+ const y = point.y as number
|
|
|
+
|
|
|
+ if (x >= 1 && x < sampleWidth - 1 && y >= 1 && y < sampleHeight - 1) {
|
|
|
+ const center = this.getPixelBrightness(scaledBitmap.getPixel(x.toInt(), y.toInt()))
|
|
|
+ const right = this.getPixelBrightness(scaledBitmap.getPixel(x.toInt() + 1, y.toInt()))
|
|
|
+ const bottom = this.getPixelBrightness(scaledBitmap.getPixel(x.toInt(), y.toInt() + 1))
|
|
|
+
|
|
|
+ const edgeValue = Math.abs(center - right) + Math.abs(center - bottom)
|
|
|
+
|
|
|
+ if (edgeValue > 10) {
|
|
|
+ edgeStrength += edgeValue
|
|
|
+ sampleCount++
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const avgEdgeStrength = sampleCount > 0 ? edgeStrength / sampleCount : 0
|
|
|
+
|
|
|
+ if (!scaledBitmap.isRecycled()) {
|
|
|
+ scaledBitmap.recycle()
|
|
|
+ }
|
|
|
+
|
|
|
+ return avgEdgeStrength
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ console.error('超快速清晰度计算失败:', error)
|
|
|
+ return -1
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 获取像素亮度
|
|
|
*/
|
|
|
@@ -241,7 +587,7 @@ export class BitmapUtils {
|
|
|
* 判断图像是否清晰
|
|
|
*/
|
|
|
static isImageClear(bitmap: Bitmap, threshold: number = 500): boolean {
|
|
|
- const variance = this.calculateBrightnessVariance2(bitmap)
|
|
|
+ const variance = this.calculateOptimizedClarity(bitmap)
|
|
|
return variance > threshold
|
|
|
}
|
|
|
|
|
|
@@ -249,18 +595,18 @@ export class BitmapUtils {
|
|
|
* 获取图像清晰度评分
|
|
|
*/
|
|
|
static getImageClarityScore(bitmap: Bitmap): ImageClarityScore {
|
|
|
- const variance = this.calculateBrightnessVariance2(bitmap)
|
|
|
+ const variance = this.calculateOptimizedClarity(bitmap)
|
|
|
|
|
|
let level: string
|
|
|
if (variance < 0) {
|
|
|
level = "处理失败"
|
|
|
- } else if (variance < 500) {
|
|
|
+ } else if (variance < 15) {
|
|
|
level = "非常模糊"
|
|
|
- } else if (variance < 2000) {
|
|
|
+ } else if (variance < 35) {
|
|
|
level = "模糊"
|
|
|
- } else if (variance < 3200) {
|
|
|
+ } else if (variance < 45) {
|
|
|
level = "一般"
|
|
|
- } else if (variance < 3500) {
|
|
|
+ } else if (variance < 60) {
|
|
|
level = "清晰"
|
|
|
} else {
|
|
|
level = "非常清晰"
|