Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(184)

Unified Diff: tools/telemetry/telemetry/internal/image_processing/screen_finder.py

Issue 1647513002: Delete tools/telemetry. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 4 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: tools/telemetry/telemetry/internal/image_processing/screen_finder.py
diff --git a/tools/telemetry/telemetry/internal/image_processing/screen_finder.py b/tools/telemetry/telemetry/internal/image_processing/screen_finder.py
deleted file mode 100755
index 932d6dfe4fa3104b963c7b5a7ec61cb103fb0633..0000000000000000000000000000000000000000
--- a/tools/telemetry/telemetry/internal/image_processing/screen_finder.py
+++ /dev/null
@@ -1,857 +0,0 @@
-#!/usr/bin/env python
-# Copyright 2014 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-#
-# This script attempts to detect the region of a camera's field of view that
-# contains the screen of the device we are testing.
-#
-# Usage: ./screen_finder.py path_to_video 0 0 --verbose
-
-from __future__ import division
-
-import copy
-import logging
-import os
-import sys
-
-if __name__ == '__main__':
- sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..'))
-
-from telemetry.internal.image_processing import cv_util
-from telemetry.internal.image_processing import frame_generator as \
- frame_generator_module
-from telemetry.internal.image_processing import video_file_frame_generator
-from telemetry.internal.util import external_modules
-
-np = external_modules.ImportRequiredModule('numpy')
-cv2 = external_modules.ImportRequiredModule('cv2')
-
-
-class ScreenFinder(object):
- """Finds and extracts device screens from video.
-
- Sample Usage:
- sf = ScreenFinder(sys.argv[1])
- while sf.HasNext():
- ret, screen = sf.GetNext()
-
- Attributes:
- _lost_corners: Each index represents whether or not we lost track of that
- corner on the previous frame. Ordered by [top-right, top-left,
- bottom-left, bottom-right]
- _frame: An unmodified copy of the frame we're currently processing.
- _frame_debug: A copy of the frame we're currently processing, may be
- modified at any time, used for debugging.
- _frame_grey: A greyscale copy of the frame we're currently processing.
- _frame_edges: A Canny Edge detected copy of the frame we're currently
- processing.
- _screen_size: The size of device screen in the video when first detected.
- _avg_corners: Exponentially weighted average of the previous corner
- locations.
- _prev_corners: The location of the corners in the previous frame.
- _lost_corner_frames: A counter of the number of successive frames in which
- we've lost a corner location.
- _border: See |border| above.
- _min_line_length: The minimum length a line must be before we consider it
- a possible screen edge.
- _frame_generator: See |frame_generator| above.
- _width, _height: The width and height of the frame.
- _anglesp5, _anglesm5: The angles for each point we look at in the grid
- when computing brightness, constant across frames."""
-
- class ScreenNotFoundError(Exception):
- pass
-
- # Square of the distance a corner can travel in pixels between frames
- MAX_INTERFRAME_MOTION = 25
- # The minimum width line that may be considered a screen edge.
- MIN_SCREEN_WIDTH = 40
- # Number of frames with lost corners before we ignore MAX_INTERFRAME_MOTION
- RESET_AFTER_N_BAD_FRAMES = 2
- # The weight applied to the new screen location when exponentially averaging
- # screen location.
- # TODO(mthiesse): This should be framerate dependent, for lower framerates
- # this value should approach 1. For higher framerates, this value should
- # approach 0. The current 0.5 value works well in testing with 240 FPS.
- CORNER_AVERAGE_WEIGHT = 0.5
-
- # TODO(mthiesse): Investigate how to select the constants used here. In very
- # bright videos, twice as bright may be too high, and the minimum of 60 may
- # be too low.
- # The factor by which a quadrant at an intersection must be brighter than
- # the other quadrants to be considered a screen corner.
- MIN_RELATIVE_BRIGHTNESS_FACTOR = 1.5
- # The minimum average brightness required of an intersection quadrant to
- # be considered a screen corner (on a scale of 0-255).
- MIN_CORNER_ABSOLUTE_BRIGHTNESS = 60
-
- # Low and high hysteresis parameters to be passed to the Canny edge
- # detection algorithm.
- CANNY_HYSTERESIS_THRESH_LOW = 300
- CANNY_HYSTERESIS_THRESH_HIGH = 500
-
- SMALL_ANGLE = 5 / 180 * np.pi # 5 degrees in radians
-
- DEBUG = False
-
- def __init__(self, frame_generator, border=5):
- """Initializes the ScreenFinder object.
-
- Args:
- frame_generator: FrameGenerator, An initialized Video Frame Generator.
- border: int, number of pixels of border to be kept when cropping the
- detected screen.
-
- Raises:
- FrameReadError: The frame generator may output a read error during
- initialization."""
- assert isinstance(frame_generator, frame_generator_module.FrameGenerator)
- self._lost_corners = [False, False, False, False]
- self._frame_debug = None
- self._frame = None
- self._frame_grey = None
- self._frame_edges = None
- self._screen_size = None
- self._avg_corners = None
- self._prev_corners = None
- self._lost_corner_frames = 0
- self._border = border
- self._min_line_length = self.MIN_SCREEN_WIDTH
- self._frame_generator = frame_generator
- self._anglesp5 = None
- self._anglesm5 = None
-
- if not self._InitNextFrame():
- logging.warn('Not enough frames in video feed!')
- return
-
- self._height, self._width = self._frame.shape[:2]
-
- def _InitNextFrame(self):
- """Called after processing each frame, reads in the next frame to ensure
- HasNext() is accurate."""
- self._frame_debug = None
- self._frame = None
- self._frame_grey = None
- self._frame_edges = None
- try:
- frame = next(self._frame_generator.Generator)
- except StopIteration:
- return False
- self._frame = frame
- self._frame_debug = copy.copy(frame)
- self._frame_grey = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
- self._frame_edges = cv2.Canny(self._frame_grey,
- self.CANNY_HYSTERESIS_THRESH_LOW,
- self.CANNY_HYSTERESIS_THRESH_HIGH)
- return True
-
- def HasNext(self):
- """True if there are more frames available to process. """
- return self._frame is not None
-
- def GetNext(self):
- """Gets the next screen image.
-
- Returns:
- A numpy matrix containing the screen surrounded by the number of border
- pixels specified in initialization, and the location of the detected
- screen corners in the current frame, if a screen is found. The returned
- screen is guaranteed to be the same size at each frame.
- 'None' and 'None' if no screen was found on the current frame.
-
- Raises:
- FrameReadError: An error occurred in the FrameGenerator.
- RuntimeError: This method was called when no frames were available."""
- if self._frame is None:
- raise RuntimeError('No more frames available.')
-
- logging.info('Processing frame: %d',
- self._frame_generator.CurrentFrameNumber)
-
- # Finds straight lines in the image.
- hlines = cv2.HoughLinesP(self._frame_edges, 1, np.pi / 180, 60,
- minLineLength=self._min_line_length,
- maxLineGap=100)
-
- # Extends these straight lines to be long enough to ensure the screen edge
- # lines intersect.
- lines = cv_util.ExtendLines(np.float32(hlines[0]), 10000) \
- if hlines is not None else []
-
- # Find intersections in the lines; these are likely to be screen corners.
- intersections = self._FindIntersections(lines)
- if len(intersections[:, 0]) > 0:
- points = np.vstack(intersections[:, 0].flat)
- if (self._prev_corners is not None and len(points) >= 4 and
- not self._HasMovedFast(points, self._prev_corners)):
- corners = self._prev_corners
- missing_corners = 0
- else:
- # Extract the corners from all intersections.
- corners, missing_corners = self._FindCorners(
- intersections, self._frame_grey)
- else:
- corners = np.empty((4, 2), np.float32)
- corners[:] = np.nan
- missing_corners = 4
-
- screen = None
- found_screen = True
- final_corners = None
- try:
- # Handle the cases where we have missing corners.
- screen_corners = self._NewScreenLocation(
- corners, missing_corners, intersections)
-
- final_corners = self._SmoothCorners(screen_corners)
-
- # Create a perspective transform from our corners.
- transform, w, h = self._GetTransform(final_corners, self._border)
-
- # Apply the perspective transform to get our output.
- screen = cv2.warpPerspective(
- self._frame, transform, (int(w + 0.5), int(h + 0.5)))
-
- self._prev_corners = final_corners
-
- except self.ScreenNotFoundError as e:
- found_screen = False
- logging.info(e)
-
- if self.DEBUG:
- self._Debug(lines, corners, final_corners, screen)
-
- self._InitNextFrame()
- if found_screen:
- return screen, self._prev_corners
- return None, None
-
- def _FindIntersections(self, lines):
- """Finds intersections in a set of lines.
-
- Filters pairs of lines that are less than 45 degrees apart. Filtering
- these pairs helps dramatically reduce the number of points we have to
- process, as these points could not represent screen corners anyways.
-
- Returns:
- The intersections, represented as a tuple of (point, line, line) of the
- points and the lines that intersect there of all lines in the array that
- are more than 45 degrees apart."""
- intersections = np.empty((0, 3), np.float32)
- for i in xrange(0, len(lines)):
- for j in xrange(i + 1, len(lines)):
- # Filter lines that are less than 45 (or greater than 135) degrees
- # apart.
- if not cv_util.AreLinesOrthogonal(lines[i], lines[j], (np.pi / 4.0)):
- continue
- ret, point = cv_util.FindLineIntersection(lines[i], lines[j])
- point = np.float32(point)
- if not ret:
- continue
- # If we know where the previous corners are, we can also filter
- # intersections that are too far away from the previous corners to be
- # where the screen has moved.
- if self._prev_corners is not None and \
- self._lost_corner_frames <= self.RESET_AFTER_N_BAD_FRAMES and \
- not self._PointIsCloseToPreviousCorners(point):
- continue
- intersections = np.vstack((intersections,
- np.array((point, lines[i], lines[j]))))
- return intersections
-
- def _PointIsCloseToPreviousCorners(self, point):
- """True if the point is close to the previous corners."""
- max_dist = self.MAX_INTERFRAME_MOTION
- if cv_util.SqDistance(self._prev_corners[0], point) <= max_dist or \
- cv_util.SqDistance(self._prev_corners[1], point) <= max_dist or \
- cv_util.SqDistance(self._prev_corners[2], point) <= max_dist or \
- cv_util.SqDistance(self._prev_corners[3], point) <= max_dist:
- return True
- return False
-
- def _HasMovedFast(self, corners, prev_corners):
- min_dist = np.zeros(4, np.float32)
- for i in xrange(4):
- dist = np.min(cv_util.SqDistances(corners, prev_corners[i]))
- min_dist[i] = dist
- # 3 corners can move up to one pixel before we consider the screen to have
- # moved. TODO(mthiesse): Should this be relaxed? Resolution dependent?
- if np.sum(min_dist) < 3:
- return False
- return True
-
- class CornerData(object):
-
- def __init__(self, corner_index, corner_location, brightness_score, line1,
- line2):
- self.corner_index = corner_index
- self.corner_location = corner_location
- self.brightness_score = brightness_score
- self.line1 = line1
- self.line2 = line2
-
- def __gt__(self, corner_data2):
- return self.corner_index > corner_data2.corner_index
-
- def __repr__(self):
- return ('\nCorner index: ' + str(self.corner_index) +
- ',\nCorner location: ' + str(self.corner_location) +
- ',\nBrightness score: ' + str(self.brightness_score) +
- ',\nline1: ' + str(self.line1) + ',\nline2: ' + str(self.line2))
-
- def _FindCorners(self, intersections, grey_frame):
- """Finds the screen corners in the image.
-
- Given the set of intersections in the image, finds the intersections most
- likely to be corners.
-
- Args:
- intersections: The array of intersections in the image.
- grey_frame: The greyscale frame we're processing.
-
- Returns:
- An array of length 4 containing the positions of the corners, or nan for
- each index where a corner could not be found, and a count of the number
- of missing corners.
- The corners are ordered as follows:
- 1 | 0
- -----
- 2 | 3
- Ex. 3 corners are found from a square of width 2 centered at the origin,
- the output would look like:
- '[[1, 1], [np.nan, np.nan], [-1, -1], [1, -1]], 1'"""
- filtered = []
- corners = np.empty((0, 2), np.float32)
- for corner_pos, score, point, line1, line2 in \
- self._LooksLikeCorner(intersections, grey_frame):
- if self.DEBUG:
- center = (int(point[0] + 0.5), int(point[1] + 0.5))
- cv2.circle(self._frame_debug, center, 5, (0, 255, 0), 1)
- point.resize(1, 2)
- corners = np.append(corners, point, axis=0)
- point.resize(2,)
- corner_data = self.CornerData(corner_pos, point, score, line1, line2)
- filtered.append(corner_data)
-
- # De-duplicate corners because we may have found many false positives, or
- # near-misses.
- self._DeDupCorners(filtered, corners)
-
- # Strip everything but the corner location.
- filtered_corners = np.array(
- [corner_data.corner_location for corner_data in filtered])
- corner_indices = [corner_data.corner_index for corner_data in filtered]
-
- # If we have found a corner to replace a lost corner, we want to check
- # that the corner is not erroneous by ensuring it makes a rectangle with
- # the 3 known good corners.
- if len(filtered) == 4:
- for i in xrange(4):
- point_info = (filtered[i].corner_location,
- filtered[i].line1,
- filtered[i].line2)
- if (self._lost_corners[i] and
- not self._PointConnectsToCorners(filtered_corners, point_info)):
- filtered_corners = np.delete(filtered_corners, i, 0)
- corner_indices = np.delete(corner_indices, i, 0)
- break
-
- # Ensure corners are sorted properly, inserting nans for missing corners.
- sorted_corners = np.empty((4, 2), np.float32)
- sorted_corners[:] = np.nan
- for i in xrange(len(filtered_corners)):
- sorted_corners[corner_indices[i]] = filtered_corners[i]
-
- # From this point on, our corners arrays are guaranteed to have 4
- # elements, though some may be nan.
-
- # Filter corners that have moved too far from the previous corner if we
- # are not resetting known corner information.
- reset_corners = (
- (self._lost_corner_frames > self.RESET_AFTER_N_BAD_FRAMES)
- and len(filtered_corners) == 4)
- if self._prev_corners is not None and not reset_corners:
- sqdists = cv_util.SqDistances(self._prev_corners, sorted_corners)
- for i in xrange(4):
- if np.isnan(sorted_corners[i][0]):
- continue
- if sqdists[i] > self.MAX_INTERFRAME_MOTION:
- sorted_corners[i] = np.nan
-
- real_corners = self._FindExactCorners(sorted_corners)
- missing_corners = np.count_nonzero(np.isnan(real_corners)) / 2
- return real_corners, missing_corners
-
- def _LooksLikeCorner(self, intersections, grey_frame):
- """Finds any intersections of lines that look like a screen corner.
-
- Args:
- intersections: The numpy array of points, and the lines that intersect
- at the given point.
- grey_frame: The greyscale frame we're processing.
-
- Returns:
- An array of: The corner location (0-3), the relative brightness score
- (to be used to de-duplicate corners later), the point, and the lines
- that make up the intersection, for all intersections that look like a
- corner."""
- points = np.vstack(intersections[:, 0].flat)
- lines1 = np.vstack(intersections[:, 1].flat)
- lines2 = np.vstack(intersections[:, 2].flat)
- # Map the image to four quadrants defined as the regions between each of
- # the lines that make up the intersection.
- line1a1 = np.pi - np.arctan2(lines1[:, 1] - points[:, 1],
- lines1[:, 0] - points[:, 0])
- line1a2 = np.pi - np.arctan2(lines1[:, 3] - points[:, 1],
- lines1[:, 2] - points[:, 0])
- line2a1 = np.pi - np.arctan2(lines2[:, 1] - points[:, 1],
- lines2[:, 0] - points[:, 0])
- line2a2 = np.pi - np.arctan2(lines2[:, 3] - points[:, 1],
- lines2[:, 2] - points[:, 0])
- line1a1 = line1a1.reshape(-1, 1)
- line1a2 = line1a2.reshape(-1, 1)
- line2a1 = line2a1.reshape(-1, 1)
- line2a2 = line2a2.reshape(-1, 1)
-
- line_angles = np.concatenate((line1a1, line1a2, line2a1, line2a2), axis=1)
- np.ndarray.sort(line_angles)
-
- # TODO(mthiesse): Investigate whether these should scale with image or
- # screen size. My intuition is that these don't scale with image size,
- # though they may be affected by image quality and how blurry the corners
- # are. See stackoverflow.com/q/7765810/ for inspiration.
- avg_range = 8.0
- num_points = 7
-
- points_m_avg = points - avg_range
- points_p_avg = points + avg_range
- # Exclude points near frame boundaries.
- include = np.where((points_m_avg[:, 0] > 0) & (points_m_avg[:, 1] > 0) &
- (points_p_avg[:, 0] < self._width) &
- (points_p_avg[:, 1] < self._height))
- line_angles = line_angles[include]
- points = points[include]
- lines1 = lines1[include]
- lines2 = lines2[include]
- points_m_avg = points_m_avg[include]
- points_p_avg = points_p_avg[include]
- # Perform a 2-d linspace to generate the x, y ranges for each
- # intersection.
- arr1 = points_m_avg[:, 0].reshape(-1, 1)
- arr2 = points_p_avg[:, 0].reshape(-1, 1)
- lin = np.linspace(0, 1, num_points)
- x_range = arr1 + (arr2 - arr1) * lin
- arr1 = points_m_avg[:, 1].reshape(-1, 1)
- arr2 = points_p_avg[:, 1].reshape(-1, 1)
- y_range = arr1 + (arr2 - arr1) * lin
-
- # The angles for each point we look at in the grid when computing
- # brightness are constant across frames, so we can generate them once.
- if self._anglesp5 is None:
- ind = np.transpose([np.tile(x_range[0], num_points),
- np.repeat(y_range[0], num_points)])
- vectors = ind - points[0]
- angles = np.arctan2(vectors[:, 1], vectors[:, 0]) + np.pi
- self._anglesp5 = angles + self.SMALL_ANGLE
- self._anglesm5 = angles - self.SMALL_ANGLE
- results = []
- for i in xrange(len(y_range)):
- # Generate our filters for which points belong to which quadrant.
- one = np.where((self._anglesp5 <= line_angles[i, 1]) &
- (self._anglesm5 >= line_angles[i, 0]))
- two = np.where((self._anglesp5 <= line_angles[i, 2]) &
- (self._anglesm5 >= line_angles[i, 1]))
- thr = np.where((self._anglesp5 <= line_angles[i, 3]) &
- (self._anglesm5 >= line_angles[i, 2]))
- fou = np.where((self._anglesp5 <= line_angles[i, 0]) |
- (self._anglesm5 >= line_angles[i, 3]))
- # Take the cartesian product of our x and y ranges to get the full list
- # of pixels to look at.
- ind = np.transpose([np.tile(x_range[i], num_points),
- np.repeat(y_range[i], num_points)])
-
- # Filter the full list by which indices belong to which quadrant, and
- # convert to integers so we can index with them.
- one_i = np.int32(np.rint(ind[one[0]]))
- two_i = np.int32(np.rint(ind[two[0]]))
- thr_i = np.int32(np.rint(ind[thr[0]]))
- fou_i = np.int32(np.rint(ind[fou[0]]))
-
- # Average the brightness of the pixels that belong to each quadrant.
- q_1 = np.average(grey_frame[one_i[:, 1], one_i[:, 0]])
- q_2 = np.average(grey_frame[two_i[:, 1], two_i[:, 0]])
- q_3 = np.average(grey_frame[thr_i[:, 1], thr_i[:, 0]])
- q_4 = np.average(grey_frame[fou_i[:, 1], fou_i[:, 0]])
-
- avg_intensity = [(q_4, 0), (q_1, 1), (q_2, 2), (q_3, 3)]
- # Sort by intensity.
- avg_intensity.sort(reverse=True)
-
- # Treat the point as a corner if one quadrant is at least twice as
- # bright as the next brightest quadrant, with a minimum brightness
- # requirement.
- tau = (2.0 * np.pi)
- min_factor = self.MIN_RELATIVE_BRIGHTNESS_FACTOR
- min_brightness = self.MIN_RELATIVE_BRIGHTNESS_FACTOR
- if avg_intensity[0][0] > avg_intensity[1][0] * min_factor and \
- avg_intensity[0][0] > min_brightness:
- bright_corner = avg_intensity[0][1]
- if bright_corner == 0:
- angle = np.pi - (line_angles[i, 0] + line_angles[i, 3]) / 2.0
- if angle < 0:
- angle = angle + tau
- else:
- angle = tau - (line_angles[i, bright_corner] +
- line_angles[i, bright_corner - 1]) / 2.0
- score = avg_intensity[0][0] - avg_intensity[1][0]
- # TODO(mthiesse): int(angle / (pi / 2.0)) will break if the screen is
- # rotated through 45 degrees. Probably many other things will break as
- # well, movement of corners from one quadrant to another hasn't been
- # tested. We should support this eventually, but this is unlikely to
- # cause issues for any test setups.
- results.append((int(angle / (np.pi / 2.0)), score, points[i],
- lines1[i], lines2[i]))
- return results
-
- def _DeDupCorners(self, corner_data, corners):
- """De-duplicate corners based on corner_index.
-
- For each set of points representing a corner: If one point is part of the
- rectangle and the other is not, filter the other one. If both or none are
- part of the rectangle, filter based on score (highest relative brightness
- of a quadrant). The reason we allow for neither to be part of the
- rectangle is because we may not have found all four corners of the
- rectangle, and in degenerate cases like this it's better to find 3 likely
- corners than none.
-
- Modifies corner_data directly.
-
- Args:
- corner_data: CornerData for each potential corner in the frame.
- corners: List of all potential corners in the frame."""
- # TODO(mthiesse): Ensure that the corners form a sensible rectangle. For
- # example, it is currently possible (but unlikely) to detect a 'screen'
- # where the bottom-left corner is above the top-left corner, while the
- # bottom-right corner is below the top-right corner.
-
- # Sort by corner_index to make de-duping easier.
- corner_data.sort()
-
- # De-dup corners.
- c_old = None
- for i in xrange(len(corner_data) - 1, 0, -1):
- if corner_data[i].corner_index != corner_data[i - 1].corner_index:
- c_old = None
- continue
- if c_old is None:
- point_info = (corner_data[i].corner_location,
- corner_data[i].line1,
- corner_data[i].line2)
- c_old = self._PointConnectsToCorners(corners, point_info, 2)
- point_info_new = (corner_data[i - 1].corner_location,
- corner_data[i - 1].line1,
- corner_data[i - 1].line2)
- c_new = self._PointConnectsToCorners(corners, point_info_new, 2)
- if (not (c_old or c_new)) or (c_old and c_new):
- if (corner_data[i].brightness_score <
- corner_data[i - 1].brightness_score):
- del corner_data[i]
- c_old = c_new
- else:
- del corner_data[i - 1]
- elif c_old:
- del corner_data[i - 1]
- else:
- del corner_data[i]
- c_old = c_new
-
- def _PointConnectsToCorners(self, corners, point_info, tolerance=1):
- """Checks if the lines of an intersection intersect with corners.
-
- This is useful to check if the point is part of a rectangle specified by
- |corners|.
-
- Args:
- point_info: A tuple of (point, line, line) representing an intersection
- of two lines.
- corners: corners that (hopefully) make up a rectangle.
- tolerance: The tolerance (approximately in pixels) of the distance
- between the corners and the lines for detecting if the point is on
- the line.
-
- Returns:
- True if each of the two lines that make up the intersection where the
- point is located connect the point to other corners."""
- line1_connected = False
- line2_connected = False
- point, line1, line2 = point_info
- for corner in corners:
- if corner is None:
- continue
-
- # Filter out points that are too close to one another to be different
- # corners.
- sqdist = cv_util.SqDistance(corner, point)
- if sqdist < self.MIN_SCREEN_WIDTH * self.MIN_SCREEN_WIDTH:
- continue
-
- line1_connected = line1_connected or \
- cv_util.IsPointApproxOnLine(corner, line1, tolerance)
- line2_connected = line2_connected or \
- cv_util.IsPointApproxOnLine(corner, line2, tolerance)
- if line1_connected and line2_connected:
- return True
- return False
-
- def _FindExactCorners(self, sorted_corners):
- """Attempts to find more accurate corner locations.
-
- Args:
- sorted_corners: The four screen corners, sorted by corner_index.
-
- Returns:
- A list of 4 probably more accurate corners, still sorted."""
- real_corners = np.empty((4, 2), np.float32)
- # Count missing corners, and search in a small area around our
- # intersections representing corners to see if we can find a more exact
- # corner, as the position of the intersections is noisy and not always
- # perfectly accurate.
- for i in xrange(4):
- corner = sorted_corners[i]
- if np.isnan(corner[0]):
- real_corners[i] = np.nan
- continue
-
- # Almost unbelievably, in edge cases with floating point error, the
- # width/height of the cropped corner image may be 2 or 4. This is fine
- # though, as long as the width and height of the cropped corner are not
- # hard-coded anywhere.
- corner_image = self._frame_edges[corner[1] - 1:corner[1] + 2,
- corner[0] - 1:corner[0] + 2]
- ret, p = self._FindExactCorner(i <= 1, i == 1 or i == 2, corner_image)
- if ret:
- if self.DEBUG:
- self._frame_edges[corner[1] - 1 + p[1]][corner[0] - 1 + p[0]] = 128
- real_corners[i] = corner - 1 + p
- else:
- real_corners[i] = corner
- return real_corners
-
- def _FindExactCorner(self, top, left, img):
- """Tries to finds the exact corner location for a given corner.
-
- Searches for the top or bottom, left or right most lit
- pixel in an edge-detected image, which should represent, with pixel
- precision, as accurate a corner location as possible. (Though perhaps
- up-sampling using cubic spline interpolation could get sub-pixel
- precision)
-
- TODO(mthiesse): This algorithm could be improved by including a larger
- region to search in, but would have to be made smarter about which lit
- pixels are on the detected screen edge and which are a not as it's
- currently extremely easy to fool by things like notification icons in
- screen corners.
-
- Args:
- top: boolean, whether or not we're looking for a top corner.
- left: boolean, whether or not we're looking for a left corner.
- img: A small cropping of the edge detected image in which to search.
-
- Returns:
- True and the location if a better corner location is found,
- False otherwise."""
- h, w = img.shape[:2]
- cy = 0
- starting_x = w - 1 if left else 0
- cx = starting_x
- if top:
- y_range = xrange(h - 1, -1, -1)
- else:
- y_range = xrange(0, h, 1)
- if left:
- x_range = xrange(w - 1, -1, -1)
- else:
- x_range = xrange(0, w, 1)
- for y in y_range:
- for x in x_range:
- if img[y][x] == 255:
- cy = y
- if (left and x <= cx) or (not left and x >= cx):
- cx = x
- if cx == starting_x and cy == 0 and img[0][starting_x] != 255:
- return False, (0, 0)
- return True, (cx, cy)
-
- def _NewScreenLocation(self, new_corners, missing_corners, intersections):
- """Computes the new screen location with best effort.
-
- Creates the final list of corners that represents the best effort attempt
- to find the new screen location. Handles degenerate cases where 3 or fewer
- new corners are present, using previous corner and intersection data.
-
- Args:
- new_corners: The corners found by our search for corners.
- missing_corners: The count of how many corners we're missing.
- intersections: The intersections of straight lines found in the current
- frame.
-
- Returns:
- An array of 4 new_corners hopefully representing the screen, or throws
- an error if this is not possible.
-
- Raises:
- ValueError: Finding the screen location was not possible."""
- screen_corners = copy.copy(new_corners)
- if missing_corners == 0:
- self._lost_corner_frames = 0
- self._lost_corners = [False, False, False, False]
- return screen_corners
- if self._prev_corners is None:
- raise self.ScreenNotFoundError(
- 'Could not locate screen on frame %d' %
- self._frame_generator.CurrentFrameNumber)
-
- self._lost_corner_frames += 1
- if missing_corners > 1:
- logging.info('Unable to properly detect screen corners, making '
- 'potentially false assumptions on frame %d',
- self._frame_generator.CurrentFrameNumber)
- # Replace missing new_corners with either nearest intersection to previous
- # corner, or previous corner if no intersections are found.
- for i in xrange(0, 4):
- if not np.isnan(new_corners[i][0]):
- self._lost_corners[i] = False
- continue
- self._lost_corners[i] = True
- min_dist = self.MAX_INTERFRAME_MOTION
- min_corner = None
-
- for isection in intersections:
- dist = cv_util.SqDistance(isection[0], self._prev_corners[i])
- if dist >= min_dist:
- continue
- if missing_corners == 1:
- # We know in this case that we have 3 corners present, meaning
- # all 4 screen lines, and therefore intersections near screen
- # corners present, so our new corner must connect to these
- # other corners.
- if not self._PointConnectsToCorners(new_corners, isection, 3):
- continue
- min_corner = isection[0]
- min_dist = dist
- screen_corners[i] = min_corner if min_corner is not None else \
- self._prev_corners[i]
-
- return screen_corners
-
- def _SmoothCorners(self, corners):
- """Smoothes the motion of corners, reduces noise.
-
- Smoothes the motion of corners by computing an exponentially weighted
- moving average of corner positions over time.
-
- Args:
- corners: The corners of the detected screen.
-
- Returns:
- The final corner positions."""
- if self._avg_corners is None:
- self._avg_corners = np.asfarray(corners, np.float32)
- for i in xrange(0, 4):
- # Keep an exponential moving average of the corner location to reduce
- # noise.
- new_contrib = np.multiply(self.CORNER_AVERAGE_WEIGHT, corners[i])
- old_contrib = np.multiply(1 - self.CORNER_AVERAGE_WEIGHT,
- self._avg_corners[i])
- self._avg_corners[i] = np.add(new_contrib, old_contrib)
-
- return self._avg_corners
-
- def _GetTransform(self, corners, border):
- """Gets the perspective transform of the screen.
-
- Args:
- corners: The corners of the detected screen.
- border: The number of pixels of border to crop along with the screen.
-
- Returns:
- A perspective transform and the width and height of the target
- transform.
-
- Raises:
- ScreenNotFoundError: Something went wrong in detecting the screen."""
- if self._screen_size is None:
- w = np.sqrt(cv_util.SqDistance(corners[1], corners[0]))
- h = np.sqrt(cv_util.SqDistance(corners[1], corners[2]))
- if w < 1 or h < 1:
- raise self.ScreenNotFoundError(
- 'Screen detected improperly (bad corners)')
- if min(w, h) < self.MIN_SCREEN_WIDTH:
- raise self.ScreenNotFoundError('Detected screen was too small.')
-
- self._screen_size = (w, h)
- # Extend min line length, if we can, to reduce the number of extraneous
- # lines the line finder finds.
- self._min_line_length = max(self._min_line_length, min(w, h) / 1.75)
- w = self._screen_size[0]
- h = self._screen_size[1]
-
- target = np.zeros((4, 2), np.float32)
- width = w + border
- height = h + border
- target[0] = np.asfarray((width, border))
- target[1] = np.asfarray((border, border))
- target[2] = np.asfarray((border, height))
- target[3] = np.asfarray((width, height))
- transform_w = width + border
- transform_h = height + border
- transform = cv2.getPerspectiveTransform(corners, target)
- return transform, transform_w, transform_h
-
- def _Debug(self, lines, corners, final_corners, screen):
- for line in lines:
- intline = ((int(line[0]), int(line[1])),
- (int(line[2]), int(line[3])))
- cv2.line(self._frame_debug, intline[0], intline[1], (0, 0, 255), 1)
- i = 0
- for corner in corners:
- if not np.isnan(corner[0]):
- cv2.putText(
- self._frame_debug, str(i), (int(corner[0]), int(corner[1])),
- cv2.FONT_HERSHEY_COMPLEX_SMALL, 1, (255, 255, 0), 1, cv2.CV_AA)
- i += 1
- if final_corners is not None:
- for corner in final_corners:
- cv2.circle(self._frame_debug,
- (int(corner[0]), int(corner[1])), 5, (255, 0, 255), 1)
- cv2.imshow('original', self._frame)
- cv2.imshow('debug', self._frame_debug)
- if screen is not None:
- cv2.imshow('screen', screen)
- cv2.waitKey()
-
-# For being run as a script.
-# TODO(mthiesse): To be replaced with a better standalone script.
-# Ex: ./screen_finder.py path_to_video 0 5 --verbose
-
-
-def main():
- start_frame = int(sys.argv[2]) if len(sys.argv) >= 3 else 0
- vf = video_file_frame_generator.VideoFileFrameGenerator(sys.argv[1],
- start_frame)
- if len(sys.argv) >= 4:
- sf = ScreenFinder(vf, int(sys.argv[3]))
- else:
- sf = ScreenFinder(vf)
- # TODO(mthiesse): Use argument parser to improve command line parsing.
- if len(sys.argv) > 4 and sys.argv[4] == '--verbose':
- logging.basicConfig(format='%(message)s', level=logging.INFO)
- else:
- logging.basicConfig(format='%(message)s', level=logging.WARN)
- while sf.HasNext():
- sf.GetNext()
-
-if __name__ == '__main__':
- main()

Powered by Google App Engine
This is Rietveld 408576698