Commit 80febef2 authored by Alexander Smorkalov's avatar Alexander Smorkalov

revision 8721 vas merged to head. Detection based tracker interface changed. cpp…

revision 8721 vas merged to head. Detection based tracker interface changed. cpp and android samples updated.
parent 915c81fe
...@@ -7,22 +7,73 @@ ...@@ -7,22 +7,73 @@
#include <vector> #include <vector>
namespace cv
{
class DetectionBasedTracker class DetectionBasedTracker
{ {
public: public:
struct Parameters struct Parameters
{ {
int minObjectSize;
int maxObjectSize;
double scaleFactor;
int maxTrackLifetime; int maxTrackLifetime;
int minNeighbors;
int minDetectionPeriod; //the minimal time between run of the big object detector (on the whole frame) in ms (1000 mean 1 sec), default=0 int minDetectionPeriod; //the minimal time between run of the big object detector (on the whole frame) in ms (1000 mean 1 sec), default=0
Parameters(); Parameters();
}; };
DetectionBasedTracker(const std::string& cascadeFilename, const Parameters& params); class IDetector
{
public:
IDetector():
minObjSize(96, 96),
maxObjSize(INT_MAX, INT_MAX),
minNeighbours(2),
scaleFactor(1.1f)
{}
virtual void detect(const cv::Mat& Image, std::vector<cv::Rect>& objects) = 0;
void setMinObjectSize(const cv::Size& min)
{
minObjSize = min;
}
void setMaxObjectSize(const cv::Size& max)
{
maxObjSize = max;
}
cv::Size getMinObjectSize() const
{
return minObjSize;
}
cv::Size getMaxObjectSize() const
{
return maxObjSize;
}
float getScaleFactor()
{
return scaleFactor;
}
void setScaleFactor(float value)
{
scaleFactor = value;
}
int getMinNeighbours()
{
return minNeighbours;
}
void setMinNeighbours(int value)
{
minNeighbours = value;
}
virtual ~IDetector() {}
protected:
cv::Size minObjSize;
cv::Size maxObjSize;
int minNeighbours;
float scaleFactor;
};
DetectionBasedTracker(cv::Ptr<IDetector> MainDetector, cv::Ptr<IDetector> TrackingDetector, const Parameters& params);
virtual ~DetectionBasedTracker(); virtual ~DetectionBasedTracker();
virtual bool run(); virtual bool run();
...@@ -44,7 +95,6 @@ class DetectionBasedTracker ...@@ -44,7 +95,6 @@ class DetectionBasedTracker
cv::Ptr<SeparateDetectionWork> separateDetectionWork; cv::Ptr<SeparateDetectionWork> separateDetectionWork;
friend void* workcycleObjectDetectorFunction(void* p); friend void* workcycleObjectDetectorFunction(void* p);
struct InnerParameters struct InnerParameters
{ {
int numLastPositionsToTrack; int numLastPositionsToTrack;
...@@ -90,13 +140,11 @@ class DetectionBasedTracker ...@@ -90,13 +140,11 @@ class DetectionBasedTracker
std::vector<float> weightsPositionsSmoothing; std::vector<float> weightsPositionsSmoothing;
std::vector<float> weightsSizesSmoothing; std::vector<float> weightsSizesSmoothing;
cv::CascadeClassifier cascadeForTracking; cv::Ptr<IDetector> cascadeForTracking;
void updateTrackedObjects(const std::vector<cv::Rect>& detectedObjects); void updateTrackedObjects(const std::vector<cv::Rect>& detectedObjects);
cv::Rect calcTrackedObjectPositionToShow(int i) const; cv::Rect calcTrackedObjectPositionToShow(int i) const;
void detectInRegion(const cv::Mat& img, const cv::Rect& r, std::vector<cv::Rect>& detectedObjectsInRegions); void detectInRegion(const cv::Mat& img, const cv::Rect& r, std::vector<cv::Rect>& detectedObjectsInRegions);
}; };
} //end of cv namespace
#endif #endif
...@@ -18,6 +18,29 @@ inline void vector_Rect_to_Mat(vector<Rect>& v_rect, Mat& mat) ...@@ -18,6 +18,29 @@ inline void vector_Rect_to_Mat(vector<Rect>& v_rect, Mat& mat)
mat = Mat(v_rect, true); mat = Mat(v_rect, true);
} }
class CascadeDetectorAdapter: public DetectionBasedTracker::IDetector
{
public:
CascadeDetectorAdapter(cv::Ptr<cv::CascadeClassifier> detector):
IDetector(),
Detector(detector)
{
CV_Assert(!detector.empty());
}
void detect(const cv::Mat &Image, std::vector<cv::Rect> &objects)
{
Detector->detectMultiScale(Image, objects, scaleFactor, minNeighbours, 0, minObjSize, maxObjSize);
}
virtual ~CascadeDetectorAdapter()
{}
private:
CascadeDetectorAdapter();
cv::Ptr<cv::CascadeClassifier> Detector;
};
JNIEXPORT jlong JNICALL Java_org_opencv_samples_fd_DetectionBasedTracker_nativeCreateObject JNIEXPORT jlong JNICALL Java_org_opencv_samples_fd_DetectionBasedTracker_nativeCreateObject
(JNIEnv * jenv, jclass, jstring jFileName, jint faceSize) (JNIEnv * jenv, jclass, jstring jFileName, jint faceSize)
{ {
...@@ -27,25 +50,26 @@ JNIEXPORT jlong JNICALL Java_org_opencv_samples_fd_DetectionBasedTracker_nativeC ...@@ -27,25 +50,26 @@ JNIEXPORT jlong JNICALL Java_org_opencv_samples_fd_DetectionBasedTracker_nativeC
try try
{ {
DetectionBasedTracker::Parameters DetectorParams; // TODO: Reimplement using adapter
if (faceSize > 0) // DetectionBasedTracker::Parameters DetectorParams;
DetectorParams.minObjectSize = faceSize; // if (faceSize > 0)
result = (jlong)new DetectionBasedTracker(stdFileName, DetectorParams); // DetectorParams.minObjectSize = faceSize;
// result = (jlong)new DetectionBasedTracker(stdFileName, DetectorParams);
} }
catch(cv::Exception e) catch(cv::Exception e)
{ {
LOGD("nativeCreateObject catched cv::Exception: %s", e.what()); LOGD("nativeCreateObject catched cv::Exception: %s", e.what());
jclass je = jenv->FindClass("org/opencv/core/CvException"); jclass je = jenv->FindClass("org/opencv/core/CvException");
if(!je) if(!je)
je = jenv->FindClass("java/lang/Exception"); je = jenv->FindClass("java/lang/Exception");
jenv->ThrowNew(je, e.what()); jenv->ThrowNew(je, e.what());
} }
catch (...) catch (...)
{ {
LOGD("nativeCreateObject catched unknown exception"); LOGD("nativeCreateObject catched unknown exception");
jclass je = jenv->FindClass("java/lang/Exception"); jclass je = jenv->FindClass("java/lang/Exception");
jenv->ThrowNew(je, "Unknown exception in JNI code {highgui::VideoCapture_n_1VideoCapture__()}"); jenv->ThrowNew(je, "Unknown exception in JNI code {highgui::VideoCapture_n_1VideoCapture__()}");
return 0; return 0;
} }
return result; return result;
...@@ -56,22 +80,22 @@ JNIEXPORT void JNICALL Java_org_opencv_samples_fd_DetectionBasedTracker_nativeDe ...@@ -56,22 +80,22 @@ JNIEXPORT void JNICALL Java_org_opencv_samples_fd_DetectionBasedTracker_nativeDe
{ {
try try
{ {
((DetectionBasedTracker*)thiz)->stop(); ((DetectionBasedTracker*)thiz)->stop();
delete (DetectionBasedTracker*)thiz; delete (DetectionBasedTracker*)thiz;
} }
catch(cv::Exception e) catch(cv::Exception e)
{ {
LOGD("nativeestroyObject catched cv::Exception: %s", e.what()); LOGD("nativeestroyObject catched cv::Exception: %s", e.what());
jclass je = jenv->FindClass("org/opencv/core/CvException"); jclass je = jenv->FindClass("org/opencv/core/CvException");
if(!je) if(!je)
je = jenv->FindClass("java/lang/Exception"); je = jenv->FindClass("java/lang/Exception");
jenv->ThrowNew(je, e.what()); jenv->ThrowNew(je, e.what());
} }
catch (...) catch (...)
{ {
LOGD("nativeDestroyObject catched unknown exception"); LOGD("nativeDestroyObject catched unknown exception");
jclass je = jenv->FindClass("java/lang/Exception"); jclass je = jenv->FindClass("java/lang/Exception");
jenv->ThrowNew(je, "Unknown exception in JNI code {highgui::VideoCapture_n_1VideoCapture__()}"); jenv->ThrowNew(je, "Unknown exception in JNI code {highgui::VideoCapture_n_1VideoCapture__()}");
} }
} }
...@@ -80,21 +104,21 @@ JNIEXPORT void JNICALL Java_org_opencv_samples_fd_DetectionBasedTracker_nativeSt ...@@ -80,21 +104,21 @@ JNIEXPORT void JNICALL Java_org_opencv_samples_fd_DetectionBasedTracker_nativeSt
{ {
try try
{ {
((DetectionBasedTracker*)thiz)->run(); ((DetectionBasedTracker*)thiz)->run();
} }
catch(cv::Exception e) catch(cv::Exception e)
{ {
LOGD("nativeStart catched cv::Exception: %s", e.what()); LOGD("nativeStart catched cv::Exception: %s", e.what());
jclass je = jenv->FindClass("org/opencv/core/CvException"); jclass je = jenv->FindClass("org/opencv/core/CvException");
if(!je) if(!je)
je = jenv->FindClass("java/lang/Exception"); je = jenv->FindClass("java/lang/Exception");
jenv->ThrowNew(je, e.what()); jenv->ThrowNew(je, e.what());
} }
catch (...) catch (...)
{ {
LOGD("nativeStart catched unknown exception"); LOGD("nativeStart catched unknown exception");
jclass je = jenv->FindClass("java/lang/Exception"); jclass je = jenv->FindClass("java/lang/Exception");
jenv->ThrowNew(je, "Unknown exception in JNI code {highgui::VideoCapture_n_1VideoCapture__()}"); jenv->ThrowNew(je, "Unknown exception in JNI code {highgui::VideoCapture_n_1VideoCapture__()}");
} }
} }
...@@ -103,21 +127,21 @@ JNIEXPORT void JNICALL Java_org_opencv_samples_fd_DetectionBasedTracker_nativeSt ...@@ -103,21 +127,21 @@ JNIEXPORT void JNICALL Java_org_opencv_samples_fd_DetectionBasedTracker_nativeSt
{ {
try try
{ {
((DetectionBasedTracker*)thiz)->stop(); ((DetectionBasedTracker*)thiz)->stop();
} }
catch(cv::Exception e) catch(cv::Exception e)
{ {
LOGD("nativeStop catched cv::Exception: %s", e.what()); LOGD("nativeStop catched cv::Exception: %s", e.what());
jclass je = jenv->FindClass("org/opencv/core/CvException"); jclass je = jenv->FindClass("org/opencv/core/CvException");
if(!je) if(!je)
je = jenv->FindClass("java/lang/Exception"); je = jenv->FindClass("java/lang/Exception");
jenv->ThrowNew(je, e.what()); jenv->ThrowNew(je, e.what());
} }
catch (...) catch (...)
{ {
LOGD("nativeStop catched unknown exception"); LOGD("nativeStop catched unknown exception");
jclass je = jenv->FindClass("java/lang/Exception"); jclass je = jenv->FindClass("java/lang/Exception");
jenv->ThrowNew(je, "Unknown exception in JNI code {highgui::VideoCapture_n_1VideoCapture__()}"); jenv->ThrowNew(je, "Unknown exception in JNI code {highgui::VideoCapture_n_1VideoCapture__()}");
} }
} }
...@@ -126,28 +150,27 @@ JNIEXPORT void JNICALL Java_org_opencv_samples_fd_DetectionBasedTracker_nativeSe ...@@ -126,28 +150,27 @@ JNIEXPORT void JNICALL Java_org_opencv_samples_fd_DetectionBasedTracker_nativeSe
{ {
try try
{ {
if (faceSize > 0) if (faceSize > 0)
{ {
DetectionBasedTracker::Parameters DetectorParams = \ // TODO: Reimplement using adapter
((DetectionBasedTracker*)thiz)->getParameters(); // DetectionBasedTracker::Parameters DetectorParams = ((DetectionBasedTracker*)thiz)->getParameters();
DetectorParams.minObjectSize = faceSize; // DetectorParams.minObjectSize = faceSize;
((DetectionBasedTracker*)thiz)->setParameters(DetectorParams); // ((DetectionBasedTracker*)thiz)->setParameters(DetectorParams);
} }
} }
catch(cv::Exception e) catch(cv::Exception e)
{ {
LOGD("nativeStop catched cv::Exception: %s", e.what()); LOGD("nativeStop catched cv::Exception: %s", e.what());
jclass je = jenv->FindClass("org/opencv/core/CvException"); jclass je = jenv->FindClass("org/opencv/core/CvException");
if(!je) if(!je)
je = jenv->FindClass("java/lang/Exception"); je = jenv->FindClass("java/lang/Exception");
jenv->ThrowNew(je, e.what()); jenv->ThrowNew(je, e.what());
} }
catch (...) catch (...)
{ {
LOGD("nativeSetFaceSize catched unknown exception"); LOGD("nativeSetFaceSize catched unknown exception");
jclass je = jenv->FindClass("java/lang/Exception"); jclass je = jenv->FindClass("java/lang/Exception");
jenv->ThrowNew(je, "Unknown exception in JNI code {highgui::VideoCapture_n_1VideoCapture__()}"); jenv->ThrowNew(je, "Unknown exception in JNI code {highgui::VideoCapture_n_1VideoCapture__()}");
} }
} }
...@@ -157,23 +180,23 @@ JNIEXPORT void JNICALL Java_org_opencv_samples_fd_DetectionBasedTracker_nativeDe ...@@ -157,23 +180,23 @@ JNIEXPORT void JNICALL Java_org_opencv_samples_fd_DetectionBasedTracker_nativeDe
{ {
try try
{ {
vector<Rect> RectFaces; vector<Rect> RectFaces;
((DetectionBasedTracker*)thiz)->process(*((Mat*)imageGray)); ((DetectionBasedTracker*)thiz)->process(*((Mat*)imageGray));
((DetectionBasedTracker*)thiz)->getObjects(RectFaces); ((DetectionBasedTracker*)thiz)->getObjects(RectFaces);
vector_Rect_to_Mat(RectFaces, *((Mat*)faces)); *((Mat*)faces) = Mat(RectFaces, true);
} }
catch(cv::Exception e) catch(cv::Exception e)
{ {
LOGD("nativeCreateObject catched cv::Exception: %s", e.what()); LOGD("nativeCreateObject catched cv::Exception: %s", e.what());
jclass je = jenv->FindClass("org/opencv/core/CvException"); jclass je = jenv->FindClass("org/opencv/core/CvException");
if(!je) if(!je)
je = jenv->FindClass("java/lang/Exception"); je = jenv->FindClass("java/lang/Exception");
jenv->ThrowNew(je, e.what()); jenv->ThrowNew(je, e.what());
} }
catch (...) catch (...)
{ {
LOGD("nativeDetect catched unknown exception"); LOGD("nativeDetect catched unknown exception");
jclass je = jenv->FindClass("java/lang/Exception"); jclass je = jenv->FindClass("java/lang/Exception");
jenv->ThrowNew(je, "Unknown exception in JNI code {highgui::VideoCapture_n_1VideoCapture__()}"); jenv->ThrowNew(je, "Unknown exception in JNI code {highgui::VideoCapture_n_1VideoCapture__()}");
} }
} }
\ No newline at end of file
#if defined(__linux__) || defined(LINUX) || defined(__APPLE__) || defined(ANDROID)
#include <opencv2/imgproc/imgproc.hpp> // Gaussian Blur
#include <opencv2/core/core.hpp> // Basic OpenCV structures (cv::Mat, Scalar)
#include <opencv2/highgui/highgui.hpp> // OpenCV window I/O
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/contrib/detection_based_tracker.hpp>
#include <stdio.h>
#include <string>
#include <vector>
using namespace std;
using namespace cv;
const string WindowName = "Face Detection example";
class CascadeDetectorAdapter: public DetectionBasedTracker::IDetector
{
public:
CascadeDetectorAdapter(cv::Ptr<cv::CascadeClassifier> detector):
IDetector(),
Detector(detector)
{
CV_Assert(!detector.empty());
}
void detect(const cv::Mat &Image, std::vector<cv::Rect> &objects)
{
Detector->detectMultiScale(Image, objects, scaleFactor, minNeighbours, 0, minObjSize, maxObjSize);
}
virtual ~CascadeDetectorAdapter()
{}
private:
CascadeDetectorAdapter();
cv::Ptr<cv::CascadeClassifier> Detector;
};
int main(int argc, char* argv[])
{
namedWindow(WindowName);
VideoCapture VideoStream(0);
if (!VideoStream.isOpened())
{
printf("Error: Cannot open video stream from camera\n");
return 1;
}
std::string cascadeFrontalfilename = "../../data/lbpcascades/lbpcascade_frontalface.xml";
cv::Ptr<cv::CascadeClassifier> cascade = new cv::CascadeClassifier(cascadeFrontalfilename);
cv::Ptr<DetectionBasedTracker::IDetector> MainDetector = new CascadeDetectorAdapter(cascade);
cascade = new cv::CascadeClassifier(cascadeFrontalfilename);
cv::Ptr<DetectionBasedTracker::IDetector> TrackingDetector = new CascadeDetectorAdapter(cascade);
DetectionBasedTracker::Parameters params;
DetectionBasedTracker Detector(MainDetector, TrackingDetector, params);
if (!Detector.run())
{
printf("Error: Detector initialization failed\n");
return 2;
}
Mat ReferenceFrame;
Mat GrayFrame;
vector<Rect> Faces;
while(true)
{
VideoStream >> ReferenceFrame;
cvtColor(ReferenceFrame, GrayFrame, COLOR_RGB2GRAY);
Detector.process(GrayFrame);
Detector.getObjects(Faces);
for (size_t i = 0; i < Faces.size(); i++)
{
rectangle(ReferenceFrame, Faces[i], CV_RGB(0,255,0));
}
imshow(WindowName, ReferenceFrame);
if (cvWaitKey(30) >= 0) break;
}
Detector.stop();
return 0;
}
#else
#include <stdio.h>
int main()
{
printf("This sample works for UNIX or ANDROID only\n");
return 0;
}
#endif
...@@ -43,8 +43,6 @@ ...@@ -43,8 +43,6 @@
#define LOGE(...) do{} while(0) #define LOGE(...) do{} while(0)
#endif #endif
using namespace cv; using namespace cv;
using namespace std; using namespace std;
...@@ -63,9 +61,31 @@ static void usage() ...@@ -63,9 +61,31 @@ static void usage()
LOGE0("\t (e.g.\"opencv/data/lbpcascades/lbpcascade_frontalface.xml\" "); LOGE0("\t (e.g.\"opencv/data/lbpcascades/lbpcascade_frontalface.xml\" ");
} }
class CascadeDetectorAdapter: public DetectionBasedTracker::IDetector
{
public:
CascadeDetectorAdapter(cv::Ptr<cv::CascadeClassifier> detector):
Detector(detector)
{
CV_Assert(!detector.empty());
}
void detect(const cv::Mat &Image, std::vector<cv::Rect> &objects)
{
Detector->detectMultiScale(Image, objects, 1.1, 3, 0, minObjSize, maxObjSize);
}
virtual ~CascadeDetectorAdapter()
{}
private:
CascadeDetectorAdapter();
cv::Ptr<cv::CascadeClassifier> Detector;
};
static int test_FaceDetector(int argc, char *argv[]) static int test_FaceDetector(int argc, char *argv[])
{ {
if (argc < 4) { if (argc < 4)
{
usage(); usage();
return -1; return -1;
} }
...@@ -80,12 +100,14 @@ static int test_FaceDetector(int argc, char *argv[]) ...@@ -80,12 +100,14 @@ static int test_FaceDetector(int argc, char *argv[])
vector<Mat> images; vector<Mat> images;
{ {
char filename[256]; char filename[256];
for(int n=1; ; n++) { for(int n=1; ; n++)
{
snprintf(filename, sizeof(filename), filepattern, n); snprintf(filename, sizeof(filename), filepattern, n);
LOGD("filename='%s'", filename); LOGD("filename='%s'", filename);
Mat m0; Mat m0;
m0=imread(filename); m0=imread(filename);
if (m0.empty()) { if (m0.empty())
{
LOGI0("Cannot read the file --- break"); LOGI0("Cannot read the file --- break");
break; break;
} }
...@@ -94,10 +116,15 @@ static int test_FaceDetector(int argc, char *argv[]) ...@@ -94,10 +116,15 @@ static int test_FaceDetector(int argc, char *argv[])
LOGD("read %d images", (int)images.size()); LOGD("read %d images", (int)images.size());
} }
DetectionBasedTracker::Parameters params;
std::string cascadeFrontalfilename=cascadefile; std::string cascadeFrontalfilename=cascadefile;
cv::Ptr<cv::CascadeClassifier> cascade = new cv::CascadeClassifier(cascadeFrontalfilename);
cv::Ptr<DetectionBasedTracker::IDetector> MainDetector = new CascadeDetectorAdapter(cascade);
cascade = new cv::CascadeClassifier(cascadeFrontalfilename);
cv::Ptr<DetectionBasedTracker::IDetector> TrackingDetector = new CascadeDetectorAdapter(cascade);
DetectionBasedTracker fd(cascadeFrontalfilename, params); DetectionBasedTracker::Parameters params;
DetectionBasedTracker fd(MainDetector, TrackingDetector, params);
fd.run(); fd.run();
...@@ -108,12 +135,13 @@ static int test_FaceDetector(int argc, char *argv[]) ...@@ -108,12 +135,13 @@ static int test_FaceDetector(int argc, char *argv[])
double freq=getTickFrequency(); double freq=getTickFrequency();
int num_images=images.size(); int num_images=images.size();
for(int n=1; n <= num_images; n++) { for(int n=1; n <= num_images; n++)
{
int64 tcur=getTickCount(); int64 tcur=getTickCount();
int64 dt=tcur-tprev; int64 dt=tcur-tprev;
tprev=tcur; tprev=tcur;
double t_ms=((double)dt)/freq * 1000.0; double t_ms=((double)dt)/freq * 1000.0;
LOGD("\n\nSTEP n=%d from prev step %f ms\n\n", n, t_ms); LOGD("\n\nSTEP n=%d from prev step %f ms\n", n, t_ms);
m=images[n-1]; m=images[n-1];
CV_Assert(! m.empty()); CV_Assert(! m.empty());
cvtColor(m, gray, CV_BGR2GRAY); cvtColor(m, gray, CV_BGR2GRAY);
...@@ -123,11 +151,8 @@ static int test_FaceDetector(int argc, char *argv[]) ...@@ -123,11 +151,8 @@ static int test_FaceDetector(int argc, char *argv[])
vector<Rect> result; vector<Rect> result;
fd.getObjects(result); fd.getObjects(result);
for(size_t i=0; i < result.size(); i++)
{
for(size_t i=0; i < result.size(); i++) {
Rect r=result[i]; Rect r=result[i];
CV_Assert(r.area() > 0); CV_Assert(r.area() > 0);
Point tl=r.tl(); Point tl=r.tl();
...@@ -136,14 +161,14 @@ static int test_FaceDetector(int argc, char *argv[]) ...@@ -136,14 +161,14 @@ static int test_FaceDetector(int argc, char *argv[])
rectangle(m, tl, br, color, 3); rectangle(m, tl, br, color, 3);
} }
} }
char outfilename[256];
for(int n=1; n <= num_images; n++)
{ {
char outfilename[256]; snprintf(outfilename, sizeof(outfilename), outfilepattern, n);
for(int n=1; n <= num_images; n++) { LOGD("outfilename='%s'", outfilename);
snprintf(outfilename, sizeof(outfilename), outfilepattern, n); m=images[n-1];
LOGD("outfilename='%s'", outfilename); imwrite(outfilename, m);
m=images[n-1];
imwrite(outfilename, m);
}
} }
fd.stop(); fd.stop();
...@@ -151,8 +176,6 @@ static int test_FaceDetector(int argc, char *argv[]) ...@@ -151,8 +176,6 @@ static int test_FaceDetector(int argc, char *argv[])
return 0; return 0;
} }
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
return test_FaceDetector(argc, argv); return test_FaceDetector(argc, argv);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment