图像转铅笔素描
来源:互联网 发布:远光软件股票 编辑:程序博客网 时间:2024/04/27 23:27
Introduce
最近看了一篇论文《Combining Sketch and Tone for Pencil Drawing Production》,感觉很好玩,所以就想实现了一下,但是里面有些公式是在太难理解了,我就在github上找到了一份代码,对其进行了重构。
方法介绍
这篇论文中前面形成stroke structure还是比较好理解的,流程如下:
1)计算图像的梯度图像;
2)用8个核对梯度图像进行卷积,每个核为指向各个方向的直线,共形成8张卷积图像(卷积);
3)比较8张卷积图像,对应像素位置的值最大则保留,于是又形成8张图像(筛选);
4)对3)中形成的8张图像,进行卷积,卷积核为每张图像对应的卷积图像的生成核(强化);
5)将4)生成的8张图像相加,并规范化。
后面形成tone map,以及进行texture rendering包含一些固定参数,不容易推到出合适的,作者给出了一些推荐值,还有一些解公式之类的工作,这里无法叙述,可参详http://www.cnblogs.com/Imageshop/p/4285566.html 。
效果图
生成效果图如下:
两张图像对比还是前一张香蕉的图像更好看,观察了多张图片,我个人认为画面内容简单生成的图像更好看。
代码
除了前面部分是理解后实现的,其余代码大部分是重构,原始代码是https://github.com/HVisionSensing/PicWall,我的代码如下:
#include <stdio.h>#include <opencv2/core/core.hpp>#include <opencv2/imgproc/imgproc.hpp>#include <opencv2/highgui/highgui.hpp>//#define DEBUG_INFOvoid generate_conv_kernel(int ksize, float angle, int hardness, cv::Mat &kernel){ int halfSize = ksize / 2; int size = halfSize * 2 - 1; int y0 = halfSize - hardness + 1; int y1 = halfSize + hardness - 1; cv::Mat rect, rotateMat; cv::Point2i central; kernel = cv::Mat(size, size, CV_32FC1, cv::Scalar(0)); rect = cv::Mat(kernel, cv::Rect(0, y0, size, y1 - y0 + 1)); rect = cv::Scalar(255); central = cv::Point2i(halfSize, halfSize); rotateMat = cv::getRotationMatrix2D(central, angle, 1.0); cv::warpAffine(kernel, kernel, rotateMat, kernel.size());}void generate_stroke_structure(cv::Mat &gray, int hardness, int directions, float strength, cv::Mat &structure){ cv::Mat gradient; cv::Mat *kernels = new cv::Mat[directions]; cv::Mat *Gs = new cv::Mat[directions]; cv::Mat *Cs = new cv::Mat[directions]; int rows, cols; int ksize; assert(gray.depth() == CV_32F); rows = gray.rows; cols = gray.cols; { cv::Mat gx, gy; cv::Sobel(gray, gx, CV_32FC1, 1, 0); cv::Sobel(gray, gy, CV_32FC1, 0, 1); cv::magnitude(gx, gy, gradient);#ifdef DEBUG_INFO cv::imshow("gradient", gradient/255); cv::waitKey();#endif } ksize = (rows < cols ? rows : cols) / 30; for(int i = 0; i < directions; i++) { float angle = i * 180.0 / directions; generate_conv_kernel(ksize, angle, hardness, kernels[i]); cv::filter2D(gradient, Gs[i], -1, kernels[i]); Cs[i] = cv::Mat(rows, cols, CV_32FC1, cv::Scalar(0)); } for(int y = 0; y < rows; y++) { for(int x = 0; x < cols; x++) { int idx = 0; float mmax = 0, t; for(int i = 0; i < directions; i++) { if( (t = Gs[i].at<float>(y, x)) > mmax) { mmax = t; idx = i; } } Cs[idx].at<float>(y, x) = gradient.at<float>(y, x); } } structure = cv::Mat(rows, cols, CV_32FC1, cv::Scalar(0)); for(int i = 0; i < directions; i++) { cv::filter2D(Cs[i], Cs[i], -1, kernels[i]); structure += Cs[i]; } cv::normalize(structure, structure, 0, 1, cv::NORM_MINMAX); structure = 1 - structure * strength; delete[] kernels; delete[] Gs; delete[] Cs;}void calc_hist(uchar *data, int width, int height, int stride, float hist[]){ float sq = width * height; memset(hist, 0, sizeof(float) * 256); for(int y = 0; y < height; y++) { for(int x = 0; x < width; x++) hist[data[x]] ++; data += stride; } for(int i = 0; i < 256; i++) hist[i] /= sq;}void specific_hist(float *hist, float *target, float histMap[]){ float cumHist[256], dist[256 * 256], *ptr; int start = 0, end = 0, lastStart = 0, lastEnd = 0; cumHist[0] = hist[0]; for(int i = 1; i < 256; i++) cumHist[i] = cumHist[i-1] + hist[i]; ptr = dist; for(int y = 0; y < 256; y++) { for(int x = 0; x < 256; x++) ptr[x] = fabs(cumHist[x] - target[y]); ptr += 256; } ptr = dist; for(int y = 0; y < 256; y++) { float mmin = ptr[0]; for(int x = 1; x < 256; x++) { if(mmin >= ptr[x]) { mmin = ptr[x]; end = x; } } if(start != lastStart || end != lastEnd) { for(int x = start; x <= end; x++) histMap[x] = y; lastStart = start; lastEnd = end; start = end + 1; } ptr += 256; }}void generate_tone(cv::Mat &gray, float hist[], cv::Mat &tone){ float target[256], bright[256], dark[256], mid[256]; float sumB = 0, sumD = 0, sumM = 0, sum = 0; int rows = gray.rows; int cols = gray.cols; tone = cv::Mat(rows, cols, CV_32FC1, cv::Scalar(0)); for(int i = 0; i < 256; i++) { bright[i] = expf((i - 255.0) / 9); dark[i] = expf( (i - 90) * (i - 90) / -242.0); sumB += bright[i]; sumD += dark[i]; if( 105 <= i && i <= 225) mid[i] = 1; else mid[i] = 0; sumM += mid[i]; } sum = 0; for(int i = 0; i < 256; i++) { float b = bright[i] / sumB; float d = dark[i] / sumD; float m = mid[i] / sumM; sum += (52 * b + 37 * m + 11 * d) / 100; target[i] = sum; } { float histMap[256]; specific_hist(hist, target, histMap); for(int y = 0; y < rows; y++) { float *ptr1 = (float*)(gray.data + gray.step * y); float *ptr2 = (float*)(tone.data + tone.step * y); for(int x = 0; x < cols; x++) ptr2[x] = histMap[(int)ptr1[x]] / 255.0; } }}void texture_rendering(cv::Mat &tone, cv::Mat &image, cv::Mat &texture){ int rows = tone.rows; int cols = tone.cols; int ny = ceil(float(rows) / image.rows); int nx = ceil(float(cols) / image.cols); cv::Mat beta(rows, cols, CV_32FC1, cv::Scalar(1)); assert(tone.depth() == CV_32F && image.depth() == CV_32F); texture = cv::Mat(rows, cols, CV_32FC1, cv::Scalar(0)); if(ny > 1 || nx > 1) cv::repeat(image, ny, nx, image); cv::resize(image, image, cv::Size(cols, rows)); image /= 255; for(int x = 0; x < cols; x++) beta.at<float>(0, x) = logf(tone.at<float>(0, x) + FLT_EPSILON) / logf(image.at<float>(0, x) + FLT_EPSILON); for(int y = 0; y < rows; y++) beta.at<float>(y, 0) = logf(tone.at<float>(y, 0) + FLT_EPSILON) / logf(image.at<float>(y, 0) + FLT_EPSILON); for(int y = 1; y < rows; y++) { for(int x = 1; x < cols; x++) { float t = logf(image.at<float>(y, x) + FLT_EPSILON); float v = (t * logf(tone.at<float>(y, x) + FLT_EPSILON) + 0.2 * ( beta.at<float>(y-1, x) + beta.at<float>(y, x - 1) )) / (t * t + 0.4); beta.at<float>(y, x) = v; } }#ifdef DEBUG_INFO cv::imshow("beta", beta); cv::waitKey();#endif for(int y = 0; y < rows; y++) for(int x = 0; x < cols; x++) texture.at<float>(y, x) = powf(image.at<float>(y, x), beta.at<float>(y, x));}void generate_sketch_images(cv::Mat &img, cv::Mat &pencilSketch, cv::Mat &colorSketch){ assert(img.channels() == 3); cv::Mat gray, structure, tone, texture; cv::cvtColor(img, gray, cv::COLOR_BGR2GRAY); float hist[256]; calc_hist(gray.data, gray.cols, gray.rows, gray.step, hist); gray.convertTo(gray, CV_32FC1); generate_stroke_structure(gray, 1, 8, 0.9, structure);#ifdef DEBUG_INFO cv::imshow("structure", structure); cv::waitKey();#endif generate_tone(gray, hist, tone);#ifdef DEBUG_INFO cv::imshow("tone", tone); cv::waitKey();#endif texture_rendering(tone, gray, texture);#ifdef DEBUG_INFO cv::imshow("texture", texture); cv::waitKey();#endif pencilSketch = structure.mul(texture) * 255; pencilSketch.convertTo(pencilSketch, CV_8UC1);#ifdef DEBUG_INFO cv::imshow("pencil", pencilSketch); cv::waitKey();#endif { cv::Mat labImg; std::vector<cv::Mat> channels; cv::cvtColor(img, labImg, cv::COLOR_BGR2Lab); cv::split(labImg, channels); channels[0] = pencilSketch; cv::merge(channels, labImg); cv::cvtColor(labImg, colorSketch, cv::COLOR_Lab2BGR); }#ifdef DEBUG_INFO cv::imshow("color", colorSketch); cv::waitKey();#endif}int main(int argc, char **argv){ if(argc < 4) { printf("Usage: %s [input image] [output pencil image] [output color pencil image]\n", argv[0]); return 1; } cv::Mat img = cv::imread(argv[1], CV_LOAD_IMAGE_COLOR); cv::Mat pencil, colorPencil; generate_sketch_images(img, pencil, colorPencil); cv::imshow("pencil", pencil); cv::imshow("color pencil", colorPencil); cv::waitKey(); if(!cv::imwrite(argv[2], pencil)) { printf("Can't write image %s\n", argv[2]); return 2; } if(! cv::imwrite(argv[3], colorPencil)) { printf("Can't write image %s\n", argv[3]); return 3; } return 0;}
0 0
- 图像转铅笔素描
- ps铅笔素描效果教程
- PS快速把人物图片转为铅笔素描
- 将图片打造铅笔素描效果
- 图像处理-素描篇
- 素描图像提取方法
- java 图像特效之素描
- 素描
- 素描
- 素描
- 素描
- 图像滤镜艺术---(Sketch Filter)素描滤镜
- 图像特效---(Sketch Filter)素描滤镜
- photoshop图像滤镜——素描算法
- 图像处理软件开发记录(五) 图像特效(素描、油画)
- 将彩色图像变为素描图像,超简单!
- 【Android图像处理】图像处理之-素描效果
- c++ opencv 彩色转素描 素描转彩色
- 如何做实时监控?—— 参考 Spring Boot 实现
- 12v 1a 18w 开关电源原理图
- Swift - 集合类型
- linux下如何退出VI编辑器
- 数据下载 - delegate 形式的异步请求
- 图像转铅笔素描
- TextView异步加载HTML格式数据中的图片(解决4.0以上主线程加载失败)
- 3308 最長01串 哈希
- 【wechat】微信开发——自定义菜单
- 自定义layout实现瀑布流_UICollectionView
- 微信开发大坑汇总之微信支付篇
- 数据库知识
- 基数排序法
- 动态规划之最长不下降子序列