/** * * @author greg (at) myrobotlab.org * * This file is part of MyRobotLab (http://myrobotlab.org). * * MyRobotLab is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version (subject to the "Classpath" exception * as provided in the LICENSE.txt file that accompanied this code). * * MyRobotLab is distributed in the hope that it will be useful or fun, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * All libraries in thirdParty bundle are subject to their own license * requirements - please refer to http://myrobotlab.org/libraries for * details. * * Enjoy ! * * */ package org.myrobotlab.opencv; import static org.bytedeco.javacpp.helper.opencv_core.CV_RGB; import static org.bytedeco.javacpp.opencv_core.CV_L1; import static org.bytedeco.javacpp.opencv_core.IPL_DEPTH_32F; import static org.bytedeco.javacpp.opencv_core.IPL_DEPTH_8U; import static org.bytedeco.javacpp.opencv_core.cvAbsDiff; import static org.bytedeco.javacpp.opencv_core.cvClearMemStorage; import static org.bytedeco.javacpp.opencv_core.cvCreateImage; import static org.bytedeco.javacpp.opencv_core.cvCreateMemStorage; import static org.bytedeco.javacpp.opencv_core.cvCvtScale; import static org.bytedeco.javacpp.opencv_core.cvMerge; import static org.bytedeco.javacpp.opencv_core.cvNorm; import static org.bytedeco.javacpp.opencv_core.cvPoint; import static org.bytedeco.javacpp.opencv_core.cvRect; import static org.bytedeco.javacpp.opencv_core.cvReleaseImage; import static org.bytedeco.javacpp.opencv_core.cvResetImageROI; import static org.bytedeco.javacpp.opencv_core.cvSetImageROI; import static org.bytedeco.javacpp.opencv_core.cvSize; import static org.bytedeco.javacpp.opencv_core.cvZero; import static org.bytedeco.javacpp.opencv_imgproc.CV_AA; import static org.bytedeco.javacpp.opencv_imgproc.CV_BGR2GRAY; import static org.bytedeco.javacpp.opencv_imgproc.CV_THRESH_BINARY; import static org.bytedeco.javacpp.opencv_imgproc.cvCircle; import static org.bytedeco.javacpp.opencv_imgproc.cvCvtColor; import static org.bytedeco.javacpp.opencv_imgproc.cvLine; import static org.bytedeco.javacpp.opencv_imgproc.cvThreshold; import org.bytedeco.javacpp.opencv_core.CvMemStorage; import org.bytedeco.javacpp.opencv_core.CvPoint; import org.bytedeco.javacpp.opencv_core.CvRect; import org.bytedeco.javacpp.opencv_core.CvScalar; import org.bytedeco.javacpp.opencv_core.CvSeq; import org.bytedeco.javacpp.opencv_core.CvSize; import org.bytedeco.javacpp.opencv_core.IplImage; import org.myrobotlab.logging.LoggerFactory; import org.slf4j.Logger; public class OpenCVFilterMotionTemplate extends OpenCVFilter { private static final long serialVersionUID = 1L; public final static Logger log = LoggerFactory.getLogger(OpenCVFilterMotionTemplate.class.getCanonicalName()); // various tracking parameters (in seconds) final double MHI_DURATION = 1; final double MAX_TIME_DELTA = 0.5; final double MIN_TIME_DELTA = 0.05; // number of cyclic frame buffer used for motion detection // (should, probably, depend on FPS) final int N = 4; // ring image buffer transient IplImage[] buf = null; int last = 0; // temporary images transient IplImage mhi = null; // MHI transient IplImage orient = null; // orientation transient IplImage mask = null; // valid orientation mask IplImage segmask = null; // motion segmentation map CvMemStorage storage = null; // temporary storage transient IplImage motion = null; public OpenCVFilterMotionTemplate() { super(); } public OpenCVFilterMotionTemplate(String name) { super(name); } @Override public void imageChanged(IplImage image) { // TODO Auto-generated method stub } @Override public IplImage process(IplImage image, OpenCVData data) { // what can you expect? nothing? - if data != null then error? if (motion == null) { motion = cvCreateImage(cvSize(image.width(), image.height()), 8, 3); cvZero(motion); motion.origin(image.origin()); } update_mhi(image, motion, 30); return motion; } // parameters: // img - input video frame // dst - resultant motion picture // args - optional parameters void update_mhi(IplImage img, IplImage dst, int diff_threshold) { double timestamp = 0.0; // TODO FIX double timestamp = (double)clock()/CLOCKS_PER_SEC; // get // current time in seconds CvSize size = cvSize(img.width(), img.height()); // get current frame // size int i, idx1 = last, idx2; IplImage silh; CvSeq seq; CvRect comp_rect; double count; double angle; CvPoint center; double magnitude; CvScalar color; // allocate images at the beginning or // reallocate them if the frame size is changed if (mhi == null || mhi.width() != size.width() || mhi.height() != size.height()) { if (buf == null) { buf = new IplImage[10];// IplImage.create(arg0, arg1, arg2, // arg3); } for (i = 0; i < N; i++) { if (buf[i] != null) { cvReleaseImage(buf[i]); } buf[i] = cvCreateImage(size, IPL_DEPTH_8U, 1); cvZero(buf[i]); } if (mhi != null) { cvReleaseImage(mhi); } if (orient != null) { cvReleaseImage(orient); } if (segmask != null) { cvReleaseImage(segmask); } if (mask != null) { cvReleaseImage(mask); } mhi = cvCreateImage(size, IPL_DEPTH_32F, 1); cvZero(mhi); // clear MHI at the beginning orient = cvCreateImage(size, IPL_DEPTH_32F, 1); segmask = cvCreateImage(size, IPL_DEPTH_32F, 1); mask = cvCreateImage(size, IPL_DEPTH_8U, 1); } cvCvtColor(img, buf[last], CV_BGR2GRAY); // convert frame to // grayscale idx2 = (last + 1) % N; // index of (last - (N-1))th frame last = idx2; silh = buf[idx2]; cvAbsDiff(buf[idx1], buf[idx2], silh); // get difference between frames cvThreshold(silh, silh, diff_threshold, 1, CV_THRESH_BINARY); // and // threshold // it cvUpdateMotionHistory(silh, mhi, timestamp, MHI_DURATION); // update // MHI // convert MHI to blue 8u image cvCvtScale(mhi, mask, 255. / MHI_DURATION, (MHI_DURATION - timestamp) * 255. / MHI_DURATION); cvZero(dst); cvMerge(mask, null, null, null, dst); // calculate motion gradient orientation and valid orientation mask cvCalcMotionGradient(mhi, mask, orient, MAX_TIME_DELTA, MIN_TIME_DELTA, 3); if (storage == null) storage = cvCreateMemStorage(0); else cvClearMemStorage(storage); // segment motion: get sequence of motion components // segmask is marked motion components map. It is not used further seq = cvSegmentMotion(mhi, segmask, storage, timestamp, MAX_TIME_DELTA); // iterate through the motion components, // One more iteration (i == -1) corresponds to the whole image (global // motion) for (i = -1; i < seq.total(); i++) { comp_rect = null; if (i < 0) { // case of the whole image comp_rect = cvRect(0, 0, size.width(), size.height()); color = CV_RGB(255, 255, 255); magnitude = 100; } else { // i-th motion component // TODO - fix CvConnectedComp connected_comp = new // CvConnectedComp(cvGetSeqElem( seq, i )); // TODO -fix comp_rect = connected_comp.rect; if (comp_rect != null && comp_rect.width() + comp_rect.height() < 100) // reject // very // small // components continue; color = CV_RGB(255, 0, 0); magnitude = 30; } // select component ROI cvSetImageROI(silh, comp_rect); cvSetImageROI(mhi, comp_rect); cvSetImageROI(orient, comp_rect); cvSetImageROI(mask, comp_rect); // calculate orientation angle = cvCalcGlobalOrientation(orient, mask, mhi, timestamp, MHI_DURATION); angle = 360.0 - angle; // adjust for images with top-left origin count = cvNorm(silh, null, CV_L1, null); // calculate number of // points within // silhouette ROI cvResetImageROI(mhi); cvResetImageROI(orient); cvResetImageROI(mask); cvResetImageROI(silh); // check for the case of little motion if (count < comp_rect.width() * comp_rect.height() * 0.05) continue; // draw a clock with arrow indicating the direction center = cvPoint((comp_rect.x() + comp_rect.width() / 2), (comp_rect.y() + comp_rect.height() / 2)); // cvCircle( dst, center, cvRound(magnitude*1.2), color, 3, CV_AA, 0 // ); cvCircle(dst, center, (int) (magnitude * 1.2), color, 3, CV_AA, 0); cvLine(dst, center, cvPoint((int) (center.x() + magnitude * Math.cos(angle * Math.PI / 180)), (int) (center.y() - magnitude * Math.sin(angle * Math.PI / 180))), color, 3, CV_AA, 0); } } }