Commit 7f5bb96d authored by LaurentBerger's avatar LaurentBerger Committed by Alexander Alekhin

Merge pull request #1750 from LaurentBerger:oilpainting

* Oil painting effect

* license

* try auto

* Insert test

* template

* review

* indentation in namespace

* remove back to future
parent de3041bc
......@@ -14,3 +14,9 @@
pages={1000--1008},
year={2015}
}
@book{Holzmann1988,
title={Beyond Photography: The Digital Darkroom},
author={GerPublished by ard J. Holzmann},
publisher={Prentice Hall in 1988}
}
\ No newline at end of file
......@@ -50,4 +50,5 @@
#include "xphoto/white_balance.hpp"
#include "xphoto/dct_image_denoising.hpp"
#include "xphoto/bm3d_image_denoising.hpp"
#include "xphoto/oilpainting.hpp"
#endif
// 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__
#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);
}
}
// 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;
}
}
}
// 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
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 ![](images/baboon.jpg)
Oil painting effect ![](images/baboon_oil_painting_effect.jpg)
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