iOS實現字串動畫

chouheiwa發表於2019-04-23

前言

好久沒有動筆,也許是因為最近比較鬆懈,壓迫感不強。所以不知道該寫些什麼才好。

最近網上看了一個叫BadApple的字串動畫,很有意思。作者是用python+OpenCv實現的轉換流程。而OpenCv又是一個開源專案,我們為什麼不能把這個效果移植到iOS手機上呢?

原文地址

正文

首先先放一下效果圖:

image

本次文章提及的相關內容demo地址

視訊網盤地址:

連結:pan.baidu.com/s/1GjSzUKIn… 密碼:lojq

我們想實現這麼一個效果,自然不能只是純粹地去翻寫程式碼,我們首先得知道原理,才能知道為什麼要這麼做?

原理

ps: 這裡的原理不一定那麼專業,其中結合了本人的理解,如有錯誤,還望指出

首先,視訊的本質是聲音+影象,這裡因為聲音相關內容並非我們的研究方向,所以暫時省略。

所以,我們要將視訊轉換成字串動畫,首先就需要知道一點,影象是如何轉成字串的

影象轉成字串

我們在研究如何轉化前,還需要大概瞭解下影象的儲存

影象的儲存

一個影象是由許許多多的小方格組成的,這些小方格都有明確的位置和顏色,這些小方格的位置與顏色最終決定了影象的樣子,這些小方格也被稱作畫素

影象的顯示

影象在螢幕上的顯示,便是將影象的每一個畫素點的顏色渲染在螢幕上的對應位置處,我們就看到對應圖片了。

計算機中一般採用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

結尾

影象處理其實是很高深的學科,這裡我只是很簡單的將影象轉換為字串。

如果大家對於這個有什麼疑問,也可以關注本人公眾號,並私聊。看到都會回覆並解答的

公眾號

相關文章