Skip to content
This repository has been archived by the owner on Dec 4, 2019. It is now read-only.

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
wenzl committed Jan 11, 2018
1 parent bc38c21 commit 7427966
Show file tree
Hide file tree
Showing 22 changed files with 941 additions and 0 deletions.
34 changes: 34 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so

# Folders
_obj
_test

# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out

*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*

_testmain.go

*.exe
*.test
*.prof
.DS_Store
.vscode/
vendor/*/
images
cmd/images
cmd/config.yml
cmd/cmd
cmd/bin
qanswer
config.yml
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
build:
cd cmd && go build -o ../qanswer
96 changes: 96 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# 快速答题

《冲顶大会》,《百万英雄》... 答题神奇,顺利吃鸡!

通过抓取手机屏幕截图经过文字识别,结合搜索引擎给出一个参考值。

![题目](./docs/screenshot.png)

分析结果:

![结果](./docs/answer.png)

**结果说明:**

- 结果数:通过题目+答案的搜索形式在搜索引擎中的结果数量

- 答案出现频率:通过搜索题目,答案在第一页结果中出现的频率

结果并不是100%的,只给出一个参考值,还需用户自己判断。理论上可支持多款APP,只需要修改`config.yml`中的题目和答案的截取位置即可。


## 编译安装

安装go环境,执行 `make build` 生成`qanswer`可执行文件。

### 配置文件参考
执行`qanswer`时,默认读取当前目录下的`config.yml`配置文件。

```
# 是否开始调试模式
debug: false
# 对应的设备类型:ios or android
device: ios
# 使用的ocr工具:baidu or tesseract
ocr_type: baidu
# ios 设备连接wda的地址
wda_address: '127.0.0.1:8100'
# 截取题目的位置 :
question_x: 30
question_y: 310
question_w: 650
question_h: 135
# 截取答案的位置
answer_x: 30
answer_y: 500
answer_w: 680
answer_h: 370
#当选用baidu ocr时,需要执行api_key和secret_key
baidu_api_key: "xxx...."
baidu_secret_key: "xxx...."
```

### iOS

- 安装WDA :[iOS 真机如何安装 WebDriverAgent](https://testerhome.com/topics/7220)
- 编译得到`qanswer`文件
- 根据设备尺寸以及答题APP,修改题目和答案截取位置参数
- 开始


### Android

> TIPS:未验证


## 百度ocr
`ocr_type: baidu`

如果使用[百度ocr](https://cloud.baidu.com/product/ocr.html),则需要预先申请api key 和secret key ,并且免费的额度有限

## tesseract
`ocr_type: tesseract`

安装tesseract以及简体中文包。

以mac:为例

```
brew install tesseract
cd /usr/local/Cellar/tesseract/{version}/share/tessdata
wget https://github.com/tesseract-ocr/tessdata/raw/master/chi_sim.traineddata
```

其他系统的安装说明:https://github.com/tesseract-ocr/tesseract/wiki



### TODO:

- 验证android adb方式
- 更友好,更快的展示
- 支持google搜索
- 不同机型,不同答题app的配置参数

3 changes: 3 additions & 0 deletions cmd/gowatch.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
output: ./bin/qanswer
watch_paths:
- ../
7 changes: 7 additions & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package main

import "github.com/silenceper/qanswer"

func main() {
qanswer.Run()
}
54 changes: 54 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package config

import (
"io/ioutil"
"path/filepath"

yaml "gopkg.in/yaml.v1"
)

//Config 全局配置
type Config struct {
Debug bool `yaml:"debug"`
Device string `yaml:"device"`
OcrType string `yaml:"ocr_type"`
WdaAddress string `yaml:"wda_address"`

//baidu ocr
BaiduAPIKey string `yaml:"baidu_api_key"`
BaiduSecretKey string `yaml:"baidu_secret_key"`

//截图题目位置
QuestionX int `yaml:"question_x"`
QuestionY int `yaml:"question_y"`
QuestionW int `yaml:"question_w"`
QuestionH int `yaml:"question_h"`

//截取答案位置
AnswerX int `yaml:"answer_x"`
AnswerY int `yaml:"answer_y"`
AnswerW int `yaml:"answer_w"`
AnswerH int `yaml:"answer_h"`
}

var cfg *Config

//GetConfig 解析配置
func GetConfig() *Config {
if cfg != nil {
return cfg
}
filename, _ := filepath.Abs("./config.yml")
yamlFile, err := ioutil.ReadFile(filename)

if err != nil {
panic(err)
}
var c *Config
err = yaml.Unmarshal(yamlFile, &c)
if err != nil {
panic(err)
}
cfg = c
return cfg
}
Binary file added docs/answer.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/screenshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions gowatch.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
output: ./bin/qanswer
watch_paths:
- ../
53 changes: 53 additions & 0 deletions image.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package qanswer

import (
"fmt"
"image"

"github.com/ngaut/log"
"github.com/silenceper/qanswer/config"
"github.com/silenceper/qanswer/proto"
"github.com/silenceper/qanswer/util"
)

func saveImage(png image.Image, cfg *config.Config) error {
screenshotPath := fmt.Sprintf("%s/screenshot.png", proto.ImagePath)
err := util.SavePNG(screenshotPath, png)
if err != nil {
return fmt.Errorf("保存截图失败,%v", err)
}
log.Debugf("保存完整截图成功,%s", screenshotPath)

//裁剪图片
questionImg, answerImg, err := cutImage(png, cfg)
if err != nil {
return fmt.Errorf("截图失败,%v", err)
}
err = util.SavePNG(proto.QuestionImage, questionImg)
if err != nil {
return fmt.Errorf("保存question截图失败,%v", err)
}
log.Debugf("保存question截图成功")
err = util.SavePNG(proto.ImagePath+"/answer.png", answerImg)
if err != nil {
return fmt.Errorf("保存answer截图失败,%v", err)

}
log.Debugf("保存answer截图成功")
return nil
}

func cutImage(src image.Image, cfg *config.Config) (questionImg image.Image, answerImg image.Image, err error) {
questionImg, err = util.CutImage(src, cfg.QuestionX, cfg.QuestionY, cfg.QuestionW, cfg.QuestionH)
//questionImg, err = util.CutImage(src, 30, 310, 650, 135)
if err != nil {
return
}

answerImg, err = util.CutImage(src, cfg.AnswerX, cfg.AnswerY, cfg.AnswerW, cfg.AnswerH)
// answerImg, err = util.CutImage(src, 30, 500, 680, 370)
if err != nil {
return
}
return
}
20 changes: 20 additions & 0 deletions ocr.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package qanswer

import (
"github.com/silenceper/qanswer/config"
"github.com/silenceper/qanswer/ocr"
"github.com/silenceper/qanswer/proto"
)

//Ocr ocr 识别图片文字
type Ocr interface {
GetText(imgPath string) (string, error)
}

//NewOcr 使用哪种ocr识别
func NewOcr(cfg *config.Config) Ocr {
if cfg.OcrType == proto.OcrTesseract {
return ocr.NewTesseract(cfg)
}
return ocr.NewBaidu(cfg)
}
96 changes: 96 additions & 0 deletions ocr/baidu.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package ocr

import (
"encoding/json"
"fmt"
"net/url"
"strings"
"sync"

"github.com/silenceper/qanswer/config"
"github.com/silenceper/qanswer/util"
)

//Baidu baidu ocr api
type Baidu struct {
apiKey string
secretKey string

accessToken string
sync.RWMutex
}

type accessTokenRes struct {
AccessToken string `json:"access_token"`
ExpiresIn int32 `json:"expires_in"`
}

//wordsResults 匹配
type wordsResults struct {
WordsNum int32 `json:"words_result_num"`
WordsResult []struct {
Words string `json:"words"`
} `json:"words_result"`
}

//NewBaidu new
func NewBaidu(cfg *config.Config) *Baidu {
baidu := new(Baidu)
baidu.apiKey = cfg.BaiduAPIKey
baidu.secretKey = cfg.BaiduSecretKey
return baidu
}

//GetText 识别图片中的文字
func (baidu *Baidu) GetText(imgPath string) (string, error) {
accessToken, err := baidu.getAccessToken()
if err != nil {
return "", err
}
base64Data, err := util.OpenImageToBase64(imgPath)
if err != nil {
return "", err
}
uri := fmt.Sprintf("https://aip.baidubce.com/rest/2.0/ocr/v1/general_basic?access_token=%s", accessToken)

postData := url.Values{}
postData.Add("image", base64Data)
body, err := util.PostForm(uri, postData)
if err != nil {
return "", err
}
wordResults := new(wordsResults)
err = json.Unmarshal(body, wordResults)
if err != nil {
return "", err
}
var text string
for _, words := range wordResults.WordsResult {
text = fmt.Sprintf("%s\n%s", text, strings.TrimSpace(words.Words))
}
text = strings.TrimLeft(text, "\n")
return text, nil
}

func (baidu *Baidu) getAccessToken() (accessToken string, err error) {
baidu.Lock()
defer baidu.Unlock()
if baidu.accessToken != "" {
accessToken = baidu.accessToken
return
}
uri := fmt.Sprintf("https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=%s&client_secret=%s", baidu.apiKey, baidu.secretKey)
body, e := util.PostJSON(uri, nil, "application/json;charset=utf-8")
if e != nil {
err = e
return
}
res := new(accessTokenRes)
err = json.Unmarshal(body, res)
if err != nil {
return
}
accessToken = res.AccessToken
baidu.accessToken = accessToken
return
}
Loading

0 comments on commit 7427966

Please sign in to comment.