百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分类 > 正文

SuperMemo实践闭环(3)-批量挖空制卡的操作

ztj100 2024-12-07 18:49 19 浏览 0 评论

本文阐述了在不使用ImageOcclusionEditor的情况下,我们通过OpenCV实现批量图片遮挡的效果.

Occlusion脚本执行后的效果如图所示

一. 运行环境-配置Python/脚本/终端环境

PyCharm / Python3.9 / Open-CV

PyCharm / Python3.9 的配置不再详细讲解,你可以参考我之前的专栏文章.

PyCharm中使用Python3.9编译器

安装Open-CV的Python插件
Bash
pip3 install opencv-python

解决找不到CV2 Module的问题:

.py文件运行时找不到,要注意在运行环境配置中添加变量

解决运行时找不到CV2模块的问题

如果以上的方法还不能解决你的问题,请按这篇内容再配置一下:

用Pycharm运行后出现“No module named 'cv2'”错误的终极解决方案blog.csdn.net/lhw19931201/article/details/86545964

终端单独运行找不到时,要确保你Python3.9的site-packages下存在cv2的模块

终端找不到CV2模块要确保.so存在

Bash
Likey@Laptop pythonProject % python3 ./occlusionCard.py "你的图片路径"

脚本生成的最终SM样式网页

二. 脚本内容 - 如下脚本生成多个Occlusion图片至图床(使用了PicGo)并返回多个图片链接

OpenCV-Occlusion 脚本提供如下,具体操作步骤为: 1.截图后对要制卡区绘制矩形遮挡并保存(注意保证矩形区透明度75%以上,而且能看到遮挡处的内容), 2.直接执行上面那条脚本:(如下代码区也有使用方法的说明) 3.如果遮挡生成的图片不对请酌情调整contourAreaValue参数

Bash
#!/usr/bin/python3# -*- coding: utf-8 -*-import numpy as npimport cv2 as cvimport osimport sysimport requestsimport jsonfrom pprint import pprint################################## 实现(opencv)批量生成挖空卡片,调用picGo上传并返回链接# 作者:一只小胖子# 版本:V0.1# 知乎:https://www.zhihu.com/people/lxf-8868# 使用方法:# 1.使用snipaste截图,并用矩形工具描出实心遮挡区(注意:透明度为75%以上)# 2.运行python3 occlusionCard.py "你的图片路径(包括后缀名)"# 设置轮廓面积值,按效果自己调整,一般是1500-4000之间contourAreaValue = 2500################################## 设置putText函数字体font = cv.FONT_HERSHEY_SIMPLEX# 计算两边夹角额cos值def angle_cos(p0, p1, p2):
    d1, d2 = (p0 - p1).astype('float'), (p2 - p1).astype('float')
    return abs(np.dot(d1, d2) / np.sqrt(np.dot(d1, d1) * np.dot(d2, d2)))# 合并图片def merge_img(image1, image2):
    h1, w1, c1 = image1.shape
    h2, w2, c2 = image2.shape
    if c1 != c2:
        print("channels NOT match, cannot merge")
        return
    else:
        if w1 > w2:
            tmp = np.zeros([h2, w1 - w2, c1])
            image3 = np.hstack([image2, tmp])
            image3 = np.vstack([image1, image3])
        elif w1 == w2:
            image3 = np.hstack([image1, image2])
        else:
            tmp = np.zeros([h1, w2 - w1, c2])
            image3 = np.hstack([image1, tmp])
            image3 = np.vstack([image3, image2])
    return image3# 查找矩形轮廓def find_squares(filepath, flag):
    img = cv.imread(filepath)  # 读取图片对象
    gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
    gray = cv.GaussianBlur(gray, (3, 3), 1)  # 1
    ret, th1 = cv.threshold(gray, 128, 255, 0)  # cv.THRESH_OTSU)  # 0,255,cv.THRESH_BINARY | cv.THRESH_OTSU  127, 255,0
    # 开闭运算去除噪点
    kernel = cv.getStructuringElement(cv.MORPH_ELLIPSE, (3, 3))
    th1 = cv.morphologyEx(th1, cv.MORPH_OPEN, kernel)
    th1 = cv.morphologyEx(th1, cv.MORPH_CLOSE, kernel)
    binary = cv.Canny(th1, 50, 100)
    contours, _hierarchy = cv.findContours(binary, cv.RETR_EXTERNAL,
                                           cv.CHAIN_APPROX_SIMPLE)  # cv.RETR_EXTERNAL cv.RETR_TREE
    print("轮廓数量:%d" % len(contours))

    # 轮廓遍历
    roi_list = []
    contours_2 = []
    for cnt in contours:
        # cnt_len = cv.arcLength(cnt, True)  # 计算轮廓周长
        # cnt = cv.approxPolyDP(cnt, 0.02 * cnt_len, True)  # 多边形逼近
        # # 条件判断逼近边的数量是否为4,轮廓面积是否大于1000,检测轮廓是否为凸的
        # if cv.contourArea(cnt) > 4000 and cv.isContourConvex(cnt):
        if cv.contourArea(cnt) > contourAreaValue:
            # 获取外接矩形的值
            x, y, w, h = cv.boundingRect(cnt)
            roi_list.append((x, y, w, h, cnt))
            contours_2.append({"h": h, "cnt": cnt})
    print("发现挖空[" + str(len(roi_list)) + "]处, contours数为[" + str(len(contours_2)) + "]")
    squares_len = len(roi_list)

    filenameAtr = filepath.rsplit("/")
    for roi_idx in range(len(roi_list)):
        index = 0
        if flag == 1:  # 正面 (将回答问题为红色遮挡,已回答的显示答案,其它为蓝色遮挡)
            img = cv.imread(filepath)
            for roi_list_ in roi_list:
                (x, y, w, h, cnt_obj) = roi_list_
                M = cv.moments(cnt_obj)  # 计算轮廓的矩
                cx = int(M['m10'] / M['m00'])
                cy = int(M['m01'] / M['m00'] + h * 0.5 + 20)  # 轮廓重心下移0.5倍高度
                cv.putText(img, ("#%d" % index), (cx, cy), font, 0.8, (0, 128, 0), 2, cv.LINE_AA)  # 抗锯齿
                cv.drawContours(img, contours, 0, (255, 0, 255), 2)
                if index < roi_idx:
                    index = index + 1
                    continue
                elif index == roi_idx:
                    idx_red_col = (0, 0, 255)  # 红色
                else:
                    idx_red_col = (255, 0, 0)  # 蓝色
                cv.rectangle(img, (x, y), (x + w, y + h), idx_red_col, -1)  # -1 2   -1为填充
                index = index + 1
            cv_filename = filenameAtr[0] + str(roi_idx) + "_A_" + filenameAtr[-1]
            print(cv_filename)
            # cv.imshow(cv_filename, img)
            cv.imwrite(cv_filename, img)
        elif flag == 0:  # 反面
            img2 = cv.imread(filepath)
            for roi_list_ in roi_list:
                (x, y, w, h, cnt_obj) = roi_list_
                M = cv.moments(cnt_obj)  # 计算轮廓的矩
                cx = int(M['m10'] / M['m00'])
                cy = int(M['m01'] / M['m00'] + h * 0.5 + 20)  # 轮廓重心下移0.5倍高度
                cv.putText(img2, ("#%d" % index), (cx, cy), font, 0.8, (0, 128, 0), 2, cv.LINE_AA)  # 抗锯齿
                cv.drawContours(img2, contours, 0, (255, 0, 255), 2)
                if index <= roi_idx:  # 已回答和正要回答的显示答案
                    index = index + 1
                    continue
                else:
                    idx_red_col = (255, 0, 0)  # 蓝色
                cv.rectangle(img2, (x, y), (x + w, y + h), idx_red_col, -1)  # -1 2   -1为填充
                index = index + 1
            cv_filename = filenameAtr[0] + str(roi_idx) + "_B_" + filenameAtr[-1]
            print(cv_filename)
            # cv.imshow(cv_filename, img2)
            cv.imwrite(cv_filename, img2)

        # cnt_len = cv.arcLength(cnt, True)  # 计算轮廓周长
        # cnt = cv.approxPolyDP(cnt, 0.02 * cnt_len, True)  # 多边形逼近
        # # 条件判断逼近边的数量是否为4,轮廓面积是否大于1000,检测轮廓是否为凸的
        # if len(cnt) == 4 and cv.contourArea(cnt) > 1000 and cv.isContourConvex(cnt):
        #     M = cv.moments(cnt)  # 计算轮廓的矩
        #     cx = int(M['m10'] / M['m00'])
        #     cy = int(M['m01'] / M['m00'])  # 轮廓重心
        #
        #     cnt = cnt.reshape(-1, 2)
        #     max_cos = np.max([angle_cos(cnt[i], cnt[(i + 1) % 4], cnt[(i + 2) % 4]) for i in range(4)])
        #     # 只检测矩形(cos90° = 0)
        #     if max_cos < 0.1:
        #         # True
        #         # 检测四边形(不限定角度范围)
        #         # if True:
        #     # if True:
        #         index = index + 1
        #         cv.putText(img, ("#%d" % index), (cx, cy), font, 0.7, (255, 0, 255), 2)
        #         squares.append(cnt)
    return squares_len# 逻辑执行入口def main(img_file_path):

    file_dir_path = os.path.dirname(img_file_path) + "/"
    file_img_name = os.path.basename(img_file_path)
    file_upload_list = []  # 待上传的图片列表

    print("1.开始生成正面图像")
    squares_len = find_squares(img_file_path, 1)
    print("2.开始生成反面图像")
    squares_len = find_squares(img_file_path, 0)

    print("3.开始合成生成的图象")
    for file_obj_idx in range(squares_len):
        # print("{}{}{}{}".format(file_dir_path, file_obj_idx, "_A_", file_img_name))
        # print("{}{}{}{}".format(file_dir_path, file_obj_idx, "_B_", file_img_name))
        view1 = cv.imread("{}{}{}{}".format(file_dir_path, file_obj_idx, "_A_", file_img_name))
        view2 = cv.imread("{}{}{}{}".format(file_dir_path, file_obj_idx, "_B_", file_img_name))
        # 迭加图片模式
        # cv.addWeighted(view1, alpha, src2, beta, gamma, dst=None, dtype=None)
        # alpha/beta 对应两张图片的透明度, 0是完全透明 1是完全不透明
        # overlapping = cv.addWeighted(view1, 0.8, view2, 0.2, 0)
        # 合并的文件名
        file_out_name = "{}{}_QA_{}".format(file_dir_path, file_obj_idx, file_img_name)
        print(file_out_name)
        # 只上传合并后的QA图片
        file_upload_list.append(file_out_name)
        # 水平或垂直合并图片
        view3 = merge_img(view1, view2)
        # cv.imshow('view3', view3)
        cv.imwrite(file_out_name, view3)

    # # Exit if ESC pressed
    # # cv.waitKey()
    k = cv.waitKey(100) & 0xff  # 100ms
    if k == 27:
        cv.destroyAllWindows()
        # exit()

    # 获取图片存储目录待上传图片
    print('
图片处理生成至: {} 结束!'.format(file_dir_path))
    print("开始上传图片,请确保你已提前配置好picGo上传环境...")

    # # 获取传入路径下的: 当前目录, 子目录列表, 文件列表
    # for f_path, dir_names, f_names in os.walk(file_dir_path):
    #     # 正常的图片文件,关键字查询过滤文件名
    #     f_names = [f_name for f_name in f_names if not f_name.startswith(".") and f_name.__contains__(".jpg")
    #                and os.path.isfile(f_name) and str(f_name).__contains__("#")]
    #     # 得到全路径信息
    #     f_names = [os.path.join(f_path, f_name) for f_name in f_names]
    #     break  # 只处理根目录文件

    print("待处理上传图片列表: {}".format(file_upload_list))

    url = "http://127.0.0.1:36677/upload"
    payload = json.dumps({
        "list": file_upload_list
    })
    headers = {
        'Content-Type': 'application/json'
    }
    response = requests.request("POST", url, headers=headers, data=payload)
    print("PicGo上传返回结果:")
    print(response)
    obj_json_list = json.loads(response.text)
    pprint(obj_json_list)

    # 获取图片的URL链接
    if "result" in obj_json_list.keys():
        url_for_sm18 = []  # 格式化成SuperMemo网页样式
        for url_ in obj_json_list["result"]:
            url_str = "{} <img src="{}"/> <hr> ".format(file_img_name, url_)
            url_for_sm18.append(url_str)

        print("上传成功,返回SuperMemo样式:")  # $fileName <img src="$url"/> <hr>
        print("
".join(url_for_sm18))
    else:
        print("上传失败,请检查是否重复上传!")# ------------开始调用方法-------------# 通过终端传参调用img_path = str(sys.argv[1])print("开始处理传入的图片:{}".format(img_path))main(img_file_path=img_path)# pycharm直接运行# if __name__ == '__main__':#     # print(__doc__)#     img_path = "/Users/Likey/PycharmProjects/pythonProject/pic/33.jpg"#     main(img_path)
如果你用的是代码,下面的第三步骤可以不用再做了,代码已经实现了这个功能,第三步骤适合不用代码,直接手工操作的场景,总体上会麻烦一点....,建议你直接用代码的方式更方便.

三. Occlusion图片批量上传服务器并返回链接(这一步可以用上面第二步的代码替换了....)

(软件自带了一个网页链接,但这里我们要另外配置一个自定义链接用于获取批量的图片网址.)

先自定义返回图片的链接格式

通过以上的脚本生成了多个挖空Occlusion图片后,我们按需选中多个图片拖至PicGo软件的主界面进行上传,上传完后通过自定义的网页形式,批量获取图片网页链接.

主界面批量上传多图至服务端获取批量多图的自定义格式链接

Bash
# 多图的自定义返回链接
QA_2_444 <img src="https://gitee.com/lxf-8868/picgo_repo1/raw/master/img/QA_2_444.jpg"/> <hr>
QA_1_444 <img src="https://gitee.com/lxf-8868/picgo_repo1/raw/master/img/QA_1_444.jpg"/> <hr>
QA_6_444 <img src="https://gitee.com/lxf-8868/picgo_repo1/raw/master/img/QA_6_444.jpg"/> <hr>
QA_7_444 <img src="https://gitee.com/lxf-8868/picgo_repo1/raw/master/img/QA_7_444.jpg"/> <hr>
20210228124446 <img src="https://gitee.com/lxf-8868/picgo_repo1/raw/master/img/20210228124446.png"/> <hr>

四. 按以上方式通过代码或者手工获取到多个链接后,我们就可以直接在SuperMemo中处理了.

导入批量图片链接并分割为子问答批量转换子问答及添加模版多图片链接制卡的最终效果

一只小胖子:SuperMemo实践闭环(4)-交互式处理网页材料2 赞同 · 2 评论文章

本文结束....


我是一只热爱学习的小胖子,如果你也热爱学习,并且对SuperMemo感兴趣,欢迎转发和评论!


相关推荐

Win10预览版10532已知问题汇总(微软win11正式版已知问题一览)

IT之家讯微软已向Insider用户推送了Win10预览版10532更新,本次更新对右键菜单、《Windows反馈》应用以及Edge浏览器进行了改进。除此之外还包含一些Bug,汇总如下,有意升级Wi...

Gabe Aul正测试Win10 Mobile 10532,Insider用户还需等

IT之家讯本月中旬微软向Insider用户推送了Win10Mobile预览版10512,该版本修复了一些Bug,增强了系统稳定性,但依然存在一些问题。今天,微软Insider项目负责人GabeAu...

微软开始推送Win10预览版10532快速版更新

8月28日消息,刚才,微软推送了Win10Build10532快速版,修复了之前的Bug,并带来了三项改进。主要来说,这次的更新改进了右键菜单的UI,使其更具Modern风格(见上图)。此外,更新...

Win10预览版10532更新内容大全(windows10更新预览版)

IT之家讯今天凌晨微软向Insider用户推送了Win10预览版10532快速版更新,本次更新主要带来了三处改进,汇总如下:o改进右键菜单,外观更加Modern。这是基于网友要求界面一致的反馈做出...

无法升级Win10预览版10532?也许Hyper-V在搞鬼

根据IT之家网友的反映,安装了微软虚拟机Hyper-V的Win10预览版用户无法成功升级Build10532版本,安装过程中会被要求回滚系统。很多朋友在尝试关闭虚拟机之后重启安装程序,结果仍然无法顺...

Win10预览版10532界面兴起“酷黑”风潮

Win10预览版10532的界面改动还是较为明显的,主要体现在右键菜单上面。总体来看,该版本的右键菜单间距更宽,视觉上更大气,操作上更便于触控。具体来说,任务栏右键菜单的变化最为明显。除了增加选项的宽...

Win10预览版10532上手图集(windows10预览版下载)

IT之家讯8月28日,微软今天推送了Win10预览版10532快速版更新,在该版本中,微软主要是加强细节上调整,并且主要是增强Edge浏览器性能等。在Windows10预览版10532中,微软改进了...

Win10预览版10532上手视频亮点演示

IT之家讯8月28日消息,今天凌晨微软向WindowsInsider快速通道用户推送了Win10预览版10532。在Windows10预览版10532中,微软改进了右键菜单,外观更加现代化。另外还...

第二篇 前端框架Vue.js(vue前端框架技术)

前端三大核心是网页开发的基础,Vue则是基于它们构建的“生产力工具”。通俗理解就是HTML是化妆的工具如眉笔,CSS是化妆品如口红,JavaScript是化妆后的互动,而Vue就是化妆助手。有了化妆工...

基于SpringBoot + vue2实现的旅游推荐管理系统

项目描述...

基于Vue以及iView组件的后端管理UI模板——iview-admin

介绍iView-admin是一套后端管理界面模板,基于Vue2.0,iView(现在为ViewUI)组件是一套完整的基于Vue的高质量组件库,虽然Github上有一套非常火的基于ElementUI...

别再说你会SPA开发了,这5个核心你真的搞懂了吗?

前言此spa非彼spa,不是你所熟知的spa。你所熟知的spa作者肯定是没有你熟悉的。我们这里指的是在前端开发中的一种模型,叫作单页应用程序,顾名思义,就是整个项目只有一个页面,而页面中的内容是动态的...

React.js Top20面试题(react.js中文官网)

概述作为React开发者,对框架的关键概念和原则有扎实的理解是很重要的。考虑到这一点,我整理了一份包含20个重要问题的清单,每个React开发者都应该知道,无论他们是在面试工作还是只是想提高技能。...

美媒:特朗普签署行政令后,FBI又发现约2400份、总计超14000页涉肯尼迪遇刺案文件

来源:环球时报新媒体1月23日特朗普下令公布肯尼迪遇刺案相关机密文件图源:美媒综合福克斯新闻网和Axios网站10日报道,在总统特朗普签署行政令,要求公布“肯尼迪遇刺案”相关政府机密文件之后,美国...

2021 年 Node.js 开发人员学习路线图

Node.js自发布以来,已成为业界重要破局者之一。Uber、Medium、PayPal和沃尔玛等大型企业,纷纷将技术栈转向Node.js。Node.js支持开发功能强大的应用,例如实时追踪...

取消回复欢迎 发表评论: