| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734 |
- // 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
- }
- }
- }
|