油画原理:
一、获取图像灰度图,灰度值范围0~255
二、mask大小,比如5*5,计算mask对应灰度图像的灰度等级(0~level)
三、将当前mask,level占有最多数量的像素点,对应到彩色图像进行RGB求和,算平均值。
四、以此类推,最终得到油画特效后的彩色图像。
优化速度:
降低时间复杂度,根据不同mask尺寸,将时间复杂度由O(n^2) -> O(2n)。
mask移动方向,蛇形移动。
→ → → → → → →
↓
← ← ← ← ← ← ←
↓
→ → ...
每次移动不再刷新bucket,而是针对移动方向,如向右移动,剔除mask最左列,增加右侧进入列。因为中间像素点不影响油桶数值,故不重复处理。
代码如下:
#include
#include
using namespace std;
using namespace cv;
class oil
{
public:
oil(char *filename, int level, int radius);
~oil();
uint8_t getGrayVal(int x, int y);
void update_bucket(int x, int y, bool isIn);
void load_in(bool isFirst, int hor, int x, int y);
void update_RGB(int x, int y);
void process();
void save(char *filename);
int getWidth();
int getHeight();
private:
int *bucket; //存储level等级 个数和 r,g,b的和
Mat oil_image; //最终油画化图像
Mat image; //源图像
Mat gray; //灰度图像
int level; //等级
int radius; //半径
float scale; //灰度压缩比
int height; //图像高度
int width; //图像宽度
int len; //mask边长
};
oil::oil(char * filename, int level, int radius)
{
this->level = level;
this->radius = radius;
this->image = imread(filename, IMREAD_COLOR);
this->oil_image = this->image.clone();
this->scale = level / 256.0;
cvtColor(this->image, this->gray, COLOR_BGR2GRAY);
for (int i = 0; i < gray.rows; i++)
{
for (int j = 0; j < gray.cols; j++)
{
gray.at(i, j) = gray.at(i, j) * scale;
}
}
this->height = this->image.rows;
this->width = this->image.cols;
this->len = radius * 2 + 1;
this->bucket = new int[level * level];
memset(this->bucket, 0, level * level * 4);
}
oil::~oil()
{
delete []this->bucket;
}
uint8_t oil::getGrayVal(int x, int y)
{
return this->gray.at(y, x);
}
void oil::update_bucket(int x, int y, bool isIn) //更新bucket 4个地址分别为 相同level数量, r通道和, g通道和, b通道和
{
int current_level = this->getGrayVal(x, y);
Vec3b color = this->image.at(y, x);
if (isIn)
{
this->bucket[current_level * 4] += 1;
this->bucket[current_level * 4 + 1] += color[2]; // r
this->bucket[current_level * 4 + 2] += color[1]; // g
this->bucket[current_level * 4 + 3] += color[0]; // b
}
else
{
this->bucket[current_level * 4] -= 1;
this->bucket[current_level * 4 + 1] -= color[2]; // r
this->bucket[current_level * 4 + 2] -= color[1]; // g
this->bucket[current_level * 4 + 3] -= color[0]; // b
}
}
void oil::load_in(bool isFirst, int hor, int x, int y) //hor为1 代表mask向右移动, -1代表向左移动, 0代表向下移动
{
if (isFirst) //第一次转载bucket
{
int current_y = 0;
int current_x = 0;
for (int i = -this->radius; i < this->radius + 1; i++)
{
for (int j = -this->radius; j < this->radius + 1; j++)
{
current_y = y + i;
current_x = x + j;
this->update_bucket(current_x, current_y, true);
}
}
}
else
{
if (hor != 0)
{
int current_in_x = x + (this->radius + 1) * hor;
int current_out_x = x - this->radius * hor;
for (int i = -this->radius; i < this->radius + 1; i++)
{
int current_y = i + y;
this->update_bucket(current_in_x, current_y, true);
this->update_bucket(current_out_x, current_y, false);
}
}
else
{
int current_in_y = y + this->radius + 1;
int current_out_y = y - this->radius;
for (int i = -this->radius; i < this->radius + 1; i++)
{
int current_x = i + x;
this->update_bucket(current_x, current_in_y, true);
this->update_bucket(current_x, current_out_y, false);
}
}
}
}
void oil::update_RGB(int x, int y) //更新mask中心 油画特效后的RGB值
{
int most_level = 0;
int most_times = 0;
for (int i = 0; i < this->level; i++)
{
int times = this->bucket[i * 4];
if (times > most_times)
{
most_times = times;
most_level = i;
}
}
int r_sum = this->bucket[most_level * 4 + 1];
int g_sum = this->bucket[most_level * 4 + 2];
int b_sum = this->bucket[most_level * 4 + 3];
int current_r = r_sum / most_times;
int current_g = g_sum / most_times;
int current_b = b_sum / most_times;
this->oil_image.at(y, x) = Vec3b(current_b, current_g, current_r);
//cout << "r, g, b" << current_r << ", " << current_g << ", " << current_b << endl;
}
void oil::process() //处理流程 蛇形降低程序时间复杂度 根据不同mask边长 时间复杂度由O(n^2) -> O(2n)
{
/* mask移动方向
→ → → → → → →
↓
← ← ← ← ← ← ←
↓
→ → ...*/
bool isFirst = true;
this->load_in(isFirst, 0, this->radius, this->radius);
this->update_RGB(this->radius, this->radius);
int hor = 1;
isFirst = false;
int k = 0;
for(int i = this->radius; i < this->height - this->radius; i++)
{
//start = time.clock()
if( hor == 1 ) //向右
{
//cout << "向右" << endl;
//print('向右')
//cout << "right" << endl;
for (int j = this->radius; j < this->width - this->radius - 1; j++)
{
this->load_in(isFirst, hor, j, i);
this->update_RGB(j + 1, i);
k = j;
}
}
else
{
//cout << "向左" << endl;
//cout << "left" << endl;
for (int j = this->width - this->radius - 1; j > this->radius; j--)
{
this->load_in(isFirst, hor, j, i);
this->update_RGB(j - 1, i);
k = j;
}
}
//cout << "i " << i << " j " << k << endl;
if (i == this->height - this->radius - 1) //最后一行不继续往下
return;
if(hor == 1) //走到最右侧向下
{
this->load_in(isFirst, 0, this->width - this->radius - 1, i); //向下
this->update_RGB(this->width - this->radius - 1, i + 1);
}
else//走到最左侧向下
{
this->load_in(isFirst, 0, this->radius, i); //向下
this->update_RGB(this->radius, i + 1);
}
hor = -hor;
}
}
void oil::save(char * filename)
{
imwrite(filename, this->oil_image);
}
int oil::getWidth()
{
return this->width;
}
int oil::getHeight()
{
return this->height;
}
void main()
{
double start = getTickCount();
oil item("scene1.jpg", 8, 3);
item.process();
item.save("scene_oil.jpg");
double end = getTickCount();
cout << "pic size (w,h) >> " << "(" << item.getWidth() << ", " << item.getHeight() << ")" << endl;
cout << "end algorithm >> " << (end - start) / getTickFrequency() << endl;
system("pause");
}
结果:
对于1920*1080大小的图像,时间为0.3s