目录

​一,背景​

​二,准备工作​

​三,图片分析​

​1,颜色分析​

​2,颜色对应​

​3,定位

​四,完整代码​


一,背景

初学opencv,刚好最近在玩水排序谜题

我已经做出一个程序,输入颜色就可以给出答案。

这几天在学opencv,一下子就想到,用程序直接读颜色就不用手动输入了,而且这个应用比较简单,用来自学练手也很合适。

二,准备工作

1,opencv入门

2,给游戏换个最朴素的背景,降低处理难度。

水排序谜题——Opencv实战_#include

三,图片分析

1,颜色分析

首先看下图片颜色够不够纯,用单通道能不能很好的区分。

我们可以用map把像素值计数,然后把计数较高的打印出来看看:

map<int, int>m;
for (int i = 0; i < image.rows; i++) {
for (int j = 0; j < image.cols; j++) {
m[int(image.at<uchar>(i, j))]++;
}
}
for (auto i : m) {
if(i.second>30000)cout << i.first << " " << i.second << endl;
}

结果:

94 40562

96 34847

121 37256

126 37201

127 38850

133 35969

134 41809

142 35440

166 39223

167 38344

177 36104

184 365437

192 127481

199 38878

205 39044

231 38935

242 1074800

结果表明,颜色非常纯,所以处理起来会非常简单。

2,颜色对应

把某个颜色的色块换成白色,以94为例

for (int i = 0; i < image.rows; i++) {
for (int j = 0; j < image.cols; j++) {
if (int(image.at<uchar>(i, j)) == 94)image.at<uchar>(i, j) = 255;
//else image.at<uchar>(i, j) = 0;
}
}
Size dsize = Size(round(0.3 * image.cols), round(0.3 * image.rows));
Mat shrink;
resize(image, shrink, dsize, 0, 0, INTER_AREA);
imshow("shrink", shrink);
waitKey(0);

输出:

水排序谜题——Opencv实战_#include_02

所以94是褐色

依次类推得到所有颜色的像素值:231 黄色  121 绿色  199 翠绿 177 青色  142 蓝色  134 紫色 127 棕色 166 橙色126 红色 167 灰蓝 94 褐色  205 粉色

最亮的242显然是背景色,即下图的白色:

水排序谜题——Opencv实战_#include_03

完整代码:

void readImage()
{
string path = "D:\im.jpg";
Mat image = imread(path, IMREAD_GRAYSCALE);
if (!image.data) {
cout << "imread fail\n";
return;
}
cout << "rows=" << image.rows << endl;
cout << "cols=" << image.cols << endl;
cout << "channels=" << image.channels() << endl;
map<int, int>m;
for (int i = 0; i < image.rows; i++) {
for (int j = 0; j < image.cols; j++) {
m[int(image.at<uchar>(i, j))]++;
}
}
for (auto i : m) {
if (i.second > 30000)cout << i.first << " " << i.second << endl;
}
for (int i = 0; i < image.rows; i++) {
for (int j = 0; j < image.cols; j++) {
if (int(image.at<uchar>(i, j)) == 242)image.at<uchar>(i, j) = 255;
else image.at<uchar>(i, j) = 0;
}
}
Size dsize = Size(round(0.2 * image.cols), round(0.2 * image.rows));
Mat shrink;
resize(image, shrink, dsize, 0, 0, INTER_AREA);
imshow("shrink", shrink);
waitKey(0);
}

3,定位

因为图片是固定的,所以不需要自己实现,直接用画图软件看一下就行了。

我们用每个色块的中心位置的像素来代表整个色块。

单个色块的高度是90,所以8个纵坐标分别是770,860,950,1040,1340,1430,1520,1610

色块的横坐标之差大概是150,所以7个横坐标分别是90,240,390,540,690,840,990

这样,56个色块的坐标就全部出来了。

四,完整代码

v1.0代码

#include<iostream>
#include<string>
#include<vector>
#include<algorithm>
#include<map>
#include<opencv2/opencv.hpp>
#include<opencv2/highgui.hpp>
#include<opencv2/core/mat.hpp>
using namespace std;
using namespace cv;

#pragma comment(lib,"../x64/vc14/lib/opencv_world452d.lib")
#pragma comment(lib,"../x64/vc14/lib/opencv_world452.lib")

#define NC 12
#define SIZE 4
vector<vector<int>>vm(14); //试管
map<vector<vector<int>>,int>visit;
const int x[8] = { 770,860,950,1040,1340,1430,1520,1610 };
const int y[7] = { 90,240,390,540,690,840,990 };
const int color[NC] = { 231,121,199,177,142,134,127,166,126,167,94,205 };//12个颜色顺序无所谓

void Init()
{
string path = "D:/im.jpg";
Mat image = imread(path, IMREAD_GRAYSCALE);
if (!image.data) {
cout << "imread fail\n";
return;
}
map<int, int>m;
for (int i = 0; i < NC; i++)m[color[i]] = i + 1;//反射
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 7; j++) {
int vmi = j + (i >= 4) * 7;
int c = int(image.at<uchar>(x[i], y[j]));
if(m[c])vm[vmi].insert(vm[vmi].begin(), m[c] - 1);
}
}
}

bool oneCol(vector<int>&vi) //是否纯色
{
for (int i = 1; i < vi.size(); i++)if (vi[i] != vi[i - 1])return false;
return true;
}

bool end()
{
for (auto &vi : vm) if (!oneCol(vi))return false;
return true;
}

bool canPour(int i, int j) //i能否倒给j
{
if (i == j)return false;
int si = vm[i].size(), sj = vm[j].size();
if (si == 0 || sj == SIZE)return false;
if (sj == 0) { // 排除纯色元素倒入空试管的情况
return !oneCol(vm[i]);
}
int ci = vm[i][si - 1], cj = vm[j][sj - 1];
if (ci != cj)return false;
int num = 0;
for (int k = si - 1; k >= 0; k--)if (vm[i][k] == ci)num++;
return sj + num <= SIZE; // 加了同色必须倒完的限制,提高搜索效率
}

int pour(int i, int j) //返回倒了几个
{
int x = 0;
while (canPour(i, j)) {
auto it = vm[i].end() - 1;
vm[j].emplace_back(*it);
vm[i].erase(it);
x++;
}
return x;
}

void pour_f(int i, int j, int num) //按照数目回溯
{
while (num--) {
auto it = vm[i].end() - 1;
vm[j].emplace_back(*it);
vm[i].erase(it);
}
}

bool dfs(int deep)
{
if (visit.find(vm) != visit.end())return false;
visit[vm] = 1;
if (end() || deep>50) {
return true;
}
for (int i = 0; i < vm.size(); i++) {
for (int j = 0; j < vm.size(); j++) {
if (!canPour(i, j))continue;
if (i == 8 && j == 3 && deep==3) {
cout << 123;
}
int x = pour(i, j);
if (dfs(deep+1)) {
cout << "\ndeep = "<<deep<<" from " << i << " to " << j;
return true;
}
pour_f(j, i, x);
}
}
return false;
}

void outVm()
{
cout << endl;
for (auto vi : vm) {
int si = vi.size();
for (int i = SIZE - 1; i >= 0; i--) {
if (i >= si)cout << "-1 ";
else cout << vi[i] << " ";
}
}
}

int main()
{
Init();
dfs(0);
outVm();
return 0;
}

实战:

以第5.1关卡为例,截图,放到d盘根目录,重命名为im.jpg

水排序谜题——Opencv实战_javascript_04

运行程序,直接得到:

deep = 41 from 12 to 3

deep = 40 from 3 to 9

deep = 39 from 3 to 7

deep = 38 from 7 to 10

deep = 37 from 7 to 11

deep = 36 from 3 to 5

deep = 35 from 3 to 11

deep = 34 from 11 to 13

deep = 33 from 11 to 4

deep = 32 from 11 to 9

deep = 31 from 9 to 0

deep = 30 from 9 to 10

deep = 29 from 10 to 4

deep = 28 from 4 to 13

deep = 27 from 13 to 6

deep = 26 from 6 to 2

deep = 25 from 2 to 4

deep = 24 from 4 to 5

deep = 23 from 5 to 8

deep = 22 from 5 to 11

deep = 21 from 11 to 2

deep = 20 from 2 to 4

deep = 19 from 4 to 12

deep = 18 from 12 to 7

deep = 17 from 12 to 13

deep = 16 from 5 to 8

deep = 15 from 4 to 10

deep = 14 from 4 to 8

deep = 13 from 8 to 7

deep = 12 from 8 to 0

deep = 11 from 7 to 0

deep = 10 from 0 to 9

deep = 9 from 9 to 13

deep = 8 from 13 to 7

deep = 7 from 13 to 2

deep = 6 from 8 to 6

deep = 5 from 7 to 6

deep = 4 from 6 to 0

deep = 3 from 0 to 10

deep = 2 from 10 to 1

deep = 1 from 6 to 1

deep = 0 from 2 to 0