iOS实现字符串动画 灰度图片

前言

最近网上看了一个叫BadApple的字符串动画,颇有意思。做者是用python+OpenCv实现的转换流程。而OpenCv又是一个开源项目,咱们为何不能把这个效果移植到iOS手机上呢?html

原文地址python

正文

首先先放一下效果图:ios

 

image

 

本次文章说起的相关内容demo地址c++

视频网盘地址:git

连接:pan.baidu.com/s/1GjSzUKIn… 密码:lojqgithub

咱们想实现这么一个效果,天然不能只是纯粹地去翻写代码,咱们首先得知道原理,才能知道为何要这么作?算法

原理

ps: 这里的原理不必定那么专业,其中结合了本人的理解,若有错误,还望指出数组

首先,视频的本质是声音+图像,这里由于声音相关内容并不是咱们的研究方向,因此暂时省略。ide

因此,咱们要将视频转换成字符串动画,首先就须要知道一点,图像是如何转成字符串的字体

图像转成字符串

咱们在研究如何转化前,还须要大概了解下图像的存储

图像的存储

一个图像是由许许多多的小方格组成的,这些小方格都有明确的位置和颜色,这些小方格的位置与颜色最终决定了图像的样子,这些小方格也被称做像素

图像的显示

图像在屏幕上的显示,即是将图像的每个像素点的颜色渲染在屏幕上的对应位置处,咱们就看到对应图片了。

计算机中通常采用Bitmap(位图),来显示图像

Bitmap通常采用二维数组进行存取,二维数组中储存的即是该像素的颜色

颜色

计算机中颜色通常是采用RGB通道(在iOS中,我没有用过其余),也就是说一个颜色是由三种基础颜色(红Red绿Green蓝Blue)通道叠加生成的。全部颜色都可由三种颜色通道的不一样强度组成。

咱们一般用到图像通常是8位图像,在这里也能够说是三种颜色的强度能够从0 ~ (2^8 - 1 = 255)一共256个强度等级,而能够显示的总颜色数量就是256^3。

至此,图像就由一个具象的画面,变成了抽象的数据了。

灰度图像

由于彩色图像使用三个颜色通道描述一个像素点的颜色,咱们也能够认为一张彩色图像拥有三个维度。

可是,若是咱们但愿以字符串显示的话,咱们只能展现其中的一个维度(这里不考虑使用富文本时候字符串能够展现多个颜色的状况)。

因此咱们须要对彩色图像进行降维处理,也就是经过某种算法使得咱们可使用一个颜色通道即可以描述出图像的特色。

灰度图像正是为了知足咱们的这种需求而出现的。

定义

在计算机领域中, 灰度 (Gray scale) 数字图像是每一个像素只有一个采样颜色的图像。 这类图像一般显示为从最暗黑色到最亮的白色的灰度 ,尽管理论上这个采样能够是任何颜色的不一样深浅,甚至能够是不一样亮度上的不一样颜色。 灰度图像与黑白图像不一样,在计算机图像领域中黑白图像只有黑白两种颜色,灰度图像在黑色与白色之间还有许多级的颜色深度。 可是,在数字图像领域以外,「黑白图像」也表示「灰度图像」,例如灰度的照片一般叫作「黑白照片」。 在一些关于数字图像的文章中单色图像等同于灰度图像,在另一些文章中又等同于黑白图像。

转换公式

  1. 浮点算法:Gray = R * 0.3 + G * 0.59+B * 0.11
  2. 整数方法:Gray = (R * 30 + G * 59 + B * 11)/100
  3. 移位方法:Gray = (R * 76 + G * 151 + B * 28)>>8;
  4. 平均值法:Gray =(R + G + B)/ 3;
  5. 仅取绿色:Gray = G;

为何要选取灰度图像?

字符串图像,意味着图像的每个像素均是使用一个字符进行表示的。而字符是一个一维的变量(在这种条件下,咱们忽略了每个字符的字体、颜色、字号差异)。因此咱们须要一个一样是一维的图像——灰度图像

如何将灰度像素转换成字符串

这里提供一个灰度值对应的字符串表示数组:

@[@"$", @"@", @"B", @"%", @"8", @"&", @"W", @"M", @"#", @"*", @"o", @"a", @"h", @"k", @"b", @"d", @"p", @"q", @"w", @"m", @"Z", @"0", @"o", @"Q", @"L", @"C", @"J", @"U", @"Y", @"X", @"z", @"c", @"v", @"u", @"n", @"x", @"r", @"j", @"f", @"t", @"/", @"\\", @"|", @"(", @")", @"1", @"{", @"}", @"[", @"]", @"?", @"-", @"_", @"+", @"~", @"<", @">", @"i", @"!", @"l", @"I", @";", @":", @",", @"\"", @"^", @"", @"'", @".", @" "]`

咱们根据对应灰度图像的每个像素的Gray值,获取对应Gray值的百分比(即 Gray * 1.f / 255.0)。而后在根据百分比,得到对应的字符,拼接成一个完整的字符串。这个字符串,即是字符串图像

流程图解

 

image

 

过程实现

咱们已经分析过基础原理了,接下来开始编写应用吧

首先固然是视频了,若是想直接观看原视频,请点击这里

OpenCV 配置

什么是OpenCV?

OpenCV的全称是Open Source Computer Vision Library,是一个跨平台的计算机视觉库。OpenCV是由英特尔公司发起并参与开发,以BSD许可证受权发行,能够在商业和研究领域中无偿使用。OpenCV可用于开发实时的图像处理、计算机视觉以及模式识别程序。

iOS 配置OpenCV

一共有三种方法能够配置OpenCV

  1. 直接从源码编译(不过多介绍,由于我也不是很了解)
  2. 从CocoaPod导入(由于折腾起来感受很费劲,所以也不过多介绍)
  3. 下载打包好的iOS Framework

这里我只介绍第3种

咱们建立好对应工程后,将解压过的Framework拖入到项目后咱们须要添加以下依赖库:

AVFoundation.framework
AssetsLibrary.framework
CoreMedia.framework
CoreVideo.framework
复制代码

这里是我目前为止须要的依赖库,用以保证咱们的项目编译不报错

处理图像

首先,由于OpenCV是c++ 代码,因此咱们全部但愿和OpenCV进行交互的类,后缀均须要改为.mm文件以使编译器识别c++ 语法

接下来导入头文件

#import <opencv2/opencv.hpp>
#import <opencv2/imgproc/types_c.h>
#import <opencv2/imgcodecs/ios.h>
#import <opencv2/videoio/cap_ios.h>
#import <opencv2/core/core.hpp>
#import <opencv2/highgui/highgui.hpp>
复制代码

这些头文件须要在咱们导入其余文件以前导入,也就是说须要写在最上面,不然会报错。

接下来我按照上面的步骤依次写出图像转字符串操做:

// 先声明一个结构体用以标明长和宽
typedef struct{
    int width, height;
}SizeT;
复制代码
  1. 图片缩放
/// 根据给定大小缩放图片
- (UIImage *)resizeImage:(UIImage *)image withSize:(SizeT)size {
    /// cv::Mat 对象为对应的图片的二维数组对象,咱们能够很方便使用角标进行数组操做
    cv::Mat cvImage;
    /// 将UIImage对象转换为对应cv::Mat对象
    UIImageToMat(image, cvImage);
    cv::Mat reSizeImage;
    /// 从新赋值大小
    cv::resize(cvImage, reSizeImage, cv::Size(size.width, size.height));
    /// 释放
    cvImage.release();
    /// 生成新的UIImage
    UIImage *nImage = MatToUIImage(reSizeImage);
    /// 释放
    reSizeImage.release();
    return nImage;
}
复制代码
  1. 彩色图转换为灰度图片
- (UIImage *)grayImage:(UIImage *)image {
    cv::Mat cvImage;

    UIImageToMat(image, cvImage);

    cv::Mat gray;
    // 将图像转换为灰度显示
    cv::cvtColor(cvImage, gray, CV_RGB2GRAY);

    cvImage.release();
    // 将灰度图片转成UIImage
    UIImage *nImage = MatToUIImage(gray);

    gray.release();

    return nImage;
}
复制代码
  1. 将灰度图片转换为字符串
- (NSString *)convertImage:(UIImage *)image {
    cv::Mat gray;
    
    UIImageToMat(image, gray);
    // 获取一共多少列
    int row = gray.rows;
    // 获取一共多少行
    int col = gray.cols;
    // 初始化字符串数组 用来存储图片的每一行
    NSMutableArray <NSString *>* array = [NSMutableArray arrayWithCapacity:row];
    // 给定字符串灰度对应值
    NSArray *pixels = @[@"$", @"@", @"B", @"%", @"8", @"&", @"W", @"M", @"#", @"*", @"o", @"a", @"h", @"k", @"b", @"d", @"p", @"q", @"w", @"m", @"Z", @"0", @"o", @"Q", @"L", @"C", @"J", @"U", @"Y", @"X", @"z", @"c", @"v", @"u", @"n", @"x", @"r", @"j", @"f", @"t", @"/", @"\\", @"|", @"(", @")", @"1", @"{", @"}", @"[", @"]", @"?", @"-", @"_", @"+", @"~", @"<", @">", @"i", @"!", @"l", @"I", @";", @":", @",", @"\"", @"^", @"`", @"'", @".", @" "];;

    for (int i = 0 ; i < row; i ++) {
        NSMutableArray <NSString *>*item = [NSMutableArray arrayWithCapacity:col];
        
        for (int j = 0; j < col; j ++) {
            // 取出对应灰度值
            int temp = gray.at<uchar>(i, j);
            // 计算灰度百分比
            CGFloat percent = temp / 255.f;
            // 根据百分比取出对应的字符
            int totalCount = (pixels.count - 1) * percent;
            // 加入到字符串数组里
            [item addObject:pixels[totalCount]];
        }
        // 将数组转成字符串
        [array addObject:[item componentsJoinedByString:@" "]];
    }

    gray.release();
    // 返回分好行后的字符串
    return [array componentsJoinedByString:@"\n"];
}
复制代码

这些步骤操做完毕后,咱们剩下的最后一个步骤,就是将字符串显示在Label上。

一些iOS上的坑

  1. 字符串图像首先得须要保证是等宽字体(由于非等宽字体将会产生图像移位,显示错乱),可是系统标准字体又是非等宽字体。

解决方案: 采用等宽字体Courier

  1. 在iOS上,UILabel若是采用居中设置的话,字符串渲染过程当中可能出现撕裂

解决方案: UILabel请所有采用默认设置(左对齐),若是须要居中,最好采用AutoLayout将Label设置居中

处理视频

视频的处理其实就是将视频的每一帧转换成图像,而后将图像转换成字符串。播放就是按照视频原有的每帧间隔将字符串显示在屏幕上,因而就变成动画了。

具体细节就不过多赘述了,能够看对应demo

结尾

图像处理实际上是很高深的学科,这里我只是很简单的将图像转换为字符串。

相关文章
相关标签/搜索