Kwok's blog - 一个代码狗的日常 / zh-CN 关注黑客与极客 Sat, 10 Apr 2021 18:36:00 +0800 Sat, 10 Apr 2021 18:36:00 +0800 记录一个Java调用OpenCV DNN模块时踩的坑--out of memory /Machine-Learning/213.html /Machine-Learning/213.html Sat, 10 Apr 2021 18:36:00 +0800 Kwok 前言

笔者在做毕业设计时,使用到了OpenCV的DNN模块进行图像识别。而今天将项目部署到服务器上后发现了一个问题,在识别两到三个图片后进程就会被杀死。

问题排查

首先笔者查看了日志文件,确定是由于OOM导致的进程被系统杀死。

cat /var/log/message

message.png

接着,笔者又使用使用ps命令去观察该进程占用的内存,发现每进行一次图像识别,内存就会增加,两到三次后进程就会被系统杀死。

在查看了代码后,发现每次调用图像识别,OpenCV就会加载一次weights文件。于是考虑将加载文件的代码写在静态代码块中,随后测试成功。

解决方法

将OpenCV的DNN模块加载模型文件的代码写在静态代码块中。

    static {
        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
        String cfgfilePath=null;
        String weightsfilePath=null;
        File cfgfile = FileUtil.createFile("static/yolov3_kwok.cfg");
        File weightsfile = FileUtil.createFile("static/yolov3_kwok_17000.weights");
        cfgfilePath=cfgfile.getPath();
        weightsfilePath=weightsfile.getPath();
        net = Dnn.readNetFromDarknet(cfgfilePath, weightsfilePath);
        if (net.empty()) {
            System.out.println("Reading Net error");
        }
    }
]]>
0 /Machine-Learning/213.html#comments /feed/Machine-Learning/213.html
CentOS8编译OpenCV 4.5.1并生成jar包和so库 /other/211.html /other/211.html Sun, 21 Mar 2021 14:28:25 +0800 Kwok 前言

最近笔者在写毕业设计时,试图将项目部署在CentOS8系统的云服务器上,编译OpenCV 4.5.1的过程有些曲折。记录一下。

编译过程

环境准备

安装相关类库

yum install gcc-c++ gtk+-devel gimp-devel gimp-devel-tools gimp-help-browser zlib-devel libtiff-devel libjpeg-devel libpng-devel gstreamer-devel libavc1394-devel libraw1394-devel libdc1394-devel jasper-devel jasper-utils swig libtool nasm

安装ant

yum install ant

下载OpenCV源码

git clone https://github.com/opencv/opencv.git

开始编译

编译前先检查一下JAVA_HOME设置是否正确。
然后切换到下载好的OpenCV源码目录内。创建build文件夹,并切换到build文件夹

cd opencv-4.5.1
mkdir build
cd build/

在build文件夹下执行如下命令完成编译

cmake -D CMAKE_BUILD_TYPE=RELEASE -DBUILD_SHARED_LIBS=OFF -DBUILD_TESTS=OFF ..
make -j2
sudo make install

此处的make -j2 也可以改为make -j4、make -j8等 根据自己机器的性能选择。(笔者的服务器配置较低已开始使用make -j8的时候服务器还死机了)

编译完成后就能在lib目录看到libopencv_java451.so文件了,opencv-451.jar文件在bin目录。

]]>
0 /other/211.html#comments /feed/other/211.html
基于计算机视觉的餐厅自助结算系统 /JavaWeb/210.html /JavaWeb/210.html Fri, 12 Mar 2021 23:32:00 +0800 Kwok 前言

本项目为笔者的毕业设计,目前基本功能已经完成,等全部功能完工毕业答辩完毕后笔者会将本项目开源。
图像识别部分采用了YOLO算法,Web部分采用了Vue、SpringBoot、Mybatis、Redis等技术。

视频演示

以下为基本功能的展示视频:
[vplayer url="/images/mp4/毕业设计.mp4" /]

]]>
0 /JavaWeb/210.html#comments /feed/JavaWeb/210.html
SpringBoot调用OpenCV的一个坑:java.lang.UnsatisfiedLinkError /JavaWeb/209.html /JavaWeb/209.html Thu, 04 Mar 2021 11:05:05 +0800 Kwok 问题

今天笔者在使用SpringBoot调用OpenCV的时候,报了如下错误:

java.lang.UnsatisfiedLinkError: 'long org.opencv.dnn.Dnn.readNetFromDarknet_0(java.lang.String, java.lang.String)'

起初以为是OpenCV的库没有加载或者是OpenCV的版本问题。最后才发现是spring-boot-devtools的锅。

解决方法

在maven的配置文件中删除spring-boot-devtools依赖。

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
]]>
0 /JavaWeb/209.html#comments /feed/JavaWeb/209.html
SpringBoot-Mybatis如何使用Redis作为二级缓存 /JavaWeb/206.html /JavaWeb/206.html Tue, 23 Feb 2021 23:34:00 +0800 Kwok 前言

笔者在毕业设计中使用了Mybatis作为ORM框架,想要使用Redis作为缓存来提升数据库性能。本文简单的介绍了如何使用Redis作为Mybatis的二级缓存。

配置Redis

添加Redis依赖

首先在maven中添加Redis的依赖。

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

配置Redis连接

在配置文件中添加Redis连接的相关配置。(这里笔者使用的是yaml格式的配置文件)

spring:
  redis:
    host: 127.0.0.1
    port: 6379
    password: 12345678

增加Redis的配置类

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
    @Bean(name = "redisTemplate")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
}

配置Mybatis

开启Mybatis缓存

在配置文件中开启Mybatis缓存。(这里笔者使用的是yaml格式的配置文件)

mybatis:
  configuration:
    cache-enabled: true

创建RedisCache类,实现Mybatis的Cache接口

import org.apache.ibatis.cache.Cache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.CollectionUtils;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class RedisCache implements Cache {

    private static final Logger LOG = LoggerFactory.getLogger(RedisCache.class);
    private static final int DEFAULT_REDIS_EXPIRE = 10;
    private static RedisTemplate<String, Object> redisTemplate = null;
    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(true);
    private String id = null;
    public RedisCache(final String id) {
        if (null == id) {
            throw new IllegalArgumentException("MybatisRedisCache Instance Require An Id...");
        }
        LOG.info("MybatisRedisCache: " + id);
        this.id = id;
    }
    @Override
    public String getId() {
        return this.id;
    }
    @Override
    public void putObject(Object key, Object value) {
        if (null != value) {
            LOG.info("putObject key: " + key.toString());
            redisTemplate.opsForValue().set(key.toString(), value, DEFAULT_REDIS_EXPIRE, TimeUnit.MINUTES);
        }
    }
    @Override
    public Object getObject(Object key) {
        try {
            if (null != key) {
                LOG.info("getObject key: " + key.toString());
                return redisTemplate.opsForValue().get(key.toString());
            }
        } catch (Exception e) {
            LOG.error("getFromRedis: " + key.toString() + " failed!");
        }
        LOG.info("getObject null...");
        return null;
    }
    @Override
    public Object removeObject(Object keyObject) {
        if (null != keyObject) {
            redisTemplate.delete(keyObject.toString());
        }
        return null;
    }
    @Override
    public void clear() {
        LOG.info("clear......");
        try {
            Set<String> keys = redisTemplate.keys("*:" + this.id + "*");
            LOG.info("keys size: " + keys.size());
            for (String key : keys) {
                LOG.info("key : " + key);
            }
            if (!CollectionUtils.isEmpty(keys)) {
                redisTemplate.delete(keys);
            }
        } catch (Exception e) {
            LOG.error("clear failed!", e);
        }
    }
    @Override
    public int getSize() {
        Long size = (Long) redisTemplate.execute(new RedisCallback<Long>() {
            @Override
            public Long doInRedis(RedisConnection connection) throws DataAccessException {
                return connection.dbSize();
            }
        });
        LOG.info("getSize: " + size.intValue());
        return size.intValue();
    }
    @Override
    public ReadWriteLock getReadWriteLock() {
        return this.readWriteLock;
    }
    public static void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
        RedisCache.redisTemplate = redisTemplate;
    }
}

注入redisTemplate

创建一个配置类来注入redisTemplate。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
@Component
public class WiredRedisTemplate {
    @Autowired
    @Qualifier("redisTemplate")
    public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
        RedisCache.setRedisTemplate(redisTemplate);
    }
}

在Mapper文件中配置二级缓存

在需要开启缓存的Mapper中添加如下注解即可。

@CacheNamespace(implementation= RedisCache.class)

参考文章

Spring Boot之基于Redis实现MyBatis查询缓存解决方案

]]>
0 /JavaWeb/206.html#comments /feed/JavaWeb/206.html
训练yolov3时如何保存log文件到backup目录 /Machine-Learning/203.html /Machine-Learning/203.html Thu, 07 Jan 2021 16:49:00 +0800 Kwok 前言

近期博主在搞毕业设计,需要用yolov3网络来训练自己的数据集。但是在训练数据集的时候想要保存log数据,在网上找了几种方法都是使用Linux的tee命令,将输出的log保存到文件。但是tee命令要在命令执行结束后才能写入文件(可能跟缓冲区有关,但是博主试了几种方法没有解决),很不方便(训练途中可能出现各种情况导致异常退出,权重文件可使用backup保存,但log文件没办法保存)。

解决方案

最终想到一种比较好的解决方案就是修改detector.c这个文件的代码,该文件的目录为/darknet/src/detector.c
在该文件的大约第316行,添加如下代码即可(添加完成后要重新编译darknet):

        /************输出log到backup************/
        char fileNameStr[256];
        sprintf(fileNameStr, "%s/Log.txt", backup_directory);
        FILE* fl =fopen(fileNameStr,"a");
        if(fl) fprintf (LogFId,"\n %d: %f, %f avg loss, %f rate, %lf seconds, %d images, %f hours left\n", iteration, loss, avg_loss, get_current_rate(net), (what_time_is_it_now() - time), iteration*imgs, avg_time);
        fflush(fl);
        if(fl) fclose(fl);
        /*************************************/

detector.c

最终效果

每次迭代的log信息都会实时的保存到backup目录下的Log.txt文件。
最终效果

]]>
0 /Machine-Learning/203.html#comments /feed/Machine-Learning/203.html
handsome主题-QQ邮箱解析头像地址加密 /web/194.html /web/194.html Mon, 25 May 2020 19:01:00 +0800 Kwok 前言

因为主题自带的QQ邮箱解析头像会泄漏评论者的QQ号,于是想修改一下。找了几个插件都不能用,最终在插件的代码里找到了腾讯官方的头像地址加密接口。

加密接口

https://ptlogin2.qq.com/getface?&uin=10001&imgtype=4

其中的uin是QQ号,imgtype是头像的分辨率(1~4分别代表40到140)
返回结果中的网址就是加密的头像地址

pt.setHeader({"10001":"https://thirdqq.qlogo.cn/g?b=sdk&k=Vjic48anMfN6ovAxw4eN94w&s=140&t=1555323598"})

handsome主题如何修改

打开usr/themes/handsome/libs目录下的Utils.php这个文件。
找到public static function getAvator($email,$size)这个函数(大约在135行)
将函数替换为以下代码(替换前请先备份原文件):

public static function getAvator($email,$size){
        $options = mget();
        $cdnUrl = $options->CDNURL;
        if (@ in_array('emailToQQ',$options->featuresetup)){
            $str = explode('@', $email);
            if (@$str[1] == 'qq.com' && @ctype_digit($str[0]) && @strlen($str[0]) >=5
                && @strlen($str[0])<=11) {
                $api_url = "https://ptlogin2.qq.com/getface?uin=".$str[0]."&imgtype=3";
                $curl = curl_init();
                curl_setopt($curl, CURLOPT_URL, $api_url);
                curl_setopt($curl, CURLOPT_HEADER, 0);
                curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
                curl_setopt($curl, CURLOPT_TIMEOUT, 10);
                $data = curl_exec($curl);
                curl_close($curl);
                $pattern = '/pt.setHeader\((.*)\)/is';
                preg_match($pattern, $data, $matches);
                $avatorSrc = json_decode($matches[1], true)["$str[0]"];
            }else{
                $avatorSrc = Utils::getGravator($email,$cdnUrl,$size);
            }
        }else{
            $avatorSrc = Utils::getGravator($email,$cdnUrl,$size);
        }
        return $avatorSrc;
    }

结语

PHP需要安装curl扩展,修改前请先备份原文件,对于修改文件出现的任何后果本人概不负责。
其他主题可参考本文中的接口文档自行修改。

]]>
2 /web/194.html#comments /feed/web/194.html
抖音无水印下载工具源码-Golang+Vue /other/193.html /other/193.html Tue, 05 May 2020 12:33:00 +0800 Kwok 后端代码

以下代码为web程序的代码,也可以改写成其他应用程序。
请求示例:http://127.0.0.1:90/api?url=视频网址
响应结果为无水印视频网址。

package main
import (
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
    "strings"
)
func main() {
    http.HandleFunc("/api", api)
    err := http.ListenAndServe(":90", nil)
    if err != nil {
        log.Fatal("ListenAndServe: ", err)
    }
}
func api(w http.ResponseWriter, r *http.Request) {
    r.ParseForm()
    fmt.Println(r.Form)
    if len(r.Form)==0 {
        fmt.Fprint(w,"非法请求")
    }
    for k, v := range r.Form {
        if k=="url" && strings.Join(v, "")!="" {
            url :=strings.Join(v, "")
            body:=httpDo(url,"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36",0)
            videourl:=strings.Replace(GetBetweenStr(body,"playAddr: \"","\","),"playwm","play",1)
            if videourl !="" {
                body2:=httpDo(videourl,"Mozilla/5.0 (iPhone; CPU iPhone OS 13_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.4 Mobile/15E148 Safari/604.1",1)
                url2:=GetBetweenStr(body2,"a href=\"","\">")
                fmt.Fprint(w,url2)
            }
        }else {
        fmt.Fprint(w,"非法请求")
        }
    }
}
func httpDo(url,ua string,i int)string {
    bodyy:=""
    if i==0 {
        client := &http.Client{}
        req, err := http.NewRequest("GET", url, strings.NewReader(""))
        if err != nil {
        }
        req.Header.Set("user-agent", ua)
        req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
        resp, err := client.Do(req)
        defer resp.Body.Close()
        body, err := ioutil.ReadAll(resp.Body)
        if err != nil {
        }
        bodyy=string(body)
    }else {
        client := &http.Transport{}
        req, err := http.NewRequest("GET", url, strings.NewReader(""))
        if err != nil {
        }
        req.Header.Set("user-agent", ua)
        req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
        resp, err := client.RoundTrip(req)
        defer resp.Body.Close()
        body, err := ioutil.ReadAll(resp.Body)
        if err != nil {
        }
        bodyy=string(body)
    }
    return bodyy
}
func GetBetweenStr(str, start, end string) string {
    n := strings.Index(str, start)
    if n == -1 {
        n = 0
    } else {
        n = n + len(start)
    }
    str = string([]byte(str)[n:])
    m := strings.Index(str, end)
    if m == -1 {
        m = len(str)
    }
    str = string([]byte(str)[:m])
    return str
}

前端代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>抖音视频解析-无水印下载</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script src="https://cdn.staticfile.org/vue-resource/1.5.1/vue-resource.min.js"></script>
    <link href="/usr/themes/handsome/assets/libs/bootstrap/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div id="app" style="width: 80%; margin:0 auto">
    <h3>抖音视频解析-无水印下载</h3>
    视频网址:<input type="text" v-model="url" class="form-control link-input">
    <input type="button" value="解析" @click="download" class="btn btn-primary"><br>
    <input type="text" readonly v-model="flag">
    <a :href="href" class='btn btn-success btn-sm' rel="noreferrer" target="_parent" v-show="show">下载</a>
</div>
<div style="width: 80%; margin:0 auto">
      <p>常见疑问:</p>
                <ol style="word-break: break-all;">
                    <li>Android手机上可以用吗?</li>
                    <p class="alert alert-info">可以的,Android手机在常用的Chrome、UC、360、QQ等浏览器上都可以很方便的使用本工具。</p>
                    <li>iOS设备(iPhone、iPad、iPod)上点击下载视频按钮后,跳转到视频页面,并没有直接下载,怎么办?</li>
                    <p class="alert alert-info">因Safari及微信内置浏览器均不支持下载文件,所以保存视频需要借助第三方App来完成下载,建议iOS用户在App Store下载免费的Documents 6,然后在Documents的内置浏览器中使用本工具,可以完美下载视频,并且Documents支持将下载的视频移到手机相册。</p>
                    <li>我在电脑上用的是IE浏览器,点下载视频按钮后,出现跟上面那用iPad的哥们一样的情况,跳转到视频页面,如何下载到本地呢?</li>
                    <p class="alert alert-info">电脑上少数浏览器不支持直接下载,但可以在下载视频按钮上点击右键,然后选择"目标另存为"或"链接存储为"来下载视频;或者到跳转后的视频页面,在视频画面上点击右键,然后选择"视频另存为"来下载视频。当然,我们更推荐在电脑上使用如谷歌Chrome浏览器、360浏览器极速模式、QQ浏览器极速模式等浏览器来获得最佳上网体验。</p>
                </ol>
</div>
</body>
<script>
 new Vue({
        el:"#app",
        data:{
            url:'',
            flag:'',
            href:'#',
            show:false
        },
        methods:{
            download:function () {
                this.show=false
                this.flag='解析中...'
                this.$http.get('api',{params : {'url':this.url}}).then(function(res){
                    if (res.body!="非法请求"){
                        this.href=res.body
                        this.show=true
                        this.flag='解析成功'
                    }else
                        this.flag='解析出错'
                },function(res){
                    this.flag='解析出错'
                });
            }
        }
    })
</script>
</html>
]]>
2 /other/193.html#comments /feed/other/193.html
B站用户hash转UID(mid) /Information-Security/191.html /Information-Security/191.html Mon, 13 Apr 2020 19:18:00 +0800 Kwok 介绍

用于查找弹幕发送者,此方法的得到的结果不一定准确(hash存在碰撞)。

代码

算法出处:https://github.com/esterTion/BiliBili_crc2mid

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        *{
            font-size: 40px;
        }
    </style>
</head>

<body>
<input id="hash" type="text"><input type="button" value="GO" onclick="getcrc()"><br>
<input id="mid" type="text">
</body>
<script>
    var BiliBili_midcrc=function(){
        'use strict';
        const CRCPOLYNOMIAL = 0xEDB88320;
        var startTime=new Date().getTime(),
            crctable=new Array(256),
            create_table=function(){
                var crcreg,
                    i,j;
                for (i = 0; i < 256; ++i)
                {
                    crcreg = i;
                    for (j = 0; j < 8; ++j)
                    {
                        if ((crcreg & 1) != 0)
                        {
                            crcreg = CRCPOLYNOMIAL ^ (crcreg >>> 1);
                        }
                        else
                        {
                            crcreg >>>= 1;
                        }
                    }
                    crctable[i] = crcreg;
                    console.log(crctable[i])
                }
            },
            crc32=function(input){
                if(typeof(input)!='string')
                    input=input.toString();
                var crcstart = 0xFFFFFFFF, len = input.length, index;
                for(var i=0;i<len;++i){
                    index = (crcstart ^ input.charCodeAt(i)) & 0xff;
                    crcstart = (crcstart >>> 8) ^ crctable[index];
                }
                return crcstart;
            },
            crc32lastindex=function(input){
                if(typeof(input)!='string')
                    input=input.toString();
                var crcstart = 0xFFFFFFFF, len = input.length, index;
                for(var i=0; i<len;++i){
                    index = (crcstart ^ input.charCodeAt(i)) & 0xff;
                    crcstart = (crcstart >>> 8) ^ crctable[index];
                }
                return index;
            },
            getcrcindex=function(t){
            for(var i=0;i<256;i++){
                if(crctable[i] >>> 24 == t)
                    return i;
            }
            return -1;
        },
        deepCheck=function(i, index){
            var tc=0x00,str='',
                hash=crc32(i);
            tc = hash & 0xff ^ index[2];
            if (!(tc <= 57 && tc >= 48))
                return [0];
            str+=tc-48;
            hash = crctable[index[2]] ^ (hash >>> 8);
            tc = hash & 0xff ^ index[1];
            if (!(tc <= 57 && tc >= 48))
                return [0];
            str+=tc-48;
            hash = crctable[index[1]] ^ (hash >>> 8);
            tc = hash & 0xff ^ index[0];
            if (!(tc <= 57 && tc >= 48))
                return [0];
            str+=tc-48;
            hash = crctable[index[0]] ^ (hash >>> 8);
            return [1,str];
        };
    create_table();
    var index=new Array(4);
    return function(input){
        var ht=parseInt('0x'+input)^0xffffffff,
            snum,i,lastindex,deepCheckData;
        for(i=3;i>=0;i--){
            index[3-i]=getcrcindex( ht >>> (i*8) );
            snum=crctable[index[3-i]];
            ht^=snum>>>((3-i)*8);
            console.log("index:"+index[i]);
        }
        for(i=0;i<1000000000; i++){
            lastindex = crc32lastindex(i);
            if(lastindex == index[3]){
                deepCheckData=deepCheck(i,index)
                if(deepCheckData[0])
                    break;
            }
        }

        if(i==1000000000)
            return -1;
        console.log('总耗时:'+(new Date().getTime()-startTime)+'ms');
        console.log(deepCheckData[1])
        return i+''+deepCheckData[1];

    }
}
window.crc322=new BiliBili_midcrc();
function getcrc() {
var inp=document.getElementById("hash");
var opt=document.getElementById("mid")
opt.value=crc322(inp.value.toString());
`}`
</script>
</html>
]]>
1 /Information-Security/191.html#comments /feed/Information-Security/191.html
Python生成字符画视频 /other/189.html /other/189.html Wed, 18 Mar 2020 13:58:00 +0800 Kwok 简介

使用Python+OpenCV将视频转化为字符在控制台输出。
请使用Python3运行该程序,运行出错根据提示安装相关库即可。
运行时,将倒数第二行的'./666.mp4'改为视频文件的路径即可。(不建议使用分辨率过高的视频)
具体效果请看一下视频的后半段。

程序代码

[scode type="yellow"]该代码来源于网络,具体出处忘记了,侵删。[/scode]

版本1

只能生成字符视频,没有声音

import sys
import os
import time
import threading
import cv2
import pyprind
class CharFrame:
    ascii_char = "$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~<>i!lI;:,\"^`'. "
    def pixelToChar(self, luminance):
        return self.ascii_char[int(luminance / 256 * len(self.ascii_char))]
    def convert(self, img, limitSize=-1, fill=False, wrap=False):
        if limitSize != -1 and (img.shape[0] > limitSize[1] or img.shape[1] > limitSize[0]):
            img = cv2.resize(img, limitSize, interpolation=cv2.INTER_AREA)
        ascii_frame = ''
        blank = ''
        if fill:
            blank += ' ' * (limitSize[0] - img.shape[1])
        if wrap:
            blank += '\n'
        for i in range(img.shape[0]):
            for j in range(img.shape[1]):
                ascii_frame += self.pixelToChar(img[i, j])
            ascii_frame += blank
        return ascii_frame
class V2Char(CharFrame):
    charVideo = []
    timeInterval = 0.033
def __init__(self, path):
    if path.endswith('txt'):
        self.load(path)
    else:
        self.genCharVideo(path)

def genCharVideo(self, filepath):
    self.charVideo = []
    cap = cv2.VideoCapture(filepath)
    self.timeInterval = round(1 / cap.get(5), 3)
    nf = int(cap.get(7))
    print('Generate char video, please wait...')
    for i in pyprind.prog_bar(range(nf)):
        rawFrame = cv2.cvtColor(cap.read()[1], cv2.COLOR_BGR2GRAY)
        frame = self.convert(rawFrame, os.get_terminal_size(), fill=True)
        self.charVideo.append(frame)
    cap.release()
def export(self, filepath):
    if not self.charVideo:
        return
    with open(filepath, 'w') as f:
        for frame in self.charVideo:
            f.write(frame + '\n')

def load(self, filepath):
    self.charVideo = []
    for i in open(filepath):
        self.charVideo.append(i[:-1])

def play(self, stream=1):
    if not self.charVideo:
        return
    if stream == 1 and os.isatty(sys.stdout.fileno()):
        self.streamOut = sys.stdout.write
        self.streamFlush = sys.stdout.flush
    elif stream == 2 and os.isatty(sys.stderr.fileno()):
        self.streamOut = sys.stderr.write
        self.streamFlush = sys.stderr.flush
    elif hasattr(stream, 'write'):
        self.streamOut = stream.write
        self.streamFlush = stream.flush
    breakflag = False

    def getChar():
        global breakflag
        try:
            import msvcrt
        except ImportError:
            import termios
            import tty
            fd = sys.stdin.fileno()
            old_settings = termios.tcgetattr(fd)
            try:
                tty.setraw(sys.stdin.fileno())
                ch = sys.stdin.read(1)
            finally:
                termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
            if ch:
                breakflag = True
        else:
            if msvcrt.getch():
                breakflag = True
    getchar = threading.Thread(target=getChar)
    getchar.daemon = True
    getchar.start()
    rows = len(self.charVideo[0]) // os.get_terminal_size()[0]
    for frame in self.charVideo:
        if breakflag:
            break
        self.streamOut(frame)
        self.streamFlush()
        time.sleep(self.timeInterval)
        self.streamOut('\033[{}A\r'.format(rows - 1))
    self.streamOut('\033[{}B\033[K'.format(rows - 1))
    for i in range(rows - 1):
        self.streamOut('\033[1A')
        self.streamOut('\r\033[K')
    if breakflag:
        self.streamOut('User interrupt!\n')
    else:
        self.streamOut('Finished!\n')


if __name__ == "__main__":
v2char = V2Char('./666.mp4')
v2char.play()

版本2

对版本1进行了修改,可以播放声音
版本Python3 需要安装一下库 opencv-python、pyprind、pygame、MoviePy、requests
使用方法

Python3 v2char.py 视频文件路径

代码:

# -*- coding:utf-8 -*-
import sys
import os
import time
import threading
import cv2
import pyprind
import pygame
from moviepy.editor import *

class CharFrame:
    ascii_char = "$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~<>i!lI;:,\"^`'. "
    def pixelToChar(self, luminance):
        return self.ascii_char[int(luminance / 256 * len(self.ascii_char))]
    def convert(self, img, limitSize=-1, fill=False, wrap=False):
        if limitSize != -1 and (img.shape[0] > limitSize[1] or img.shape[1] > limitSize[0]):
            img = cv2.resize(img, limitSize, interpolation=cv2.INTER_AREA)
        ascii_frame = ''
        blank = ''
        if fill:
            blank += ' ' * (limitSize[0] - img.shape[1])
        if wrap:
            blank += '\n'
        for i in range(img.shape[0]):
            for j in range(img.shape[1]):
                ascii_frame += self.pixelToChar(img[i, j])
            ascii_frame += blank
        return ascii_frame
class V2Char(CharFrame):
    charVideo = []
    timeInterval = 0.033

    def __init__(self, path):
        if path.endswith('txt'):
            self.load(path)
        else:
            self.genCharVideo(path)

    def genCharVideo(self, filepath):
        self.charVideo = []
        cap = cv2.VideoCapture(filepath)
        self.timeInterval = round(1 / cap.get(5), 3)
        nf = int(cap.get(7))
        print('正在生成字符视频...')
        for i in pyprind.prog_bar(range(nf)):
            rawFrame = cv2.cvtColor(cap.read()[1], cv2.COLOR_BGR2GRAY)
            frame = self.convert(rawFrame, os.get_terminal_size(), fill=True)
            self.charVideo.append(frame)
        cap.release()
    def export(self, filepath):
        if not self.charVideo:
            return
        with open(filepath, 'w') as f:
            for frame in self.charVideo:
                f.write(frame + '\n')

    def load(self, filepath):
        self.charVideo = []
        for i in open(filepath):
            self.charVideo.append(i[:-1])

    def play(self, stream=1):
        if not self.charVideo:
            return
        if stream == 1 and os.isatty(sys.stdout.fileno()):
            self.streamOut = sys.stdout.write
            self.streamFlush = sys.stdout.flush
        elif stream == 2 and os.isatty(sys.stderr.fileno()):
            self.streamOut = sys.stderr.write
            self.streamFlush = sys.stderr.flush
        elif hasattr(stream, 'write'):
            self.streamOut = stream.write
            self.streamFlush = stream.flush
        breakflag = False

        def getChar():
            global breakflag
            try:
                import msvcrt
            except ImportError:
                import termios
                import tty
                fd = sys.stdin.fileno()
                old_settings = termios.tcgetattr(fd)
                try:
                    tty.setraw(sys.stdin.fileno())
                    ch = sys.stdin.read(1)
                finally:
                    termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
                if ch:
                    breakflag = True
            else:
                if msvcrt.getch():
                    breakflag = True
        getchar = threading.Thread(target=getChar)
        getchar.daemon = True
        getchar.start()
        rows = len(self.charVideo[0]) // os.get_terminal_size()[0]
        for frame in self.charVideo:
            if breakflag:
                break
            self.streamOut(frame)
            self.streamFlush()
            time.sleep(self.timeInterval)
            self.streamOut('\033[{}A\r'.format(rows - 1))
        self.streamOut('\033[{}B\033[K'.format(rows - 1))
        for i in range(rows - 1):
            self.streamOut('\033[1A')
            self.streamOut('\r\033[K')
        if breakflag:
            self.streamOut('User interrupt!\n')
        else:
            self.streamOut('Finished!\n')
if __name__ == "__main__":
    src_file = sys.argv[1]
    v2char = V2Char(src_file)
    video = VideoFileClip(src_file)
    pindex = src_file.rindex('.')
    audio = video.audio
    audio.write_audiofile(src_file[0:pindex]+".mp3")
    pygame.mixer.init()
    # 加载音乐
    pygame.mixer.music.load(src_file[0:pindex]+".mp3")
    pygame.mixer.music.play()
    v2char.play()
]]>
0 /other/189.html#comments /feed/other/189.html