Вы находитесь на странице: 1из 16

The algorithm for calculating machines is implemented in the file processing.cpp.

Steps of the algorithm:

Calculation of brightness for the previous and current frames, their Gaussian blur with a radius of
5 pixels, calculation of the difference between the received frames (cv :: cvtColor, cv ::
GaussianBlur, cv :: absdiff)

Calculating a binary mask for moving objects (cv :: threshold)


Application of morphology: РРСРРСРРС, where Р - math. extension (cv :: dilate), C. narrowing (cv
:: erode)

Search for contours of closed areas (cv :: convexHull), initialization of CarDescriptor to track their
movement

Match the objects found in the previous step with the objects found in the current step
(matchCars). Comprises:

prediction of the next position of the object to a maximum of 5 points from the transport history
(predictNextPosition), * searching for an object in the radius sqrt (w ^ 2 + h ^ 2) * 0.5 with respect
to the predicted point,

Delete objects from the list of monitored after their absence for 5 frames.

#define SHOW_STEPS 0

#include <algorithm>
#include <iterator>
#include <vector>
#include "processing.h"

CarDescriptor::CarDescriptor() :
isMatchFound(true),
isCounted(false),
numFramesWithoutMatch(0)
{
// empty
}

CarDescriptor::CarDescriptor(const std::vector<cv::Point>& contour) :


contour(contour),
boundingRect(cv::boundingRect(contour)),
isMatchFound(true),
isCounted(false),
numFramesWithoutMatch(0)
{
cv::Point center(
boundingRect.x + boundingRect.width / 2,
boundingRect.y + boundingRect.height / 2);
centerPositions.push_back(center);
diagonalSize = sqrt(pow(boundingRect.width, 2) + pow(boundingRect.height, 2));
aspectRatio = (double)boundingRect.width / boundingRect.height;
}

void CarDescriptor::predictNextPosition() {
int account = std::min(5, (int)centerPositions.size());
auto prev = centerPositions.rbegin();
auto current = prev;
std::advance(current, 1);
int deltaX = 0, deltaY = 0, sum = 0;
for (int i = 1; i < account; ++i) {
deltaX += (current->x - prev->x) * i;
deltaY += (current->y - prev->y) * i;
sum += i;
}
if (sum > 0) {
deltaX /= sum;
deltaY /= sum;
}
predictedNextPos.x = centerPositions.back().x + deltaX;
predictedNextPos.y = centerPositions.back().y + deltaY;
}

bool CarDescriptor::isCar() const {


return boundingRect.area() > 600 &&
0.2 < aspectRatio && aspectRatio < 4.0 &&
boundingRect.width > 40 && boundingRect.height > 40 &&
diagonalSize > 70.0 &&
cv::contourArea(contour)/boundingRect.area() > 0.5;
}

void CarDescriptor::assign(const CarDescriptor& other) {


contour = other.contour;
boundingRect = other.boundingRect;
centerPositions.push_back(other.centerPositions.back());
diagonalSize = other.diagonalSize;
aspectRatio = other.aspectRatio;
isMatchFound = true;
}

const cv::Scalar BLACK = cv::Scalar(0.0, 0.0, 0.0);


const cv::Scalar WHITE = cv::Scalar(255.0, 255.0, 255.0);
const cv::Scalar YELLOW = cv::Scalar(0.0, 255.0, 255.0);
const cv::Scalar GREEN = cv::Scalar(0.0, 200.0, 0.0);
const cv::Scalar RED = cv::Scalar(0.0, 0.0, 255.0);

double distance(const cv::Point& p1, const cv::Point& p2) {


int intX = p1.x - p2.x;
int intY = p1.y - p2.y;
return sqrt((double)(intX * intX + intY * intY));
}

void matchCars(std::list<CarDescriptor>& existing, const std::list<CarDescriptor>& current) {


for (auto &ex : existing) {
ex.isMatchFound = false;
ex.predictNextPosition();
}
for (auto &cur : current) {
std::list<CarDescriptor>::iterator itOfLeastDistance = existing.begin();
double leastDistance = std::numeric_limits<double>::max();
for (auto it = existing.begin(); it != existing.end(); ++it) {
double v = distance(cur.centerPositions.back(), it->predictedNextPos);
if (v < leastDistance) {
leastDistance = v;
itOfLeastDistance = it;
}
}
if (leastDistance < cur.diagonalSize * 0.5) {
itOfLeastDistance->assign(cur);
}
else {
existing.emplace_back(cur);
existing.back().isMatchFound = true;
}
}
for (auto it = existing.begin(); it != existing.end(); ) {
if (!it->isMatchFound) {
if (++it->numFramesWithoutMatch >= 5) {
it = existing.erase(it);
continue;
}
}
++it;
}
}

#ifdef SHOW_STEPS

void show(const cv::Size& imageSize, const std::vector<std::vector<cv::Point>>& contours, const


std::string& title) {
cv::Mat image(imageSize, CV_8UC3, BLACK);
cv::drawContours(image, contours, -1, WHITE, -1);
cv::imshow(title, image);
}

void show(const cv::Size& imageSize, const std::list<CarDescriptor>& cars, const std::string& title)
{
cv::Mat image(imageSize, CV_8UC3, BLACK);
std::vector<std::vector<cv::Point>> contours;
for (auto &car : cars)
contours.push_back(car.contour);
cv::drawContours(image, contours, -1, WHITE, -1);
cv::imshow(title, image);
}

#endif

inline void drawCarsInfo(const std::list<CarDescriptor>& cars, cv::Mat& image) {


for (auto car = cars.begin(); car != cars.end(); ++car)
cv::rectangle(image, car->boundingRect, RED, 2);
}

DetectFilter::DetectFilter(QObject* parent) :
AbstractFilter(parent),
_mutex(QMutex::Recursive),
_carsCount(0)
{
_structuringElement = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3));
}

bool DetectFilter::process(cv::Mat& currentFrame) {


_frameCount++;
cv::resize(currentFrame, currentFrame, cv::Size(), 0.5, 0.5, cv::INTER_AREA);
if (_prevFrame.empty()) {
_prevFrame = currentFrame;
return true;
}
_currentFrameCars.resize(0);

cv::Mat prevFrameCopy, curFrameCopy, imgDifference, imgThresh;


cv::cvtColor(currentFrame, curFrameCopy, CV_BGR2GRAY);
cv::GaussianBlur(curFrameCopy, curFrameCopy, cv::Size(5,5), 0);
cv::cvtColor(_prevFrame, prevFrameCopy, CV_BGR2GRAY);
cv::GaussianBlur(prevFrameCopy, prevFrameCopy, cv::Size(5,5), 0);
cv::absdiff(prevFrameCopy, curFrameCopy, imgDifference);
cv::threshold(imgDifference, imgThresh, 15, 255.0, CV_THRESH_BINARY);
for (int i = 0; i < 3; i++) {
cv::dilate(imgThresh, imgThresh, _structuringElement);
cv::dilate(imgThresh, imgThresh, _structuringElement);
cv::erode(imgThresh, imgThresh, _structuringElement);
}
std::vector<std::vector<cv::Point>> contours;
cv::findContours(imgThresh, contours, cv::RETR_EXTERNAL,
cv::CHAIN_APPROX_TC89_KCOS);
#if SHOW_STEPS
show(imgThresh.size(), contours, "contours");
#endif

std::vector<std::vector<cv::Point>> convexHulls(contours.size());
for (int i = 0; i < contours.size(); i++)
cv::convexHull(contours[i], convexHulls[i]);
#if SHOW_STEPS
show(imgThresh.size(), convexHulls, "convexHulls");
#endif

for (auto &convexHull : convexHulls) {


CarDescriptor car(convexHull);
if (car.isCar())
_currentFrameCars.push_back(car);
}
#if SHOW_STEPS
show(imgThresh.size(), _currentFrameCars, "currentCars");
#endif

if (_frameCount <= 2)
_cars.swap(_currentFrameCars);
else {
matchCars(_cars, _currentFrameCars);
}
#if SHOW_STEPS
show(imgThresh.size(), _cars, "trackedCars");
#endif

_prevFrame = currentFrame;
currentFrame = currentFrame.clone();

// prepare visualization
drawCarsInfo(_cars, currentFrame);

double fontScale = (currentFrame.rows * currentFrame.cols) / 1000000.0;


int fontThickness = (int)std::round(fontScale * 1.5);

// for each line


QMutexLocker lock(&_mutex);
for (int i = 0; i < _segments.size(); ++i) {
const QLineF &line = _segments[i];
bool highlight = false;
for (CarDescriptor &car : _cars) {
if (!car.isCounted && car.centerPositions.size() >= 2) {
bool directionDown;
const cv::Point
&p2 = car.centerPositions[(int)car.centerPositions.size() - 2],
&q2 = car.centerPositions[(int)car.centerPositions.size() - 1];
if (intersects(line, QLineF(p2.x, p2.y, q2.x, q2.y), directionDown)) {
if (directionDown) {
++_carsCount[i];
highlight = true;
car.isCounted = true;
}
}
}
}
QPointF center = line.center();
cv::line(currentFrame, cv::Point((int)line.x1(), (int)line.y1()), cv::Point((int)line.x2(),
(int)line.y2()), highlight ? GREEN : RED, 2);
cv::putText(currentFrame, std::to_string(_carsCount[i]), cv::Point((int)center.x(),
(int)center.y()), CV_FONT_HERSHEY_SIMPLEX, fontScale, YELLOW, fontThickness);
}
//cv::resize(result, result, cv::Size(), 0.5, 0.5, cv::INTER_AREA);
return true;
}
#include "GraphicsItemPolyline.h"

#include <QGraphicsScene>
#include <QGraphicsSceneMouseEvent>
#include <QPainter>

GraphicsItemPolylinePoint::GraphicsItemPolylinePoint(const QRectF& rect, const QBrush& brush,


QGraphicsItem* parent) :
QGraphicsObject(parent),
_rect(rect),
_brush(brush)
{
_menu = new QMenu();
auto action = new QAction(this);
action->setText(tr("Delete"));
connect(action, SIGNAL(triggered()), this, SLOT(menuDeletePoint()));
_menu->addAction(action);
}

GraphicsItemPolylinePoint::~GraphicsItemPolylinePoint() {
delete _menu;
}

void GraphicsItemPolylinePoint::mousePressEvent(QGraphicsSceneMouseEvent* ev) {


if (ev->button() == Qt::RightButton) {
_menu->exec(ev->screenPos());
}
QGraphicsObject::mousePressEvent(ev);
}

void GraphicsItemPolylinePoint::menuDeletePoint() {
emit deletePoint();
}

QRectF GraphicsItemPolylinePoint::boundingRect() const {


return _rect;
}

void GraphicsItemPolylinePoint::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,


QWidget *widget) {
Q_UNUSED(option);
Q_UNUSED(widget);
painter->fillRect(_rect, _brush);
painter->drawRect(_rect);
}
GraphicsItemPolyline::Item::Item(const QPointF& point, GraphicsItemPolyline* parent) :
cache(point),
invertDirection(false)
{
QRectF rect(QPointF(-parent->_ptSize.width() / 2, -parent->_ptSize.height() / 2), parent-
>_ptSize);
object = new GraphicsItemPolylinePoint(rect, parent->_brush, parent);
parent->scene()->addItem(object);
object->setPos(point);
QObject::connect(object, SIGNAL(xChanged()), parent, SLOT(itemPosChanged()));
QObject::connect(object, SIGNAL(yChanged()), parent, SLOT(itemPosChanged()));
QObject::connect(object, SIGNAL(zChanged()), parent, SLOT(itemPosChanged()));
QObject::connect(object, SIGNAL(deletePoint()), parent, SLOT(menuDeletePoint()));
object->setFlag(ItemIsMovable);
}

GraphicsItemPolyline::GraphicsItemPolyline(QGraphicsScene* scene, QGraphicsItem* parent) :


QGraphicsObject(parent),
_mutex(QMutex::Recursive),
_brush(Qt::red),
_pen(Qt::red),
_penInverted(Qt::blue),
_ptSize(10, 10)
{
_menu = new QMenu();
auto action = new QAction(this);
action->setText(tr("Add Point"));
connect(action, SIGNAL(triggered()), this, SLOT(menuAddPoint()));
_menu->addAction(action);
action = new QAction(this);
action->setText(tr("Invert direction"));
action->setCheckable(true);
connect(action, SIGNAL(triggered()), this, SLOT(menuInvertSegment()));
_menu->addAction(action);
_invertDirectionAction = action;

scene->addItem(this);

_pen.setWidth(3);
_penInverted.setWidth(3);
setAcceptedMouseButtons(Qt::RightButton);

InsertPoint(0, QPointF(0, 0));


InsertPoint(1, QPointF(100, 0));
}

GraphicsItemPolyline::~GraphicsItemPolyline() {
for (auto &pt : _points)
scene()->removeItem(pt.object);
delete _menu;
}
QRectF GraphicsItemPolyline::boundingRect() const {
QMutexLocker lock(&_mutex);
for (auto &item : _points) {
auto rect = item.object->boundingRect();
auto newCenter = item.object->pos();
rect.moveCenter(newCenter);
_boundRect = _boundRect.united(rect);
}
return _boundRect;
}

void GraphicsItemPolyline::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,


QWidget *widget) {
QMutexLocker lock(&_mutex);
Q_UNUSED(option);
Q_UNUSED(widget);
#if 0
auto oldPen = painter->pen();
QPointF lastPoint = _points[0].cache;
bool inverted = _points[0].invertDirection;
painter->setPen(inverted ? _penInverted : _pen);
for (int i = 1; i < _points.size(); ++i) {
auto &current = _points[i];
painter->drawLine(QLineF(lastPoint, current.cache));
if (inverted != current.invertDirection) {
inverted = current.invertDirection;
painter->setPen(inverted ? _penInverted : _pen);
}
lastPoint = current.cache;
}
painter->setPen(oldPen);
#endif
}

QVector<QLineF> GraphicsItemPolyline::segments() const {


QMutexLocker lock(&_mutex);
QVector<QLineF> result;
for (int i = 0; i < _points.size() - 1; ++i) {
result.push_back(QLineF(_points[i].cache, _points[i + 1].cache));
if (_points[i].invertDirection)
result.back().setPoints(result.back().p2(), result.back().p1());
}
return result;
}

void GraphicsItemPolyline::mousePressEvent(QGraphicsSceneMouseEvent* ev) {


if (ev->button() == Qt::RightButton) {
_menuPoint = ev->pos();
{
QMutexLocker lock(&_mutex);
int pos = IndexOfSegment(_menuPoint);
auto &point = _points[pos];
_invertDirectionAction->setChecked(point.invertDirection);
}
_menu->exec(ev->screenPos());
}
QGraphicsObject::mousePressEvent(ev);
}

void GraphicsItemPolyline::InsertPoint(int pos, const QPointF& pt) {


QMutexLocker lock(&_mutex);
_points.insert(pos, Item(pt, this));
emit segmentsUpdated(segments());
update();
}

bool GraphicsItemPolyline::UpdatePoint(int pos) {


QMutexLocker lock(&_mutex);
auto &point = _points[pos];
auto newValue = point.object->pos();
if (point.cache != newValue) {
point.cache = newValue;
emit segmentsUpdated(segments());
return true;
}
return false;
}

void GraphicsItemPolyline::DeletePoint(int pos) {


QMutexLocker lock(&_mutex);
scene()->removeItem(_points[pos].object);
_points.removeAt(pos);
emit segmentsUpdated(segments());
update();
}

// slots & utility

int GraphicsItemPolyline::IndexOfPoint(QObject* sender) const {


QMutexLocker lock(&_mutex);
for (int i = 0; i < _points.size(); ++i)
if (_points[i].object == sender)
return i;
return -1;
}

int GraphicsItemPolyline::IndexOfSegment(const QPointF& p2) const {


QMutexLocker lock(&_mutex);
float distance;
int index = -1;
for (int i = 0; i < _points.size() - 1; ++i) {
auto &p1 = _points[i].cache, &b = _points[i + 1].cache;
QLineF vl1 = QLineF(p1, QPointF(b.x() - p1.x(), b.y() - p1.y()));
QLineF vl2 = vl1.normalVector();

QPointF v1(vl1.dx(), vl1.dy()), v2(vl2.dx(), vl2.dy());

float g = v2.x()*v1.y() - v1.x()*v2.y();


float c = p1.x()*v1.y() - p1.y()*v1.x();
float f = p2.x()*v2.y() - p2.y()*v2.x();
QPointF x(
(v2.x()*c - v1.x()*f) / g,
(v2.y()*c - v1.y()*f) / g
);
float cur_distance = QLineF(p2, x).length();
if (index < 0 || distance > cur_distance) {
index = i;
distance = cur_distance;
}
}
return index;
}

void GraphicsItemPolyline::itemPosChanged() {
int pos = IndexOfPoint(sender());
if (pos >= 0) {
if (UpdatePoint(pos))
update();
}
}

void GraphicsItemPolyline::menuAddPoint() {
int pos = IndexOfSegment(_menuPoint);
InsertPoint(pos + 1, _menuPoint);
}
void GraphicsItemPolyline::menuDeletePoint() {
QMutexLocker lock(&_mutex);
int pos = IndexOfPoint(sender());
if (_points.size() > 2)
DeletePoint(pos);
}
void GraphicsItemPolyline::menuInvertSegment() {
QMutexLocker lock(&_mutex);
int pos = IndexOfSegment(_menuPoint);
auto &point = _points[pos];
point.invertDirection = !point.invertDirection;
emit segmentsUpdated(segments());
update();
}
#include <QTimerEvent>

#include "QtUtility.h"

// class QtCVImage

const QImage& QtCVImage::image() const {


if (_image.isNull() && !_mat.empty()) {
QImage::Format format;
switch (_mat.type()) {
case CV_8UC1: format = QImage::Format_Indexed8; break;
case CV_8UC3: format = QImage::Format_RGB888; break;
default:
_image = QImage();
return _image;
}
_image = QImage(_mat.data, _mat.cols, _mat.rows, (int)_mat.step, format,
[](void *ptr) { delete static_cast<cv::Mat*>(ptr); },
new cv::Mat(_mat)
);
if (_mat.type() == CV_8UC1) {
QVector<QRgb> colorTable;
colorTable.reserve(256);
for (int i = 0; i < 256; i++)
colorTable.push_back(qRgb(i, i, i));
_image.setColorTable(colorTable);
}
}
return _image;
}

const cv::Mat& QtCVImage::mat() const {


if (_mat.empty() && !_image.isNull()) {
_mat = cv::Mat(_image.height(), _image.width(), CV_8UC3,
(void*)_image.constScanLine(0), _image.bytesPerLine());
}
return _mat;
}

QtCVImage& QtCVImage::operator=(const QtCVImage& other) {


_image = other._image;
_mat = other._mat;
return *this;
}

QtCVImage& QtCVImage::operator=(const QImage& image) {


_image = image.convertToFormat(QImage::Format_RGB888);
_mat.release();
return *this;
}
QtCVImage& QtCVImage::operator=(const cv::Mat& mat) {
_image = QImage();
if (_mat.type() == CV_8UC3)
cv::cvtColor(mat, _mat, CV_BGR2RGB);
else
_mat = mat;
return *this;
}

#ifdef VIDEOFRAME_SUPPORT
QtCVImage& QtCVImage::operator=(const QVideoFrame& frame) {
_frame = frame;
if (_frame.map(QAbstractVideoBuffer::ReadOnly)) {
QImage::Format format =
QVideoFrame::imageFormatFromPixelFormat(frame.pixelFormat());
_image = QImage(frame.bits(), frame.width(), frame.height(), frame.bytesPerLine(),
format);
}
return *this;
}
#endif

// class AbstractFilter

void AbstractFilter::start() {
if (!_timer.isActive()) {
_frameCount = 0;
_timer.start((int)(1001 / 24), this);
}
}

void AbstractFilter::stop() {
_timer.stop();
}

void AbstractFilter::timerEvent(QTimerEvent* ev) {


if (ev->timerId() != _timer.timerId())
return;
cv::Mat frame;
// Next statement blocks until a new frame is ready
if (!_videoCapture->read(frame)) {
_timer.stop();
return;
}
_frameCount++;
if (process(frame)) {
QtCVImage i(frame);
emit newFrame(i.image());
}
}

bool AbstractFilter::open(cv::VideoCapture* capturePtr) {


if (_timer.isActive())
stop();
if (!capturePtr->isOpened())
return false;
_videoCapture.reset(capturePtr);

start();
return true;
}

bool AbstractFilter::open(const QString& filename) {


return open(new cv::VideoCapture(filename.toStdString()));
}

bool AbstractFilter::open(int cvCamId) {


return open(new cv::VideoCapture(cvCamId));
}

// Given three colinear points p, q, r, the function checks if


// point q lies on line segment 'pr'
inline bool onSegment(const QPointF& p, const QPointF& q, const QPointF& r) {
return
q.x() <= std::max(p.x(), r.x()) && q.x() >= std::min(p.x(), r.x()) &&
q.y() <= std::max(p.y(), r.y()) && q.y() >= std::min(p.y(), r.y());
}

// To find orientation of ordered triplet (p, q, r).


// The function returns following values
// 0 --> p, q and r are colinear
// 1 --> Clockwise
// 2 --> Counterclockwise
int orientation(const QPointF& p, const QPointF& q, const QPointF& r)
{
// See http://www.geeksforgeeks.org/orientation-3-ordered-points/
// for details of below formula.
int val = (q.y() - p.y()) * (r.x() - q.x()) - (q.x() - p.x()) * (r.y() - q.y());
if (val == 0)
return 0; // colinear
return (val > 0) ? 1 : 2; // clock or counterclock wise
}

// The main function that returns true if line segment 'p1q1'


// and 'p2q2' intersect.
bool intersects(const QLineF& l1, const QLineF& l2, bool& directionDown) {
const QPointF &p1 = l1.p1(), &q1 = l1.p2(), &p2 = l2.p1(), &q2 = l2.p2();

// Find the four orientations needed for general and special cases
int o1 = orientation(p1, q1, p2);
int o2 = orientation(p1, q1, q2);
int o3 = orientation(p2, q2, p1);
int o4 = orientation(p2, q2, q1);

directionDown = o3 == 2;

// General case
if (o1 != o2 && o3 != o4)
return true;

// Special Cases
// p1, q1 and p2 are colinear and p2 lies on segment p1q1
if (o1 == 0 && onSegment(p1, p2, q1)) return true;

// p1, q1 and p2 are colinear and q2 lies on segment p1q1


if (o2 == 0 && onSegment(p1, q2, q1)) return true;

// p2, q2 and p1 are colinear and p1 lies on segment p2q2


if (o3 == 0 && onSegment(p2, p1, q2)) return true;

// p2, q2 and q1 are colinear and q1 lies on segment p2q2


if (o4 == 0 && onSegment(p2, q1, q2)) return true;

return false; // Doesn't fall in any of the above cases


}

Оценить