今天给大家带来的是我大二上短学期参与的一个C++项目,当时的项目成员有我、隔壁班一同学、我一东北室友以及一个好康的转系学姐嘻嘻,这项目名字是我那逗比室友非要加的,不仅是项目名字,就连软件的名字都充满了哲♂学气息:

大一的时候也是我们几个组了C语言的团队项目,当时做的是五子棋,我们以一两百行的代码的智障AI成功完胜另外几组写了几千行的高级AI,笑死我了哈哈哈哈哈,只可惜当时的项目丢失了,不然也想着记录一下诶 。

    这个项目的灵感来源是我室友衣柜上贴的《楚门的世界》的海报,就下面这个:

整张海报是以主人公人生各个阶段的照片拼接而成,海报放大后的局部是这样的:

受此启发,我们想着也弄个这样的东西玩玩(主要是当时大家都跑去做游戏去了,我们觉着没意思hhh),扯远了扯远了,正文开始:

一、开发平台及辅助工具

开发平台:Visual Studio
辅助库:OpenCV
界面开发:MFC
爬虫:PYcharm

二、软件功能

    我们的预期目标如上面提到的海报所示,功能框图如下:

三、方案设计

    我们将软件的工作分为4个流程:

  1. 元素图片处理(已在图库建设中同时完成):将元素图片剪切为统一大小的图片
  2. 目标图片处理:将目标图片划为多个小块,小块大小和元素图片处理后大小一样。
  3. 图片匹配:根据图片的平均像素值来匹配目标图片所有小区块和处理后元素图片。
  4. 图片拼接:根据图片匹配结果,将元素图片按序拼接,得到拼接成的目标图片。

四、方案实现

1. 元素图片处理

    给出框图如下:

    a. 爬虫
    这里需要提到的是,因为选取待拼图对象的随机性,我们并不能做到采用一系列具有关联性的图片作为图片库,因此我们采取的是按照颜色作为关键词在百度图片中进行爬图:

keyword = [
'红色', '绿色', '黄色','鸨色','赤白橡','油色','绀桔梗','踯躅色','肌色','伽罗色','花色','桜色',
'青丹','瑠璃色','蔷薇色','灰茶','莺色','琉璃绀','韩红','茶色','利久色','绀色','珊瑚色','桦茶色',
'媚茶','青蓝','红梅色','枯茶','蓝海松茶','杜若色','桃色','焦茶','青钝','胜色','薄柿','柑子色',
'抹茶色','群青色','薄红梅','杏色','黄緑','铁绀','曙色','蜜柑色','苔色','蓝铁','红色','褐色',
'若草色','青褐','赤丹','路考茶','若緑','褐返','红赤','饴色''萌黄','藤纳戸','臙脂色','丁子色','苗色'
]

     爬虫用的python写的,也不难,这里只贴出代码,不作过多解释:

"""根据搜索词下载百度图片"""import re
from pip._vendor import requests
import sys
import urllibN = 1000#keyword = '殷红'  # 关键词, 改为你想输入的词即可, 相当于在百度图片里搜索一样def getPage(keyword, page, n):page = page * nkeyword = urllib.parse.quote(keyword, safe='/')url_begin = "http://image.baidu.com/search/flip?tn=baiduimage&ie=utf-8&word="url = url_begin + keyword + "&pn=" + str(page) + "&gsm=" + str(hex(page)) + "&ct=&ic=0&lm=-1&width=0&height=0"return urldef get_onepage_urls(onepageurl):try:html = requests.get(onepageurl).textexcept Exception as e:#  print(e)pic_urls = []return pic_urlspic_urls = re.findall('"objURL":"(.*?)",', html, re.S)return pic_urlsdef down_pic(pic_urls,n):"""给出图片链接列表, 下载所有图片"""for i, pic_url in enumerate(pic_urls):try:pic = requests.get(pic_url, timeout=1)string = str(i + 1 + n*100 ) + '.jpg'with open('E:\\团队项目\\短学期\\Record\\Record\\pic2\\'+string, 'wb') as f:f.write(pic.content)print('成功下载第%s张图片' % (str(i + 1 + n*100)))except Exception as e:print('下载第%s张图片时失败: ' % (str(i + 1 + n*100)))#print(e)continuekeyword = ['红色', '绿色', '黄色','鸨色','赤白橡','油色','绀桔梗','踯躅色','肌色','伽罗色','花色','桜色','青丹','瑠璃色','蔷薇色','灰茶','莺色','琉璃绀','韩红','茶色','利久色','绀色','珊瑚色','桦茶色','媚茶','青蓝','红梅色','枯茶','蓝海松茶','杜若色','桃色','焦茶','青钝','胜色','薄柿','柑子色','抹茶色','群青色','薄红梅','杏色','黄緑','铁绀','曙色','蜜柑色','苔色','蓝铁','红色','褐色','若草色','青褐','赤丹','路考茶','若緑','褐返','红赤','饴色''萌黄','藤纳戸','臙脂色','丁子色','苗色']if __name__ == '__main__':# keyword = ''  # 关键词, 改为你想输入的词即可, 相当于在百度图片里搜索一样for j in range(0,62):page_begin = 0page_number = 10image_number = 4all_pic_urls = []while 1:if page_begin > image_number:breakprint("第%d次请求数据", [page_begin])url = getPage(keyword[j], page_begin, page_number)onepage_urls = get_onepage_urls(url)page_begin += 1print(page_begin)all_pic_urls.extend(onepage_urls)down_pic(list(set(all_pic_urls)),j)

爬虫结果:

    b. 元素图片归类
    这里采取的方法是获取归一化尺寸后的元素图片的RGB平均值,以像素值5为单位步长进行分类。

//裁剪图像
void Resize_Cut(cv::Mat& FileImg, cv::Mat& Resize_Cut_Img)
{CvSize sorSize;//原图尺寸cv::Mat SourceImg = FileImg.clone();double qw = 1.0*SourceImg.cols / W;double qh = 1.0*SourceImg.rows / H;sorSize.width = (qw > qh) ? (qh*W) : SourceImg.cols;sorSize.height = (qw > qh) ? SourceImg.rows : (qw*H);cv::Mat Cut_Img = SourceImg(Rect(0, 0, sorSize.width, sorSize.height));Resize_Cut_Img = cv::Mat::zeros(ele_h, ele_w, CV_8UC3);resize(Cut_Img, Resize_Cut_Img, Resize_Cut_Img.size());
}
//写入最终索引库
void Write()
{char imgname1[100];char imgname2[100];for (int m = 1; m <= n; m++) {sprintf_s(imgname1, "change2/%d.jpg", m);//按照图像名称读取图像cv::Mat M_img = imread(imgname1);IplImage *I_img = &IplImage(M_img);CvScalar avgChannels = cvAvg(I_img);int avgB = avgChannels.val[0];//获取B通道平均值,下同int avgG = avgChannels.val[1];int avgR = avgChannels.val[2];int R = avgR - (avgR % 5);int G = avgG - (avgG % 5);int B = avgB - (avgB % 5);sprintf_s(imgname2, "E:\\团队项目\\短学期\\Combine\\MFCApplication1\\colorstore\\%d %d %d.jpg", R, G, B);cv::imwrite(imgname2, M_img);//保存照片到指定文件夹}
}

分好类的图片库:

2.目标图片与元素图片进行匹配

    在准备好拼图元素图片库之后,只需要对目标图片进行切割,每一个小区域(ROI区域)如同上述准备元素库中获取RGB均值的方法一样,再获取一下当前ROI区域的RGB均值,然后再去元素库中调取相应的RGB图片,再按切割的行号和列号命名存储到文件夹中,下一步合成拼图只需到该文件夹中按序号调取执行拼图即可。

//目标图片与元素图片进行匹配
void Match(string aim_pic_path)
{char imgname[100];//拼图库命名char imgname1[100];//初始元素图片库命名char imgname2[100]; //处理元素图片库命名int i, j;cv::Mat M_OrgImg = imread(aim_pic_path);//读取目标图片if (!M_OrgImg.empty()){int ccc = M_OrgImg.cols;int hhh = M_OrgImg.rows;int Width = ccc - ccc % 20;int Height = hhh - hhh % 20;resize(M_OrgImg, M_OrgImg, Size(Width*L, Height*L), 0, 0);//适当裁剪原目标图片for (i = 1; i <= L * Height / ele_h; i++){for (j = 1; j <= L * Width / ele_w; j++){cv::Mat M_imageROI = M_OrgImg(Rect((j - 1) * ele_w, (i - 1) * ele_h, ele_w, ele_h));//截取ROI区域IplImage *I_imageROI = &IplImage(M_imageROI);//Mat 转 IplImageCvScalar avgChannels = cvAvg(I_imageROI);int avgB = avgChannels.val[0];//获取ROI区域的RGB值int avgG = avgChannels.val[1];int avgR = avgChannels.val[2];int R = avgR - (avgR % 5);int G = avgG - (avgG % 5);int B = avgB - (avgB % 5);sprintf_s(imgname1, ele_pic_path, R, G, B);cv::Mat M_Cut_Img = imread(imgname1);//读取相同RGB命名的图片if (M_Cut_Img.empty())//当图片库中找不到匹配的图时,直接截取当前图片进行拼图{M_Cut_Img = M_imageROI;}sprintf_s(imgname2, "order\\%d-%d.jpg", i, j);//以行列顺序存储图片cv::imwrite(imgname2, M_Cut_Img);}}}
}

匹配结果示例:

3. 拼图

    做完以上工作,我们只需要将排好序的图片进行合并,就可以得到完整的拼图图片了。在OpenCV中,提供了两个矩阵合并的函数,一个是横向合并hconcat(),另一个是纵向合并vconcat(),而图像在OpenCV中可以以Mat类型存储,Mat本身就是一个矩阵,因此我们可以很方便的利用这两个函数将我们的拼图碎片进行拼接:

//拼图碎片拼接
void Combine(std::string aim_pic_path)
{/*cv::Mat replace = imread("white.jpg");*/cv::Mat all;cv::Mat end;char a[100];char b[100];cv::Mat M_OrgImg = imread(aim_pic_path);int ccc = M_OrgImg.cols;int hhh = M_OrgImg.rows;int Width = ccc - ccc % 20;int Height = hhh - hhh % 20;for (int i = 1; i <= L * Height / ele_h; i++){sprintf_s(a, "order\\%d-%d.jpg", i, 1);all = imread(a);//读取图片for (int j = 2; j <= L * Width / ele_w; j++){sprintf_s(b, "order\\%d-%d.jpg", i, j);cv::Mat next = imread(b);hconcat(all, next, all);//图像矩阵横向合并}if (i == 1){end = all;}else{vconcat(end, all, end); //图像矩阵纵向合并}}cv::imwrite("Finalimg.jpg", end);//保存拼接图片
}

拼图结果示例:

4.美化

    为了让我们的拼图效果看起来更好,我们采取了将原图与拼图按照一定的透明度进行叠加的方法,利用OpenCV中的addWeighted()函数可以很好的实现这一要求。

//原图与拼图叠加
void Overlapping(std::string aim_pic_path)
{cv::Mat end = imread("check1.jpg");cv::Mat overlapping;cv::Mat M_OrgImg = imread(aim_pic_path);int ccc = M_OrgImg.cols;int hhh = M_OrgImg.rows;int Width = ccc - ccc % 20;int Height = hhh - hhh % 20;resize(M_OrgImg, M_OrgImg, Size(Width*L, Height*L), 0, 0);addWeighted(M_OrgImg, 0.55, end, 0.45, 0.0, overlapping);//将原图和拼接图片按一定透明度比例叠加cv::imwrite("overlapping.jpg", overlapping);
}

叠加后的结果:

5.界面

    最后呢,由于本身这是个课程设计,在设计完功能之后呢,还是得象征性地做个界面出来hhh,这部分是漂亮学姐用MFC搞的,好在我们当时写的都是封装好了的函数,所以和界面部分的对接也不算麻烦,这里界面就贴出来看看就好了,丑是挺丑的,不过基本实现了我们的功能要求hhhhh

-- 写在最后面的话 --

    这个项目的话,现在看来的话,其实确实没什么技术难度,主要还是基础语法和OpenCV的基础运用,不过对于大二短学期的项目来说也差不多了,要说改进的话,我暂时想到的有:

(1)拼图图片与目标图片的匹配不用局限于RGB这一标准,可以加入图像线条、色块甚至以某一主题作为拼图图片来源,进行更加精准的匹配;
(2)在拼图过程中减少图像的存储和读取操作,采用一边匹配一边拼图的策略,能够有效减少拼图所需时间;
(3)图片库的扩充。现在我们这个项目的图像库有262500张图片,按照(255/5)^3 = 132651的分类要求按道理来说已经足够了,但是在实际我们分类的时候仍然存在一些RGB不存在的情况,如果考虑(1)的要求的话,这些库远远不够;
(4)图片库的存储问题。几十万的图片库如何进行存储,直接打包倒是方便,不过一个项目下来4、500M也顶不住啊,弄个服务器真香。

    项目完整的工程本来是想git的,结果试了发现文件太大了传不上去,有需要的再留言吧。

团队项目(1) -- 肥宅快乐拼图相关推荐

  1. 喜迎儿童节,肥宅快乐码。

    发现编辑器不能插入视频音频资源,完整版请查看原文链接:http://dopro.io/morse.html 上周是六月一日儿童节.很多小朋友大朋友们都拥有了自己的"肥宅快乐套餐", ...

  2. 肥宅快乐还是不快乐,拓展欧几里得,exgcd???bfs

    扩展欧几里德算法 先介绍什么叫做欧几里德算法有两个数 a b,现在,我们要求 a b 的最大公约数,怎么求?枚举他们的因子?不现实,当 a b 很大的时候,枚举显得那么的naïve ,那怎么做?欧几里 ...

  3. 平分肥宅快乐水(C++)

    #include<bits/stdc++.h> using namespace std; int main(){double t;int n;cin>>t>>n;p ...

  4. 【愚公系列】2021年11月 攻防世界-进阶题-MISC-055(肥宅快乐题)

    文章目录 一.肥宅快乐题 二.答题步骤 1.视频播放器 2.base64 总结 一.肥宅快乐题 题目链接:https://adworld.xctf.org.cn/task/task_list?type ...

  5. (程序设计方法与实践)肥宅快乐串

    肥宅快乐串 Description 龙龙在研究字符串和字符串处理.龙龙发现有一些字符串让他第一眼看到就会发自内心的感到快乐,他把这些字符串称为"肥宅快乐串".龙龙进一步研究发现,一 ...

  6. 平分肥宅快乐水之辗转相除法

    链接:登录-专业IT笔试面试备考平台_牛客网 来源:牛客网 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 65536K,其他语言131072K 64bit IO Format: %l ...

  7. 爱喝「肥宅快乐水」的班长

    题目描述 现在已经是暑假了!这周日,码队的弟弟所在的班级--上海某中学高二 33 班的同学们准备在码队弟弟的带领下(码队的弟弟是这个班的班长),举办一场派对. 码队的弟弟让两名同学打开手机 「云闪付」 ...

  8. 攻防世界 Misc高手进阶区 3分题 肥宅快乐题

    前言 继续ctf的旅程 攻防世界Misc高手进阶区的3分题 本篇是肥宅快乐题的writeup 发现攻防世界的题目分数是动态的 就仅以做题时的分数为准了 解题过程 下下来一个swf文件 真就打游戏呗 不 ...

  9. 我用 80 行核心 JS 代码每个月躺着挣一瓶肥宅快乐水

    今年520活动和618活动开始的时候,京东的京豆签到福利很大,动不动就是10个20个京豆,看的小编是激动不已.就是每天都要签到,有时候忙工作忘记签到,一旦断签了心情很是低落

最新文章

  1. Go 语言编程 — net/http — HTTP 服务端
  2. 简单理解AOP(面向切面编程)
  3. Android Studio 导入应用时报错 Error:java.lang.RuntimeException: Some file crunching failed, see logs for de
  4. Ext 入门 (05) 打印+gridpanel()方法
  5. arduino运行java_IC之路(一)Proteus-Arduino仿真环境搭建
  6. 合作︱2018CCF青年精英大会首设科技创业竞赛,快来报名吧!
  7. NOIP2013华容道
  8. 妙味课堂原创JavaScript视频教程基础+提高+项目
  9. android文件管理器,10款优秀的Android文件管理器
  10. 海外客户如何进行问卷调查
  11. STM32单片机定时器
  12. shell遍历ip执行特定功能
  13. 999999数码管显示c语言,定时器1中断动态刷新从999999~0倒计时,数码管只显示有效位的C语言程序怎么编?...
  14. 【游戏开发进阶】教你使用IL2CppDumper从Unity il2cpp的二进制文件中获取类型、方法、字段等(反编译)
  15. 2021年中国云游戏产业发展环境(PEST)分析:中国云游戏服务拥有光明前景[图]
  16. 【弄nèng - Activiti6】Activiti6入门篇(十七)—— 消息中间事件
  17. Make sure that `gem install libv8 -v '3.16.14.3'` succeeds before bundling.
  18. 朋友圈爱心拼图php源码_朋友圈爱心形配图怎么弄 微信朋友圈九宫格心形拼图教程方法...
  19. JQuery冒泡(选择并上传多张图片)
  20. 京东云引擎:免费好用的web应用托管平台

热门文章

  1. 业务中台、数据中台、技术中台到底是什么?
  2. java scanner怎么循环_java怎么实现循环输入一个数?
  3. 分布式应用(Dubbo)
  4. 计算机r代表什么意思,R.I.P是什么意思?R.I.P是网络用语吗?
  5. 618真香数码产品有哪些、618数码精品清单
  6. css中什么是伪类选择器?伪类选择器的简介
  7. Python常考面试题汇总(附答案)(持续更新)
  8. 老宇哥带你玩转 ESP32:09 EEPROM的使用演示
  9. 机器学习是如何利用线性代数来解决数据问题的
  10. Java全栈(二)JavaSE:14.面向对象下