Merge pull request #1750 from LaurentBerger:oilpainting
* Oil painting effect * license * try auto * Insert test * template * review * indentation in namespace * remove back to futurepull/1769/head
parent
de3041bc71
commit
7f5bb96dcc
9 changed files with 454 additions and 0 deletions
@ -0,0 +1,41 @@ |
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
|
||||
|
||||
#ifndef __OPENCV_OIL_PAINTING_HPP__ |
||||
#define __OPENCV_OIL_PAINTING_HPP__ |
||||
|
||||
#include <opencv2/core.hpp> |
||||
#include <opencv2/imgproc.hpp> |
||||
|
||||
namespace cv |
||||
{ |
||||
namespace xphoto |
||||
{ |
||||
|
||||
//! @addtogroup xphoto
|
||||
//! @{
|
||||
|
||||
/** @brief oilPainting
|
||||
See the book @cite Holzmann1988 for details. |
||||
@param src Input three-channel or one channel image (either CV_8UC3 or CV_8UC1) |
||||
@param dst Output image of the same size and type as src. |
||||
@param size neighbouring size is 2-size+1 |
||||
@param dynRatio image is divided by dynRatio before histogram processing |
||||
@param code color space conversion code(see ColorConversionCodes). Histogram will used only first plane |
||||
*/ |
||||
CV_EXPORTS_W void oilPainting(InputArray src, OutputArray dst, int size, int dynRatio, int code); |
||||
/** @brief oilPainting
|
||||
See the book @cite Holzmann1988 for details. |
||||
@param src Input three-channel or one channel image (either CV_8UC3 or CV_8UC1) |
||||
@param dst Output image of the same size and type as src. |
||||
@param size neighbouring size is 2-size+1 |
||||
@param dynRatio image is divided by dynRatio before histogram processing |
||||
*/ |
||||
CV_EXPORTS_W void oilPainting(InputArray src, OutputArray dst, int size, int dynRatio); |
||||
//! @}
|
||||
} |
||||
} |
||||
|
||||
#endif // __OPENCV_OIL_PAINTING_HPP__
|
@ -0,0 +1,103 @@ |
||||
#include <opencv2/core.hpp> |
||||
#include <opencv2/highgui.hpp> |
||||
#include <opencv2/imgproc.hpp> |
||||
#include <opencv2/xphoto.hpp> |
||||
#include "opencv2/xphoto/oilpainting.hpp" |
||||
#include <iostream> |
||||
|
||||
using namespace cv; |
||||
using namespace std; |
||||
|
||||
static void TrackSlider(int , void *); |
||||
static void addSlider(String sliderName, String windowName, int minSlider, int maxSlider, int valDefault, int *valSlider, void(*f)(int, void *), void *r); |
||||
vector<int> colorSpace = { COLOR_BGR2GRAY,COLOR_BGR2HSV,COLOR_BGR2YUV,COLOR_BGR2XYZ }; |
||||
|
||||
struct OilImage { |
||||
String winName = "Oil painting"; |
||||
int size; |
||||
int dynRatio; |
||||
int colorSpace; |
||||
Mat img; |
||||
}; |
||||
|
||||
const String keys = |
||||
"{Help h usage ? help | | Print this message }" |
||||
"{v | 0 | video index }" |
||||
"{a | 700 | API index }" |
||||
"{s | 10 | neighbouring size }" |
||||
"{d | 1 | dynamic ratio }" |
||||
"{c | 0 | color space }" |
||||
"{@arg1 | | file path}" |
||||
; |
||||
|
||||
|
||||
int main(int argc, char* argv[]) |
||||
{ |
||||
CommandLineParser parser(argc, argv, keys); |
||||
|
||||
if (parser.has("help")) |
||||
{ |
||||
parser.printMessage(); |
||||
return 0; |
||||
} |
||||
String filename = parser.get<String>(0); |
||||
OilImage p; |
||||
p.dynRatio = parser.get<int>("d"); |
||||
p.size = parser.get<int>("s"); |
||||
p.colorSpace = parser.get<int>("c"); |
||||
if (p.colorSpace < 0 || p.colorSpace >= static_cast<int>(colorSpace.size())) |
||||
{ |
||||
std::cout << "Color space must be >= 0 and <"<< colorSpace.size()<<"\n"; |
||||
return EXIT_FAILURE; |
||||
} |
||||
if (!filename.empty()) |
||||
{ |
||||
p.img = imread(filename); |
||||
if (p.img.empty()) |
||||
{ |
||||
std::cout << "Check file path!\n"; |
||||
return EXIT_FAILURE; |
||||
} |
||||
Mat dst; |
||||
xphoto::oilPainting(p.img, dst, p.size, p.dynRatio, colorSpace[p.colorSpace]); |
||||
imshow("oil painting effect", dst); |
||||
waitKey(); |
||||
return 0; |
||||
} |
||||
VideoCapture v(parser.get<int>("v")+ parser.get<int>("a")); |
||||
v>> p.img; |
||||
p.winName="Oil Painting"; |
||||
namedWindow(p.winName); |
||||
addSlider("DynRatio", p.winName, 1,127,p.dynRatio,&p.dynRatio, TrackSlider, &p); |
||||
addSlider("Size", p.winName, 1, 100, p.size, &p.size, TrackSlider, &p); |
||||
addSlider("ColorSpace", p.winName, 0, static_cast<int>(colorSpace.size()-1), p.colorSpace, &p.colorSpace, TrackSlider, &p); |
||||
while (waitKey(20) != 27) |
||||
{ |
||||
v>>p.img; |
||||
imshow("Original", p.img); |
||||
TrackSlider(0, &p); |
||||
waitKey(10); |
||||
} |
||||
return 0; |
||||
} |
||||
|
||||
void addSlider(String sliderName, String windowName, int minSlider, int maxSlider, int valDefault, int *valSlider, void(*f)(int, void *), void *r) |
||||
{ |
||||
createTrackbar(sliderName, windowName, valSlider, 1, f, r); |
||||
setTrackbarMin(sliderName, windowName, minSlider); |
||||
setTrackbarMax(sliderName, windowName, maxSlider); |
||||
setTrackbarPos(sliderName, windowName, valDefault); |
||||
} |
||||
|
||||
void TrackSlider(int , void *r) |
||||
{ |
||||
OilImage *p = (OilImage *)r; |
||||
Mat dst; |
||||
p->img = p->img / p->dynRatio; |
||||
p->img = p->img*p->dynRatio; |
||||
xphoto::oilPainting(p->img, dst, p->size, p->dynRatio,colorSpace[p->colorSpace]); |
||||
if (!dst.empty()) |
||||
{ |
||||
imshow(p->winName, dst); |
||||
} |
||||
} |
@ -0,0 +1,172 @@ |
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html
|
||||
|
||||
#include "opencv2/xphoto.hpp" |
||||
#include <opencv2/core.hpp> |
||||
#include <opencv2/imgproc.hpp> |
||||
|
||||
template<class T> |
||||
class Vec3fTo { |
||||
public : |
||||
cv::Vec3f a; |
||||
Vec3fTo(cv::Vec3f x) { |
||||
a = x; |
||||
}; |
||||
T extract(); |
||||
cv::Vec3f make(int); |
||||
}; |
||||
|
||||
template<> |
||||
uchar Vec3fTo<uchar>::extract() |
||||
{ |
||||
return static_cast<uchar>(a[0]); |
||||
} |
||||
|
||||
template<> |
||||
cv::Vec3b Vec3fTo<cv::Vec3b>::extract() |
||||
{ |
||||
return a; |
||||
} |
||||
|
||||
template<> |
||||
cv::Vec3f Vec3fTo<uchar>::make(int x) |
||||
{ |
||||
return cv::Vec3f((a*x)/x); |
||||
} |
||||
|
||||
template<> |
||||
cv::Vec3f Vec3fTo<cv::Vec3b>::make(int x) |
||||
{ |
||||
return cv::Vec3f(static_cast<float>(static_cast<int>(a[0]*x)/x), |
||||
static_cast<float>(static_cast<int>(a[1] * x) / x), |
||||
static_cast<float>(static_cast<int>(a[2] * x) / x)); |
||||
} |
||||
|
||||
namespace cv |
||||
{ |
||||
namespace xphoto |
||||
{ |
||||
template<typename Type> |
||||
class ParallelOilPainting : public ParallelLoopBody |
||||
{ |
||||
private: |
||||
Mat & imgSrc; |
||||
Mat &dst; |
||||
Mat &imgLuminance; |
||||
int halfsize; |
||||
int dynRatio; |
||||
|
||||
public: |
||||
ParallelOilPainting<Type>(Mat& img, Mat &d, Mat &iLuminance, int r,int k) : |
||||
imgSrc(img), |
||||
dst(d), |
||||
imgLuminance(iLuminance), |
||||
halfsize(r), |
||||
dynRatio(k) |
||||
{} |
||||
virtual void operator()(const Range& range) const CV_OVERRIDE |
||||
{ |
||||
std::vector<int> histogram(256); |
||||
std::vector<Vec3f> meanBGR(256); |
||||
|
||||
for (int y = range.start; y < range.end; y++) |
||||
{ |
||||
Type *vDst = dst.ptr<Type>(y); |
||||
for (int x = 0; x < imgSrc.cols; x++, vDst++) |
||||
{ |
||||
if (x == 0) |
||||
{ |
||||
histogram.assign(256, 0); |
||||
meanBGR.assign(256, Vec3f(0,0,0)); |
||||
for (int yy = -halfsize; yy <= halfsize; yy++) |
||||
{ |
||||
if (y + yy >= 0 && y + yy < imgSrc.rows) |
||||
{ |
||||
Type *vPtr = imgSrc.ptr<Type>(y + yy) + x - 0; |
||||
uchar *uc = imgLuminance.ptr(y + yy) + x - 0; |
||||
for (int xx = 0; xx <= halfsize; xx++, vPtr++, uc++) |
||||
{ |
||||
if (x + xx >= 0 && x + xx < imgSrc.cols) |
||||
{ |
||||
histogram[*uc]++; |
||||
meanBGR[*uc] += Vec3fTo<Type>(*vPtr).make(dynRatio); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
} |
||||
else |
||||
{ |
||||
for (int yy = -halfsize; yy <= halfsize; yy++) |
||||
{ |
||||
if (y + yy >= 0 && y + yy < imgSrc.rows) |
||||
{ |
||||
Type *vPtr = imgSrc.ptr<Type>(y + yy) + x - halfsize - 1; |
||||
uchar *uc = imgLuminance.ptr(y + yy) + x - halfsize - 1; |
||||
int xx = -halfsize - 1; |
||||
if (x + xx >= 0 && x + xx < imgSrc.cols) |
||||
{ |
||||
histogram[*uc]--; |
||||
meanBGR[*uc] -= Vec3fTo<Type>(*vPtr).make(dynRatio); |
||||
} |
||||
vPtr = imgSrc.ptr<Type>(y + yy) + x + halfsize; |
||||
uc = imgLuminance.ptr(y + yy) + x + halfsize; |
||||
xx = halfsize; |
||||
if (x + xx >= 0 && x + xx < imgSrc.cols) |
||||
{ |
||||
histogram[*uc]++; |
||||
meanBGR[*uc] += Vec3fTo<Type>(*vPtr).make(dynRatio); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
auto pos = distance(histogram.begin(), std::max_element(histogram.begin(), histogram.end())); |
||||
*vDst = Vec3fTo<Type>(meanBGR[pos] / histogram[pos]).extract(); |
||||
} |
||||
} |
||||
} |
||||
}; |
||||
|
||||
void oilPainting(InputArray src, OutputArray dst, int size, int dynValue) |
||||
{ |
||||
oilPainting(src, dst, size, dynValue, COLOR_BGR2GRAY); |
||||
} |
||||
|
||||
void oilPainting(InputArray _src, OutputArray _dst, int size, int dynValue,int code) |
||||
{ |
||||
CV_CheckType(_src.type(), _src.type() == CV_8UC1 || _src.type() == CV_8UC3, "only 1 or 3 channels (CV_8UC)"); |
||||
CV_Assert(_src.kind() == _InputArray::MAT); |
||||
CV_Assert(size >= 1); |
||||
CV_CheckGT(dynValue , 0,"dynValue must be 0"); |
||||
CV_CheckLT(dynValue, 128, "dynValue must less than 128 "); |
||||
Mat src = _src.getMat(); |
||||
Mat lum,dst(_src.size(),_src.type()); |
||||
if (src.type() == CV_8UC3) |
||||
{ |
||||
cvtColor(_src, lum, code); |
||||
if (lum.channels() > 1) |
||||
{ |
||||
extractChannel(lum, lum, 0); |
||||
} |
||||
} |
||||
else |
||||
lum = src.clone(); |
||||
double dratio = 1 / double(dynValue); |
||||
lum.forEach<uchar>([=](uchar &pixel, const int * /*position*/) { pixel = saturate_cast<uchar>(cvRound(pixel * dratio)); }); |
||||
if (_src.type() == CV_8UC1) |
||||
{ |
||||
ParallelOilPainting<uchar> oilAlgo(src, dst, lum, size, dynValue); |
||||
parallel_for_(Range(0, src.rows), oilAlgo); |
||||
} |
||||
else |
||||
{ |
||||
ParallelOilPainting<Vec3b> oilAlgo(src, dst, lum, size, dynValue); |
||||
parallel_for_(Range(0, src.rows), oilAlgo); |
||||
} |
||||
dst.copyTo(_dst); |
||||
dst = (dst / dynValue) * dynValue; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,108 @@ |
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
#include "test_precomp.hpp" |
||||
|
||||
namespace opencv_test { namespace { |
||||
|
||||
Mat testOilPainting(Mat imgSrc, int halfSize, int dynRatio, int colorSpace) |
||||
{ |
||||
vector<int> histogramme; |
||||
vector<Vec3f> moyenneRGB; |
||||
Mat dst(imgSrc.size(), imgSrc.type()); |
||||
Mat lum; |
||||
if (imgSrc.channels() != 1) |
||||
{ |
||||
cvtColor(imgSrc, lum, colorSpace); |
||||
if (lum.channels() > 1) |
||||
{ |
||||
extractChannel(lum, lum, 0); |
||||
} |
||||
} |
||||
else |
||||
lum = imgSrc.clone(); |
||||
lum = lum / dynRatio; |
||||
if (dst.channels() == 3) |
||||
for (int y = 0; y < imgSrc.rows; y++) |
||||
{ |
||||
Vec3b *vDst = dst.ptr<Vec3b>(y); |
||||
for (int x = 0; x < imgSrc.cols; x++, vDst++) //for each pixel
|
||||
{ |
||||
Mat mask(lum.size(), CV_8UC1, Scalar::all(0)); |
||||
Rect r(Point(x - halfSize, y - halfSize), Size(2 * halfSize + 1, 2 * halfSize + 1)); |
||||
r = r & Rect(Point(0, 0), lum.size()); |
||||
mask(r).setTo(255); |
||||
int histSize[] = { 256 }; |
||||
float hranges[] = { 0, 256 }; |
||||
const float* ranges[] = { hranges }; |
||||
Mat hist; |
||||
int channels[] = { 0 }; |
||||
calcHist(&lum, 1, channels, mask, hist, 1, histSize, ranges, true, false); |
||||
double maxVal = 0; |
||||
Point pMin, pMax; |
||||
minMaxLoc(hist, 0, &maxVal, &pMin, &pMax); |
||||
mask.setTo(0, lum != static_cast<int>(pMax.y)); |
||||
Scalar v = mean(imgSrc, mask); |
||||
*vDst = Vec3b(static_cast<uchar>(v[0]), static_cast<uchar>(v[1]), static_cast<uchar>(v[2])); |
||||
} |
||||
} |
||||
else |
||||
for (int y = 0; y < imgSrc.rows; y++) |
||||
{ |
||||
uchar *vDst = dst.ptr<uchar>(y); |
||||
for (int x = 0; x < imgSrc.cols; x++, vDst++) //for each pixel
|
||||
{ |
||||
Mat mask(lum.size(), CV_8UC1, Scalar::all(0)); |
||||
Rect r(Point(x - halfSize, y - halfSize), Size(2 * halfSize + 1, 2 * halfSize + 1)); |
||||
r = r & Rect(Point(0, 0), lum.size()); |
||||
mask(r).setTo(255); |
||||
int histSize[] = { 256 }; |
||||
float hranges[] = { 0, 256 }; |
||||
const float* ranges[] = { hranges }; |
||||
Mat hist; |
||||
int channels[] = { 0 }; |
||||
calcHist(&lum, 1, channels, mask, hist, 1, histSize, ranges, true, false); |
||||
double maxVal = 0; |
||||
Point pMin, pMax; |
||||
minMaxLoc(hist, 0, &maxVal, &pMin, &pMax); |
||||
mask.setTo(0, lum != static_cast<int>(pMax.y)); |
||||
Scalar v = mean(imgSrc, mask); |
||||
*vDst = static_cast<uchar>(v[0]); |
||||
} |
||||
} |
||||
return dst; |
||||
} |
||||
|
||||
TEST(xphoto_oil_painting, regression) |
||||
{ |
||||
string folder = string(cvtest::TS::ptr()->get_data_path()) + "cv/inpaint/"; |
||||
Mat orig = imread(folder+"exp1.png", IMREAD_COLOR); |
||||
ASSERT_TRUE(!orig.empty()); |
||||
resize(orig, orig, Size(100, 100)); |
||||
Mat dst1, dst2, dd; |
||||
xphoto::oilPainting(orig, dst1, 3, 5, COLOR_BGR2GRAY); |
||||
dst2 = testOilPainting(orig, 3, 5, COLOR_BGR2GRAY); |
||||
absdiff(dst1, dst2, dd); |
||||
vector<Mat> plane; |
||||
split(dd, plane); |
||||
for (auto p : plane) |
||||
{ |
||||
double maxVal; |
||||
Point pIdx; |
||||
minMaxLoc(p, NULL, &maxVal, NULL, &pIdx); |
||||
ASSERT_LE(p.at<uchar>(pIdx), 2); |
||||
} |
||||
Mat orig2 = imread(folder + "exp1.png",IMREAD_GRAYSCALE); |
||||
ASSERT_TRUE(!orig2.empty()); |
||||
resize(orig2, orig2, Size(100, 100)); |
||||
Mat dst3, dst4, ddd; |
||||
xphoto::oilPainting(orig2, dst3, 3, 5, COLOR_BGR2GRAY); |
||||
dst4 = testOilPainting(orig2, 3, 5, COLOR_BGR2GRAY); |
||||
absdiff(dst3, dst4, ddd); |
||||
double maxVal; |
||||
Point pIdx; |
||||
minMaxLoc(ddd, NULL, &maxVal, NULL, &pIdx); |
||||
ASSERT_LE(ddd.at<uchar>(pIdx), 2); |
||||
} |
||||
|
||||
}} // namespace
|
After Width: | Height: | Size: 176 KiB |
After Width: | Height: | Size: 112 KiB |
@ -0,0 +1,23 @@ |
||||
Oil painting effect {#tutorial_xphoto_oil_painting_effect} |
||||
=================================================== |
||||
|
||||
Introduction |
||||
------------ |
||||
Image is converted in a color space default color space COLOR_BGR2GRAY. |
||||
For every pixel in the image a program calculated a histogram (first plane of color space) of the neighbouring of size 2*size+1. |
||||
and assigned the value of the most frequently occurring value. The result looks almost like an oil painting. Parameter 4 of oilPainting is used to decrease image dynamic and hence increase oil painting effect. |
||||
|
||||
Example |
||||
-------------------- |
||||
|
||||
|
||||
@code{.cpp} |
||||
Mat img; |
||||
Mat dst; |
||||
img = imread("opencv/samples/data/baboon.jpg"); |
||||
xphoto::oilPainting(img, dst, 10, 1, COLOR_BGR2Lab); |
||||
imshow("oil painting effect", dst); |
||||
@endcode |
||||
|
||||
Original  |
||||
Oil painting effect  |
Loading…
Reference in new issue