PageRenderTime 52ms CodeModel.GetById 12ms app.highlight 34ms RepoModel.GetById 1ms app.codeStats 0ms

/ocr/ocrservice/src/com/googlecode/eyesfree/ocr/intent/NoisyDetector.java

http://eyes-free.googlecode.com/
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}