// nativeplugins/opencv/android/uts/BitmapUtils.uts import Bitmap from 'android.graphics.Bitmap' import Base64 from 'android.util.Base64' import BitmapFactory from 'android.graphics.BitmapFactory' // 定义类型接口 type ImageClarityScore = { score: number level: string } type ImageClarityResult = { score: number level: string isClear: boolean } /** * 图像清晰度检测工具类 */ export class BitmapUtils { /** * 计算图像的亮度方差作为清晰度指标 */ static calculateBrightnessVariance(bitmap: Bitmap): number { try { const width = bitmap.getWidth() const height = bitmap.getHeight() // 根据原始尺寸动态计算采样尺寸 // 目标采样区域的总像素数,控制在合理范围内 const targetPixelCount = 2500 // 50x50 = 2500像素 // 计算合适的采样尺寸,保持宽高比 let sampleWidth: number let sampleHeight: number if (width > height) { // 宽图 sampleWidth = Math.min(width, Math.sqrt(targetPixelCount * width / height).toInt()) sampleHeight = Math.min(height, (sampleWidth * height / width).toInt()) } else { // 高图或方图 sampleHeight = Math.min(height, Math.sqrt(targetPixelCount * height / width).toInt()) sampleWidth = Math.min(width, (sampleHeight * width / height).toInt()) } // 确保最小尺寸 sampleWidth = Math.max(sampleWidth, 32) sampleHeight = Math.max(sampleHeight, 32) const scaledBitmap = Bitmap.createScaledBitmap( bitmap, sampleWidth.toInt(), sampleHeight.toInt(), false ) let totalBrightness = 0.0 let brightnessSquares = 0.0 const pixelCount = sampleWidth * sampleHeight // 计算平均亮度 for (let i: number = 0; i < sampleWidth; i++) { for (let j: number = 0; j < sampleHeight; j++) { const pixel = scaledBitmap.getPixel(i.toInt(), j.toInt()) const brightness = this.getPixelBrightness(pixel) totalBrightness += brightness } } const meanBrightness = totalBrightness / pixelCount // 计算亮度方差 for (let i: number = 0; i < sampleWidth; i++) { for (let j: number = 0; j < sampleHeight; j++) { const pixel = scaledBitmap.getPixel(i.toInt(), j.toInt()) const brightness = this.getPixelBrightness(pixel) const diff = brightness - meanBrightness brightnessSquares += diff * diff } } const variance = brightnessSquares / pixelCount // 回收临时Bitmap if (!scaledBitmap.isRecycled()) { scaledBitmap.recycle() } return variance } catch (error) { console.error('清晰度计算失败:', error) return -1 } } // 在calculateBrightnessVariance方法中使用多尺度采样 static calculateBrightnessVariance2(bitmap: Bitmap): number { try { const width = bitmap.getWidth() const height = bitmap.getHeight() // 对2048×1536的图片,使用多个采样尺度 const scales = [ { width: 200, height: 150 }, // 约10%尺度 { width: 100, height: 75 }, // 约5%尺度 { width: 50, height: 38 } // 约2.5%尺度 ] let totalVariance = 0.0 let scaleCount = 0 for (const scale of scales) { const sampleWidth = Math.min(width, scale.width as number) const sampleHeight = Math.min(height, scale.height as number) if (sampleWidth < 32 || sampleHeight < 32) continue const scaledBitmap = Bitmap.createScaledBitmap( bitmap, sampleWidth.toInt(), sampleHeight.toInt(), false ) const variance = this.calculateVarianceForBitmap(scaledBitmap) totalVariance += variance scaleCount++ if (!scaledBitmap.isRecycled()) { scaledBitmap.recycle() } } return scaleCount > 0 ? totalVariance / scaleCount : -1 } catch (error) { console.error('多尺度清晰度计算失败:', error) return -1 } } // 提取方差计算为独立方法 private static calculateVarianceForBitmap(bitmap: Bitmap): number { const width = bitmap.getWidth() const height = bitmap.getHeight() let totalBrightness = 0.0 let brightnessSquares = 0.0 const pixelCount = width * height // 计算平均亮度 for (let i: number = 0; i < width; i++) { for (let j: number = 0; j < height; j++) { const pixel = bitmap.getPixel(i.toInt(), j.toInt()) const brightness = this.getPixelBrightness(pixel) totalBrightness += brightness } } const meanBrightness = totalBrightness / pixelCount // 计算亮度方差 for (let i: number = 0; i < width; i++) { for (let j: number = 0; j < height; j++) { const pixel = bitmap.getPixel(i.toInt(), j.toInt()) const brightness = this.getPixelBrightness(pixel) const diff = brightness - meanBrightness brightnessSquares += diff * diff } } return brightnessSquares / pixelCount } /** * 基于边缘梯度的清晰度检测(对模糊更敏感) */ static calculateEdgeGradientClarity(bitmap: Bitmap): number { try { const width = bitmap.getWidth() const height = bitmap.getHeight() // 取中心区域 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( centerBitmap, sampleWidth.toInt(), sampleHeight.toInt(), false ) 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 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)) // 拉普拉斯值 = 4*中心 - 上下左右 const laplacianValue = Math.abs(4 * center - left - right - top - bottom) if (laplacianValue > 5) { // 忽略微小变化 laplacianSum += laplacianValue pixelCount++ } } } const avgLaplacian = pixelCount > 0 ? laplacianSum / pixelCount : 0 // 回收Bitmap if (!scaledBitmap.isRecycled()) { scaledBitmap.recycle() } if (!centerBitmap.isRecycled()) { centerBitmap.recycle() } return avgLaplacian } catch (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 } } /** * 获取像素亮度 */ private static getPixelBrightness(pixel: number): number { const r = (pixel >> 16) & 0xff const g = (pixel >> 8) & 0xff const b = pixel & 0xff return (r + g + b) / 3.0 } /** * 判断图像是否清晰 */ static isImageClear(bitmap: Bitmap, threshold: number = 500): boolean { const variance = this.calculateOptimizedClarity(bitmap) return variance > threshold } /** * 获取图像清晰度评分 */ static getImageClarityScore(bitmap: Bitmap): ImageClarityScore { const variance = this.calculateOptimizedClarity(bitmap) let level: string if (variance < 0) { level = "处理失败" } else if (variance < 15) { level = "非常模糊" } else if (variance < 35) { level = "模糊" } else if (variance < 45) { level = "一般" } else if (variance < 60) { level = "清晰" } else { level = "非常清晰" } return { score: Math.round(variance * 100) / 100, level: level } } /** * 从Base64数据检测清晰度 */ static checkImageClarityFromBase64(base64Data: string): ImageClarityResult { try { const bitmap = this.base64ToBitmap(base64Data) if (bitmap == null) { return { score: -1, level: "加载失败", isClear: false } } const clarityInfo = this.getImageClarityScore(bitmap) const isClear = this.isImageClear(bitmap) if (!bitmap.isRecycled()) { bitmap.recycle() } return { score: clarityInfo.score, level: clarityInfo.level, isClear: isClear } } catch (error) { console.error('图像清晰度检测失败:', error) return { score: -1, level: "检测失败", isClear: false } } } /** * 从文件路径检测清晰度 */ static checkImageClarityFromPath(imagePath: string): ImageClarityResult { try { const bitmap = BitmapFactory.decodeFile(imagePath) if (bitmap == null) { return { score: -1, level: "加载失败", isClear: false } } const clarityInfo = this.getImageClarityScore(bitmap) const isClear = this.isImageClear(bitmap) if (!bitmap.isRecycled()) { bitmap.recycle() } return { score: clarityInfo.score, level: clarityInfo.level, isClear: isClear } } catch (error) { console.error('图像清晰度检测失败:', error) return { score: -1, level: "检测失败", isClear: false } } } /** * 简化的Base64转Bitmap方法 */ private static base64ToBitmap(base64Data: string): Bitmap | null { try { // 移除Base64前缀 let pureBase64 = base64Data if (base64Data.includes(',')) { pureBase64 = base64Data.split(',')[1] } // 解码Base64 const decodedBytes = Base64.decode(pureBase64, Base64.DEFAULT) // 获取字节数组长度 const bitmap = BitmapFactory.decodeByteArray(decodedBytes, 0, decodedBytes.size) return bitmap } catch (error) { console.error('Base64转Bitmap失败:', error) return null } } /** * 备用方法:直接返回清晰度分数(避免复杂对象类型) */ static getSimpleClarityScore(bitmap: Bitmap): number { return this.calculateBrightnessVariance(bitmap) } /** * 从Base64获取简单清晰度分数 */ static getSimpleClarityScoreFromBase64(base64Data: string): number { try { const bitmap = this.base64ToBitmap(base64Data) if (bitmap == null) { return -1 } const score = this.calculateBrightnessVariance(bitmap) if (!bitmap.isRecycled()) { bitmap.recycle() } return score } catch (error) { console.error('清晰度检测失败:', error) return -1 } } }