这两天闲着没事,觉得将图像内容用ASCII码显示出来挺有意思的,就自己尝试着写了写,到网上一搜才知道原来这东西还是一门艺术-ASCII art!!
咱也跟艺术沾边了O(∩_∩)O哈哈哈~
闲话少说,切入正题,将图像内容用ASCII码显示出来主要就是用不同的字符表示灰度,以此来区分不同的灰度值达到将内容可视化的目的,这个道理很简单,实现起来其实也不难,如果是直接输入到控制台或者txt文件的则比较简单,主要是需要设置一下字体的宽高,最好是正方形的,否则看起来会比较别扭。下面一个是将lena缩放到100*100之后在控制台窗口直接显示的截图,采用的字符是标点,字体设置的宽高相等为8*8:
但是这种方式无法保存成图像(当然可以截图,不过那多费劲!),所以就研究了一下如何将字符直接写到图像上,这样保存图像就可以了。这时关键的问题就是如何将字符写到图像上,并且尽量让字符的宽高相等,这样看起来才不会觉得图像被挤过了⊙﹏⊙b汗!
还是用OpenCV来将字符写到图像上,使用putText函数,不过OpenCV中的字体好像不能设置为等宽高,没办法,就取了折中的方法,将用来代替灰度的所有字符的宽度和高度取了个平均作为字符宽高,这样就将原图像中的一个像素替换为这样一个字符,而且还设置了几个选项:可以选择标点,字母或者数字来代替灰度,并且可以选择输出二值,灰度或者彩色ascii图像-----文中只使用了8个灰度级,即将整幅图像量化为了8个灰度级,这样的缺点就是对灰度分布比较集中的图像失真比较大,可以先对图像进行灰度均衡化处理之后再转换,这里没有考虑这些,下面是主要的函数代码。
- char ascii_code_symbol[CODE_SIZE-7] = {'#','&','$','*','+',';','.',' ',0};
- char ascii_code_letter[CODE_SIZE-7] = {'m','n','e','f','t','l','i',' ',0};
- char ascii_code_number[CODE_SIZE-7] = {'8','9','5','3','2','7','1',' ',0};
- char *ascii_code_8[3] = { ascii_code_symbol, ascii_code_letter, ascii_code_number};
- //check the input image size and return a defined size,
- // that's the max one of width and height is not bigger than 100;
- cv::Size get_board_size(Mat &image)
- {
- if(image.empty())
- return Size(0,0);
- int f = 0;
- float big=(float)image.rows, smal=(float)image.cols;
- if(image.cols>image.rows)
- {
- f = 1;
- big = float(image.cols);
- smal = float(image.rows);
- }
- cv::Size board_size;
- if(big <= 100.f)
- {
- board_size = cv::Size(image.cols, image.rows);
- }
- else
- {
- board_size = cv::Size(int(f==1?100:(100*image.cols/big)), int(f==1?(100*image.rows/big):100));
- }
- return board_size;
- }
- //get the char code step
- int get_char_size(const string &asciiStr)
- {
- //init font
- int fontFace = FONT_HERSHEY_PLAIN;
- double fontScale = 0.5;
- int thickness = 1;
- //max_size is the max of all the char code width and height,
- //or return the average size of all the char code width and height;
- int max_size = 0;
- int total_size = 0;
- for (size_t i=0; i<asciiStr.size(); ++i)
- {
- int baseline = 0;
- string str(1, asciiStr[i]);
- cv::Size textsize = getTextSize(str, fontFace, fontScale, thickness, &baseline);
- if(max_size < textsize.height+baseline)
- max_size = textsize.height+baseline;
- if(max_size < textsize.width)
- max_size = textsize.width;
- total_size += (textsize.height+textsize.width+baseline);
- }
- return total_size/(asciiStr.size()*2);
- // return max_size;
- }
- /*
- convert image to ascii image;
- code_type: 0-symbol,1-letter,2-number
- color_type: 0-binary,1-gray,2-color
- */
- Mat image_to_ascii(Mat &image, int code_type, int color_type)
- {
- //check input image
- if(image.empty() || code_type<0 || code_type>2 || code_type<0)
- return Mat();
- //if input is a gray image, set color_type to gray;
- if(image.channels()==1&&code_type==2)
- color_type = 1;
- //create the output image
- int char_size = get_char_size(ascii_code_8[code_type]);
- cv::Size board_size = get_board_size(image);
- Mat out_image(char_size*board_size.height, char_size*board_size.width, CV_8UC3,Scalar::all(0));
- //resize the input image to defined size;
- Mat resized_image;
- resize(image, resized_image, board_size);
- Mat gray_image;
- if(resized_image.channels() == 3)
- cvtColor(resized_image, gray_image, COLOR_BGR2GRAY);
- else
- gray_image = resized_image;
- //font init
- int fontFace = FONT_HERSHEY_PLAIN;
- double fontScale = 0.5;
- int thickness = 1;
- //print char code to the output image
- for (int i=0; i<resized_image.rows; ++i)
- {
- uchar *ptr_bgr = resized_image.ptr<uchar>(i);
- uchar *ptr_gray = gray_image.ptr<uchar>(i);
- for (int j=0; j<resized_image.cols; ++j)
- {
- //get pixel
- uchar pix_gray = ptr_gray[j];
- //prepare the char code and coordinate;
- cv::Point textOrg(j*char_size, (i+1)*char_size);
- string text(1, ascii_code_8[code_type][8-(pix_gray>>5)]);
- //colored or not
- if (color_type==0)
- {
- putText(out_image, text, textOrg, fontFace, fontScale, Scalar::all(255));
- }
- else if(color_type==1)
- {
- putText(out_image, text, textOrg, fontFace, fontScale, Scalar::all(pix_gray));
- }
- else
- {
- putText(out_image, text, textOrg, fontFace, fontScale, Scalar(ptr_bgr[3*j],ptr_bgr[3*j+1],ptr_bgr[3*j+2]));
- }
- }
- }
- return out_image;
- }
char ascii_code_symbol[CODE_SIZE-7] = {'#','&','$','*','+',';','.',' ',0};
char ascii_code_letter[CODE_SIZE-7] = {'m','n','e','f','t','l','i',' ',0};
char ascii_code_number[CODE_SIZE-7] = {'8','9','5','3','2','7','1',' ',0};
char *ascii_code_8[3] = { ascii_code_symbol, ascii_code_letter, ascii_code_number};
//check the input image size and return a defined size,
// that's the max one of width and height is not bigger than 100;
cv::Size get_board_size(Mat &image)
{
if(image.empty())
return Size(0,0);
int f = 0;
float big=(float)image.rows, smal=(float)image.cols;
if(image.cols>image.rows)
{
f = 1;
big = float(image.cols);
smal = float(image.rows);
}
cv::Size board_size;
if(big <= 100.f)
{
board_size = cv::Size(image.cols, image.rows);
}
else
{
board_size = cv::Size(int(f==1?100:(100*image.cols/big)), int(f==1?(100*image.rows/big):100));
}
return board_size;
}
//get the char code step
int get_char_size(const string &asciiStr)
{
//init font
int fontFace = FONT_HERSHEY_PLAIN;
double fontScale = 0.5;
int thickness = 1;
//max_size is the max of all the char code width and height,
//or return the average size of all the char code width and height;
int max_size = 0;
int total_size = 0;
for (size_t i=0; i<asciiStr.size(); ++i)
{
int baseline = 0;
string str(1, asciiStr[i]);
cv::Size textsize = getTextSize(str, fontFace, fontScale, thickness, &baseline);
if(max_size < textsize.height+baseline)
max_size = textsize.height+baseline;
if(max_size < textsize.width)
max_size = textsize.width;
total_size += (textsize.height+textsize.width+baseline);
}
return total_size/(asciiStr.size()*2);
// return max_size;
}
/*
convert image to ascii image;
code_type: 0-symbol,1-letter,2-number
color_type: 0-binary,1-gray,2-color
*/
Mat image_to_ascii(Mat &image, int code_type, int color_type)
{
//check input image
if(image.empty() || code_type<0 || code_type>2 || code_type<0)
return Mat();
//if input is a gray image, set color_type to gray;
if(image.channels()==1&&code_type==2)
color_type = 1;
//create the output image
int char_size = get_char_size(ascii_code_8[code_type]);
cv::Size board_size = get_board_size(image);
Mat out_image(char_size*board_size.height, char_size*board_size.width, CV_8UC3,Scalar::all(0));
//resize the input image to defined size;
Mat resized_image;
resize(image, resized_image, board_size);
Mat gray_image;
if(resized_image.channels() == 3)
cvtColor(resized_image, gray_image, COLOR_BGR2GRAY);
else
gray_image = resized_image;
//font init
int fontFace = FONT_HERSHEY_PLAIN;
double fontScale = 0.5;
int thickness = 1;
//print char code to the output image
for (int i=0; i<resized_image.rows; ++i)
{
uchar *ptr_bgr = resized_image.ptr<uchar>(i);
uchar *ptr_gray = gray_image.ptr<uchar>(i);
for (int j=0; j<resized_image.cols; ++j)
{
//get pixel
uchar pix_gray = ptr_gray[j];
//prepare the char code and coordinate;
cv::Point textOrg(j*char_size, (i+1)*char_size);
string text(1, ascii_code_8[code_type][8-(pix_gray>>5)]);
//colored or not
if (color_type==0)
{
putText(out_image, text, textOrg, fontFace, fontScale, Scalar::all(255));
}
else if(color_type==1)
{
putText(out_image, text, textOrg, fontFace, fontScale, Scalar::all(pix_gray));
}
else
{
putText(out_image, text, textOrg, fontFace, fontScale, Scalar(ptr_bgr[3*j],ptr_bgr[3*j+1],ptr_bgr[3*j+2]));
}
}
}
return out_image;
}
下面是采用数字的ascii字符的彩色和灰度以及二值lena图像,【对lena图像来说数字的看起来比较好,其他两种效果不如数字的】
可以看出效果还是比较好的,O(∩_∩)O,
当然这里只是简单的将像素进行了替换是基于灰度的,更高级的可以有基于结构的,如边缘,生成更加简练的内容表达,以后有时间再研究研究~~