最近網上看了一個叫BadApple的字符串動畫,頗有意思。做者是用python+OpenCv實現的轉換流程。而OpenCv又是一個開源項目,咱們爲何不能把這個效果移植到iOS手機上呢?html
原文地址python
首先先放一下效果圖:ios
本次文章說起的相關內容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) 數字圖像是每一個像素只有一個採樣顏色的圖像。 這類圖像一般顯示爲從最暗黑色到最亮的白色的灰度 ,儘管理論上這個採樣能夠是任何顏色的不一樣深淺,甚至能夠是不一樣亮度上的不一樣顏色。 灰度圖像與黑白圖像不一樣,在計算機圖像領域中黑白圖像只有黑白兩種顏色,灰度圖像在黑色與白色之間還有許多級的顏色深度。 可是,在數字圖像領域以外,「黑白圖像」也表示「灰度圖像」,例如灰度的照片一般叫作「黑白照片」。 在一些關於數字圖像的文章中單色圖像等同於灰度圖像,在另一些文章中又等同於黑白圖像。
轉換公式
爲何要選取灰度圖像?
字符串圖像,意味着圖像的每個像素均是使用一個字符進行表示的。而字符是一個一維的變量(在這種條件下,咱們忽略了每個字符的字體、顏色、字號差異)。因此咱們須要一個一樣是一維的圖像——灰度圖像
如何將灰度像素轉換成字符串
這裏提供一個灰度值對應的字符串表示數組:
@[@"$", @"@", @"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
)。而後在根據百分比,得到對應的字符,拼接成一個完整的字符串。這個字符串,即是字符串圖像了
咱們已經分析過基礎原理了,接下來開始編寫應用吧
首先固然是視頻了,若是想直接觀看原視頻,請點擊這裏
什麼是OpenCV?
OpenCV的全稱是Open Source Computer Vision Library,是一個跨平臺的計算機視覺庫。OpenCV是由英特爾公司發起並參與開發,以BSD許可證受權發行,能夠在商業和研究領域中無償使用。OpenCV可用於開發實時的圖像處理、計算機視覺以及模式識別程序。
iOS 配置OpenCV
一共有三種方法能夠配置OpenCV
這裏我只介紹第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; 複製代碼
/// 根據給定大小縮放圖片 - (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; } 複製代碼
- (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; } 複製代碼
- (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上的坑
解決方案: 採用等寬字體Courier
解決方案: UILabel請所有采用默認設置(左對齊),若是須要居中,最好採用AutoLayout將Label設置居中
視頻的處理其實就是將視頻的每一幀轉換成圖像,而後將圖像轉換成字符串。播放就是按照視頻原有的每幀間隔將字符串顯示在屏幕上,因而就變成動畫了。
具體細節就不過多贅述了,能夠看對應demo
圖像處理實際上是很高深的學科,這裏我只是很簡單的將圖像轉換爲字符串。