字符画实现基本思路
萌新小白为了实现这个功能真的是百度了很久很久,看了很多大佬的文章,终于我成功了Ψ( ̄∀ ̄)Ψ。现在来分享一下我的大致思路。
思路分析
若相对整个视频进行字符画处理,则需要对视频的每一帧进行处理,读取视频的每一帧的代码如下:
VideoCapture cap;
cap.open("F:/opencv/images/test01.MP4");
if (!cap.isOpened()) {
cout << "can't open file" << endl;
return -1;
}
Mat frame, frame_resize, frame_gray;
while (true)
{
cap >> frame;
if (frame.empty()) break;
}
因为要输出原始图像的大小,我们需要对图像的尺寸进行重新调整,然后将图片转换为灰度图(后面需要根据灰度等级选择字符):
resize(frame, frame_resize, Size(300, 90));
cvtColor(frame_resize, frame_gray, COLOR_BGR2GRAY);
接下来就是要对图像的每个像素进行遍历,根据灰度等级划分字符,如果想要更加明显的效果可以选用更多的字符。
因为最后要将字符串进行输出,所以我们首先对字符串进行初始化,然后依次加上每个像素的字符。
String txt = "";
int height = frame_gray.rows;
int width = frame_gray.cols;
for (int j = 0; j < height; j++) {
for (int i = 0; i < width; i++)
{
int pdata = frame_gray.ptr<uchar>(j)[i];
if (pdata == 255)
txt += char_list[87];
else
txt += char_list[pdata / len];
}
txt += "\n";
}
这样的话一帧就处理好啦,紧接着直接将字符串直接进行输出就可以了:
cout << txt;//输出当前字符
如果你看到这里就去尝试的话,很快就会发现输出的字符串很吓人,让你眼花缭乱Σ(っ °Д °;)っ。这是因为在你的编译器中,字符默认大小比较大,而一张图像又有很多个像素,图像肯定会变大了,解决这个问题的最好办法就是直接改变编译器输出的字体大小。
我用的是VS2019,可以右键单击控制台窗口后点击默认值,会出来这个窗口:
我这里将字体大小改为了5,你们可以根据自己视频的情况进行更改,只要能让窗口大小大于一帧字符串的大小即可。
然后我们就实现了基本的字符画功能了ψ(`∇´)ψ,但是可能细心的小朋友又会发现字符画视频显示的很不稳定,甚至闪的眼睛疼。这个时候我们可以在程序里面加点内容就好啦:
//将光标移动到(0,0)处
HANDLE hConsoleOutput = GetStdHandle(STD_OUTPUT_HANDLE);
COORD pos;
pos.X = 0;
pos.Y = 0;
SetConsoleCursorPosition(hConsoleOutput, pos);
每次改变帧数的时候都将光标移动到(0, 0)处,大家再也不用担心闪眼睛了w(゚Д゚)w。代码参考了好多大佬的文章,附上一张我自己测试的图片啊:
不知道能不能看出来这是一坨什么w(゚Д゚)w,好吧其实它是奥特曼里面的怪兽。
源代码
#include <opencv2/opencv.hpp>
#include <iostream>
#include <Windows.h>
using namespace cv;
using namespace std;
const char* char_list = " .,-'`:!1+*abcdefghijklmnopqrstuvwxyz<>()\/{}[]?234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ%&@#$";
int len = 255 / strlen(char_list);
int main()
{
VideoCapture cap;
cap.open("F:/opencv/images/test01.MP4");
if (!cap.isOpened()) {
cout << "can't open file" << endl;
return -1;
}
Mat frame, frame_resize, frame_gray;
while (true)
{
cap >> frame;
if (frame.empty()) break;
resize(frame, frame_resize, Size(300, 90));
cvtColor(frame_resize, frame_gray, COLOR_BGR2GRAY);
String txt = "";
int height = frame_gray.rows;
int width = frame_gray.cols;
for (int j = 0; j < height; j++) {
for (int i = 0; i < width; i++)
{
int pdata = frame_gray.ptr<uchar>(j)[i];
if (pdata == 255)
txt += char_list[87];
else
txt += char_list[pdata / len];
}
txt += "\n";
}
//将光标移动到(0,0)处
HANDLE hConsoleOutput = GetStdHandle(STD_OUTPUT_HANDLE);
COORD pos;
pos.X = 0;
pos.Y = 0;
SetConsoleCursorPosition(hConsoleOutput, pos);
//输出当前字符
cout << txt;
}
return 0;
}