/ocr/ocrservice/src/com/googlecode/eyesfree/ocr/intent/NoisyDetector.java
Java | 200 lines | 116 code | 47 blank | 37 comment | 28 complexity | 1910c47b65469071f7dcc2bc4f8663c9 MD5 | raw file
1/* 2 * Copyright (C) 2011 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy of 6 * the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 17package com.googlecode.eyesfree.ocr.intent; 18 19import android.content.Context; 20import android.graphics.RectF; 21import android.media.AudioManager; 22import android.media.SoundPool; 23 24import com.googlecode.eyesfree.env.Metronome; 25import com.googlecode.eyesfree.ocr.R; 26 27import java.util.ArrayList; 28 29/** 30 * @author alanv@google.com (Alan Viverette) 31 */ 32public class NoisyDetector { 33 /* Minimum height of a text item in pixels */ 34 private static final int MIN_HEIGHT = 30; 35 36 /* Percent error allowed for height threshold */ 37 private static final float HEIGHT_ERROR = 0.4f; 38 39 private boolean mPaused; 40 41 private Metronome mMetronome; 42 43 private SoundPool mSoundPool; 44 45 private int mBeepId; 46 47 public NoisyDetector(Context context) { 48 mPaused = false; 49 50 mSoundPool = new SoundPool(1, AudioManager.STREAM_MUSIC, 0); 51 mBeepId = mSoundPool.load(context, R.raw.loud_beep, 1); 52 53 mMetronome = new Metronome(context, R.raw.click); 54 } 55 56 public void start() { 57 mPaused = false; 58 59 mMetronome.start(); 60 } 61 62 public void pause() { 63 mPaused = true; 64 65 mMetronome.stop(); 66 } 67 68 public void onTextDetected(RectF pixaBound, ArrayList<RectF> pixBounds) { 69 if (mPaused) 70 return; 71 72 // Compute whether this is likely a text block 73 int likelyText = 0; 74 int likelySmallText = 0; 75 76 for (int i = 0; i < pixBounds.size() && likelyText < 4; i++) { 77 if (pixBounds.get(i) == null) 78 break; 79 80 if (BoundsClassifier.isLikelyTextRect(pixBounds, i)) { 81 // Lines must be at least MIN_HEIGHT pixels high 82 if (pixBounds.get(i).height() < MIN_HEIGHT) { 83 // Allow a tolerance of HEIGHT_ERROR percent around 84 // MIN_HEIGHT 85 if (BoundsClassifier.computeError(pixBounds.get(i).height(), MIN_HEIGHT) 86 < HEIGHT_ERROR) { 87 likelyText++; 88 } 89 likelySmallText++; 90 } else { 91 likelyText++; 92 } 93 } 94 } 95 96 if (mPaused) 97 return; 98 99 if (pixBounds.size() > 0) { 100 float pixaArea = pixaBound.width() * pixaBound.height(); 101 float pixaCenterX = pixaBound.centerX(); 102 103 float textAreaL = 0.0f; 104 float textAreaR = 0.0f; 105 106 for (RectF textBounds : pixBounds) { 107 float textArea = textBounds.width() * textBounds.height(); 108 float textCenterX = textBounds.centerX(); 109 110 float offsetRatio = (pixaCenterX - textCenterX) / textBounds.width(); 111 float leftPortion = Math.min(1.0f, Math.max(0.0f, 0.5f - offsetRatio)); 112 float rightPortion = 1.0f - leftPortion; 113 114 textAreaL += leftPortion * textArea; 115 textAreaR += rightPortion * textArea; 116 } 117 118 float volumeL = Math.min(1.0f, 5.0f * textAreaL / pixaArea); 119 float volumeR = Math.min(1.0f, 5.0f * textAreaR / pixaArea); 120 long delay = (long) (300 / Math.log(1 + pixBounds.size())); 121 122 // Make clicking noises according to how much area is text 123 mMetronome.setVolume(volumeL, volumeR); 124 mMetronome.setDelay(delay); 125 } else { 126 mMetronome.setDelay(3000); 127 } 128 129 if (mPaused) 130 return; 131 132 // Vibrate if we're looking at text 133 if (likelySmallText > likelyText && likelySmallText > 4) { 134 mSoundPool.play(mBeepId, 0.3f, 0.3f, 1, 0, 1.0f); 135 } else if (likelyText >= 4) { 136 mSoundPool.play(mBeepId, 1.0f, 1.0f, 1, 0, 1.0f); 137 } 138 } 139 140 /** 141 * @author alanv@google.com (Alan Viverette) 142 */ 143 private static class BoundsClassifier { 144 /** Minimum number of matching lines. */ 145 private static final int MIN_PAIRS = 1; 146 147 /** Minimum width/height ratio. */ 148 private static final int MIN_WRATIO = 10; 149 150 /** Top offset in height units. */ 151 private static final float TOP_ERROR = 2.0f; 152 153 /** Left offset in height units. */ 154 private static final float LEFT_ERROR = 1.0f; 155 156 157 public static boolean isLikelyTextRect(ArrayList<RectF> results, int position) { 158 RectF bounds = results.get(position); 159 160 // Lines must be at least MIN_WRATIO times as wide as they are tall 161 if (bounds.width() / bounds.height() < MIN_WRATIO) { 162 return false; 163 } 164 165 int pairs = 0; 166 167 for (int i = 0; i < results.size() && pairs < MIN_PAIRS; i++) { 168 if (i == position) 169 continue; 170 if (results.get(i) == null) 171 break; 172 173 RectF otherBounds = results.get(i); 174 175 // Pairs must be within HEIGHT_ERROR similar height 176 float hError = computeError(bounds.height(), otherBounds.height()); 177 if (hError > HEIGHT_ERROR) 178 continue; 179 180 // Pairs must have similar left margins within LEFT_ERROR heights 181 float xDist = Math.abs(bounds.left - otherBounds.left); 182 if (xDist > LEFT_ERROR * bounds.height()) 183 continue; 184 185 // Pairs must be within SPACING_ERROR heights apart 186 float yDist = Math.abs(bounds.top - otherBounds.top); 187 if (yDist > TOP_ERROR * bounds.height()) 188 continue; 189 190 pairs++; 191 } 192 193 return pairs >= MIN_PAIRS; 194 } 195 196 public static float computeError(float expected, float experimental) { 197 return Math.abs((experimental - expected) / expected); 198 } 199 } 200}